//===--- SIMemoryLegalizer.cpp ----------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
/// \file
/// \brief Memory legalizer - implements memory model. More information can be
/// found here:
///   http://llvm.org/docs/AMDGPUUsage.html#memory-model
///
//
//===----------------------------------------------------------------------===//

#include "AMDGPU.h"
#include "AMDGPUMachineModuleInfo.h"
#include "AMDGPUSubtarget.h"
#include "Utils/AMDGPUBaseInfo.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/IR/DiagnosticInfo.h"

using namespace llvm;
using namespace llvm::AMDGPU;

#define DEBUG_TYPE "si-memory-legalizer"
#define PASS_NAME "SI Memory Legalizer"

namespace {

class SIMemoryLegalizer final : public MachineFunctionPass {
private:
  struct AtomicInfo final {
    SyncScope::ID SSID = SyncScope::System;
    AtomicOrdering Ordering = AtomicOrdering::SequentiallyConsistent;
    AtomicOrdering FailureOrdering = AtomicOrdering::SequentiallyConsistent;

    AtomicInfo() {}

    AtomicInfo(SyncScope::ID SSID,
               AtomicOrdering Ordering,
               AtomicOrdering FailureOrdering)
        : SSID(SSID),
          Ordering(Ordering),
          FailureOrdering(FailureOrdering) {}

    AtomicInfo(const MachineMemOperand *MMO)
        : SSID(MMO->getSyncScopeID()),
          Ordering(MMO->getOrdering()),
          FailureOrdering(MMO->getFailureOrdering()) {}
  };

  /// \brief LLVM context.
  LLVMContext *CTX = nullptr;
  /// \brief Machine module info.
  const AMDGPUMachineModuleInfo *MMI = nullptr;
  /// \brief Instruction info.
  const SIInstrInfo *TII = nullptr;

  /// \brief Immediate for "vmcnt(0)".
  unsigned Vmcnt0Immediate = 0;
  /// \brief Opcode for cache invalidation instruction (L1).
  unsigned Wbinvl1Opcode = 0;

  /// \brief List of atomic pseudo instructions.
  std::list<MachineBasicBlock::iterator> AtomicPseudoMIs;

  /// \brief Inserts "buffer_wbinvl1_vol" instruction \p Before or after \p MI.
  /// Always returns true.
  bool insertBufferWbinvl1Vol(MachineBasicBlock::iterator &MI,
                              bool Before = true) const;
  /// \brief Inserts "s_waitcnt vmcnt(0)" instruction \p Before or after \p MI.
  /// Always returns true.
  bool insertWaitcntVmcnt0(MachineBasicBlock::iterator &MI,
                           bool Before = true) const;

  /// \brief Sets GLC bit if present in \p MI. Returns true if \p MI is
  /// modified, false otherwise.
  bool setGLC(const MachineBasicBlock::iterator &MI) const;

  /// \brief Removes all processed atomic pseudo instructions from the current
  /// function. Returns true if current function is modified, false otherwise.
  bool removeAtomicPseudoMIs();

  /// \brief Reports unknown synchronization scope used in \p MI to LLVM
  /// context.
  void reportUnknownSynchScope(const MachineBasicBlock::iterator &MI);

  /// \returns Atomic fence info if \p MI is an atomic fence operation,
  /// "None" otherwise.
  Optional<AtomicInfo> getAtomicFenceInfo(
      const MachineBasicBlock::iterator &MI) const;
  /// \returns Atomic load info if \p MI is an atomic load operation,
  /// "None" otherwise.
  Optional<AtomicInfo> getAtomicLoadInfo(
      const MachineBasicBlock::iterator &MI) const;
  /// \returns Atomic store info if \p MI is an atomic store operation,
  /// "None" otherwise.
  Optional<AtomicInfo> getAtomicStoreInfo(
      const MachineBasicBlock::iterator &MI) const;
  /// \returns Atomic cmpxchg info if \p MI is an atomic cmpxchg operation,
  /// "None" otherwise.
  Optional<AtomicInfo> getAtomicCmpxchgInfo(
      const MachineBasicBlock::iterator &MI) const;
  /// \returns Atomic rmw info if \p MI is an atomic rmw operation,
  /// "None" otherwise.
  Optional<AtomicInfo> getAtomicRmwInfo(
      const MachineBasicBlock::iterator &MI) const;

  /// \brief Expands atomic fence operation \p MI. Returns true if
  /// instructions are added/deleted or \p MI is modified, false otherwise.
  bool expandAtomicFence(const AtomicInfo &AI,
                         MachineBasicBlock::iterator &MI);
  /// \brief Expands atomic load operation \p MI. Returns true if
  /// instructions are added/deleted or \p MI is modified, false otherwise.
  bool expandAtomicLoad(const AtomicInfo &AI,
                        MachineBasicBlock::iterator &MI);
  /// \brief Expands atomic store operation \p MI. Returns true if
  /// instructions are added/deleted or \p MI is modified, false otherwise.
  bool expandAtomicStore(const AtomicInfo &AI,
                         MachineBasicBlock::iterator &MI);
  /// \brief Expands atomic cmpxchg operation \p MI. Returns true if
  /// instructions are added/deleted or \p MI is modified, false otherwise.
  bool expandAtomicCmpxchg(const AtomicInfo &AI,
                           MachineBasicBlock::iterator &MI);
  /// \brief Expands atomic rmw operation \p MI. Returns true if
  /// instructions are added/deleted or \p MI is modified, false otherwise.
  bool expandAtomicRmw(const AtomicInfo &AI,
                       MachineBasicBlock::iterator &MI);

public:
  static char ID;

  SIMemoryLegalizer()
      : MachineFunctionPass(ID) {}

  void getAnalysisUsage(AnalysisUsage &AU) const override {
    AU.setPreservesCFG();
    MachineFunctionPass::getAnalysisUsage(AU);
  }

  StringRef getPassName() const override {
    return PASS_NAME;
  }

  bool runOnMachineFunction(MachineFunction &MF) override;
};

} // end namespace anonymous

bool SIMemoryLegalizer::insertBufferWbinvl1Vol(MachineBasicBlock::iterator &MI,
                                               bool Before) const {
  MachineBasicBlock &MBB = *MI->getParent();
  DebugLoc DL = MI->getDebugLoc();

  if (!Before)
    ++MI;

  BuildMI(MBB, MI, DL, TII->get(Wbinvl1Opcode));

  if (!Before)
    --MI;

  return true;
}

bool SIMemoryLegalizer::insertWaitcntVmcnt0(MachineBasicBlock::iterator &MI,
                                            bool Before) const {
  MachineBasicBlock &MBB = *MI->getParent();
  DebugLoc DL = MI->getDebugLoc();

  if (!Before)
    ++MI;

  BuildMI(MBB, MI, DL, TII->get(AMDGPU::S_WAITCNT)).addImm(Vmcnt0Immediate);

  if (!Before)
    --MI;

  return true;
}

bool SIMemoryLegalizer::setGLC(const MachineBasicBlock::iterator &MI) const {
  int GLCIdx = AMDGPU::getNamedOperandIdx(MI->getOpcode(), AMDGPU::OpName::glc);
  if (GLCIdx == -1)
    return false;

  MachineOperand &GLC = MI->getOperand(GLCIdx);
  if (GLC.getImm() == 1)
    return false;

  GLC.setImm(1);
  return true;
}

bool SIMemoryLegalizer::removeAtomicPseudoMIs() {
  if (AtomicPseudoMIs.empty())
    return false;

  for (auto &MI : AtomicPseudoMIs)
    MI->eraseFromParent();

  AtomicPseudoMIs.clear();
  return true;
}

void SIMemoryLegalizer::reportUnknownSynchScope(
    const MachineBasicBlock::iterator &MI) {
  DiagnosticInfoUnsupported Diag(*MI->getParent()->getParent()->getFunction(),
                                 "Unsupported synchronization scope");
  CTX->diagnose(Diag);
}

Optional<SIMemoryLegalizer::AtomicInfo> SIMemoryLegalizer::getAtomicFenceInfo(
    const MachineBasicBlock::iterator &MI) const {
  assert(MI->getDesc().TSFlags & SIInstrFlags::maybeAtomic);

  if (MI->getOpcode() != AMDGPU::ATOMIC_FENCE)
    return None;

  SyncScope::ID SSID =
      static_cast<SyncScope::ID>(MI->getOperand(1).getImm());
  AtomicOrdering Ordering =
      static_cast<AtomicOrdering>(MI->getOperand(0).getImm());
  return AtomicInfo(SSID, Ordering, AtomicOrdering::NotAtomic);
}

Optional<SIMemoryLegalizer::AtomicInfo> SIMemoryLegalizer::getAtomicLoadInfo(
    const MachineBasicBlock::iterator &MI) const {
  assert(MI->getDesc().TSFlags & SIInstrFlags::maybeAtomic);

  if (!(MI->mayLoad() && !MI->mayStore()))
    return None;
  if (!MI->hasOneMemOperand())
    return AtomicInfo();

  const MachineMemOperand *MMO = *MI->memoperands_begin();
  if (!MMO->isAtomic())
    return None;

  return AtomicInfo(MMO);
}

Optional<SIMemoryLegalizer::AtomicInfo> SIMemoryLegalizer::getAtomicStoreInfo(
    const MachineBasicBlock::iterator &MI) const {
  assert(MI->getDesc().TSFlags & SIInstrFlags::maybeAtomic);

  if (!(!MI->mayLoad() && MI->mayStore()))
    return None;
  if (!MI->hasOneMemOperand())
    return AtomicInfo();

  const MachineMemOperand *MMO = *MI->memoperands_begin();
  if (!MMO->isAtomic())
    return None;

  return AtomicInfo(MMO);
}

Optional<SIMemoryLegalizer::AtomicInfo> SIMemoryLegalizer::getAtomicCmpxchgInfo(
    const MachineBasicBlock::iterator &MI) const {
  assert(MI->getDesc().TSFlags & SIInstrFlags::maybeAtomic);

  if (!(MI->mayLoad() && MI->mayStore()))
    return None;
  if (!MI->hasOneMemOperand())
    return AtomicInfo();

  const MachineMemOperand *MMO = *MI->memoperands_begin();
  if (!MMO->isAtomic())
    return None;
  if (MMO->getFailureOrdering() == AtomicOrdering::NotAtomic)
    return None;

  return AtomicInfo(MMO);
}

Optional<SIMemoryLegalizer::AtomicInfo> SIMemoryLegalizer::getAtomicRmwInfo(
    const MachineBasicBlock::iterator &MI) const {
  assert(MI->getDesc().TSFlags & SIInstrFlags::maybeAtomic);

  if (!(MI->mayLoad() && MI->mayStore()))
    return None;
  if (!MI->hasOneMemOperand())
    return AtomicInfo();

  const MachineMemOperand *MMO = *MI->memoperands_begin();
  if (!MMO->isAtomic())
    return None;
  if (MMO->getFailureOrdering() != AtomicOrdering::NotAtomic)
    return None;

  return AtomicInfo(MMO);
}

bool SIMemoryLegalizer::expandAtomicFence(const AtomicInfo &AI,
                                          MachineBasicBlock::iterator &MI) {
  assert(MI->getOpcode() == AMDGPU::ATOMIC_FENCE);

  bool Changed = false;
  if (AI.SSID == SyncScope::System ||
      AI.SSID == MMI->getAgentSSID()) {
    if (AI.Ordering == AtomicOrdering::Acquire ||
        AI.Ordering == AtomicOrdering::Release ||
        AI.Ordering == AtomicOrdering::AcquireRelease ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent)
      Changed |= insertWaitcntVmcnt0(MI);

    if (AI.Ordering == AtomicOrdering::Acquire ||
        AI.Ordering == AtomicOrdering::AcquireRelease ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent)
      Changed |= insertBufferWbinvl1Vol(MI);

    AtomicPseudoMIs.push_back(MI);
    return Changed;
  } else if (AI.SSID == SyncScope::SingleThread ||
             AI.SSID == MMI->getWorkgroupSSID() ||
             AI.SSID == MMI->getWavefrontSSID()) {
    AtomicPseudoMIs.push_back(MI);
    return Changed;
  } else {
    reportUnknownSynchScope(MI);
    return Changed;
  }
}

bool SIMemoryLegalizer::expandAtomicLoad(const AtomicInfo &AI,
                                         MachineBasicBlock::iterator &MI) {
  assert(MI->mayLoad() && !MI->mayStore());

  bool Changed = false;
  if (AI.SSID == SyncScope::System ||
      AI.SSID == MMI->getAgentSSID()) {
    if (AI.Ordering == AtomicOrdering::Acquire ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent)
      Changed |= setGLC(MI);

    if (AI.Ordering == AtomicOrdering::SequentiallyConsistent)
      Changed |= insertWaitcntVmcnt0(MI);

    if (AI.Ordering == AtomicOrdering::Acquire ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent) {
      Changed |= insertWaitcntVmcnt0(MI, false);
      Changed |= insertBufferWbinvl1Vol(MI, false);
    }

    return Changed;
  } else if (AI.SSID == SyncScope::SingleThread ||
             AI.SSID == MMI->getWorkgroupSSID() ||
             AI.SSID == MMI->getWavefrontSSID()) {
    return Changed;
  } else {
    reportUnknownSynchScope(MI);
    return Changed;
  }
}

bool SIMemoryLegalizer::expandAtomicStore(const AtomicInfo &AI,
                                          MachineBasicBlock::iterator &MI) {
  assert(!MI->mayLoad() && MI->mayStore());

  bool Changed = false;
  if (AI.SSID == SyncScope::System ||
      AI.SSID == MMI->getAgentSSID()) {
    if (AI.Ordering == AtomicOrdering::Release ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent)
      Changed |= insertWaitcntVmcnt0(MI);

    return Changed;
  } else if (AI.SSID == SyncScope::SingleThread ||
             AI.SSID == MMI->getWorkgroupSSID() ||
             AI.SSID == MMI->getWavefrontSSID()) {
    return Changed;
  } else {
    reportUnknownSynchScope(MI);
    return Changed;
  }
}

bool SIMemoryLegalizer::expandAtomicCmpxchg(const AtomicInfo &AI,
                                            MachineBasicBlock::iterator &MI) {
  assert(MI->mayLoad() && MI->mayStore());

  bool Changed = false;
  if (AI.SSID == SyncScope::System ||
      AI.SSID == MMI->getAgentSSID()) {
    if (AI.Ordering == AtomicOrdering::Release ||
        AI.Ordering == AtomicOrdering::AcquireRelease ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent ||
        AI.FailureOrdering == AtomicOrdering::SequentiallyConsistent)
      Changed |= insertWaitcntVmcnt0(MI);

    if (AI.Ordering == AtomicOrdering::Acquire ||
        AI.Ordering == AtomicOrdering::AcquireRelease ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent ||
        AI.FailureOrdering == AtomicOrdering::Acquire ||
        AI.FailureOrdering == AtomicOrdering::SequentiallyConsistent) {
      Changed |= insertWaitcntVmcnt0(MI, false);
      Changed |= insertBufferWbinvl1Vol(MI, false);
    }

    return Changed;
  } else if (AI.SSID == SyncScope::SingleThread ||
             AI.SSID == MMI->getWorkgroupSSID() ||
             AI.SSID == MMI->getWavefrontSSID()) {
    Changed |= setGLC(MI);
    return Changed;
  } else {
    reportUnknownSynchScope(MI);
    return Changed;
  }
}

bool SIMemoryLegalizer::expandAtomicRmw(const AtomicInfo &AI,
                                        MachineBasicBlock::iterator &MI) {
  assert(MI->mayLoad() && MI->mayStore());

  bool Changed = false;
  if (AI.SSID == SyncScope::System ||
      AI.SSID == MMI->getAgentSSID()) {
    if (AI.Ordering == AtomicOrdering::Release ||
        AI.Ordering == AtomicOrdering::AcquireRelease ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent)
      Changed |= insertWaitcntVmcnt0(MI);

    if (AI.Ordering == AtomicOrdering::Acquire ||
        AI.Ordering == AtomicOrdering::AcquireRelease ||
        AI.Ordering == AtomicOrdering::SequentiallyConsistent) {
      Changed |= insertWaitcntVmcnt0(MI, false);
      Changed |= insertBufferWbinvl1Vol(MI, false);
    }

    return Changed;
  } else if (AI.SSID == SyncScope::SingleThread ||
             AI.SSID == MMI->getWorkgroupSSID() ||
             AI.SSID == MMI->getWavefrontSSID()) {
    Changed |= setGLC(MI);
    return Changed;
  } else {
    reportUnknownSynchScope(MI);
    return Changed;
  }
}

bool SIMemoryLegalizer::runOnMachineFunction(MachineFunction &MF) {
  bool Changed = false;
  const SISubtarget &ST = MF.getSubtarget<SISubtarget>();
  const IsaInfo::IsaVersion IV = IsaInfo::getIsaVersion(ST.getFeatureBits());

  CTX = &MF.getFunction()->getContext();
  MMI = &MF.getMMI().getObjFileInfo<AMDGPUMachineModuleInfo>();
  TII = ST.getInstrInfo();

  Vmcnt0Immediate =
      AMDGPU::encodeWaitcnt(IV, 0, getExpcntBitMask(IV), getLgkmcntBitMask(IV));
  Wbinvl1Opcode = ST.getGeneration() <= AMDGPUSubtarget::SOUTHERN_ISLANDS ?
      AMDGPU::BUFFER_WBINVL1 : AMDGPU::BUFFER_WBINVL1_VOL;

  for (auto &MBB : MF) {
    for (auto MI = MBB.begin(); MI != MBB.end(); ++MI) {
      if (!(MI->getDesc().TSFlags & SIInstrFlags::maybeAtomic))
        continue;

      if (const auto &AI = getAtomicFenceInfo(MI))
        Changed |= expandAtomicFence(AI.getValue(), MI);
      else if (const auto &AI = getAtomicLoadInfo(MI))
        Changed |= expandAtomicLoad(AI.getValue(), MI);
      else if (const auto &AI = getAtomicStoreInfo(MI))
        Changed |= expandAtomicStore(AI.getValue(), MI);
      else if (const auto &AI = getAtomicCmpxchgInfo(MI))
        Changed |= expandAtomicCmpxchg(AI.getValue(), MI);
      else if (const auto &AI = getAtomicRmwInfo(MI))
        Changed |= expandAtomicRmw(AI.getValue(), MI);
    }
  }

  Changed |= removeAtomicPseudoMIs();
  return Changed;
}

INITIALIZE_PASS(SIMemoryLegalizer, DEBUG_TYPE, PASS_NAME, false, false)

char SIMemoryLegalizer::ID = 0;
char &llvm::SIMemoryLegalizerID = SIMemoryLegalizer::ID;

FunctionPass *llvm::createSIMemoryLegalizerPass() {
  return new SIMemoryLegalizer();
}
