//===- ScopDetectionDiagnostic.h - Diagnostic for ScopDetection -*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Small set of diagnostic helper classes to encapsulate any errors occurred
// during the detection of Scops.
//
// The ScopDetection defines a set of error classes (via Statistic variables)
// that groups a number of individual errors into a group, e.g. non-affinity
// related errors.
// On error we generate an object that carries enough additional information
// to diagnose the error and generate a helpful error message.
//
//===----------------------------------------------------------------------===//

#ifndef POLLY_SCOPDETECTIONDIAGNOSTIC_H
#define POLLY_SCOPDETECTIONDIAGNOSTIC_H

#include "llvm/Analysis/LoopInfo.h"
#include "llvm/IR/DebugLoc.h"
#include "llvm/IR/Instruction.h"
#include <cstddef>

namespace llvm {
class AliasSet;
class BasicBlock;
class OptimizationRemarkEmitter;
class Region;
class SCEV;
} // namespace llvm

namespace polly {
using llvm::AliasSet;
using llvm::BasicBlock;
using llvm::DebugLoc;
using llvm::Instruction;
using llvm::Loop;
using llvm::OptimizationRemarkEmitter;
using llvm::raw_ostream;
using llvm::Region;
using llvm::SCEV;
using llvm::SmallVector;
using llvm::Value;

/// Type to hold region delimiters (entry & exit block).
using BBPair = std::pair<BasicBlock *, BasicBlock *>;

/// Return the region delimiters (entry & exit block) of @p R.
BBPair getBBPairForRegion(const Region *R);

/// Set the begin and end source location for the region limited by @p P.
void getDebugLocations(const BBPair &P, DebugLoc &Begin, DebugLoc &End);

class RejectLog;

/// Emit optimization remarks about the rejected regions to the user.
///
/// This emits the content of the reject log as optimization remarks.
/// Remember to at least track failures (-polly-detect-track-failures).
/// @param P The region delimiters (entry & exit) we emit remarks for.
/// @param Log The error log containing all messages being emitted as remark.
void emitRejectionRemarks(const BBPair &P, const RejectLog &Log,
                          OptimizationRemarkEmitter &ORE);

// Discriminator for LLVM-style RTTI (dyn_cast<> et al.)
enum class RejectReasonKind {
  // CFG Category
  CFG,
  InvalidTerminator,
  IrreducibleRegion,
  UnreachableInExit,
  LastCFG,

  // Non-Affinity
  AffFunc,
  UndefCond,
  InvalidCond,
  UndefOperand,
  NonAffBranch,
  NoBasePtr,
  UndefBasePtr,
  VariantBasePtr,
  NonAffineAccess,
  DifferentElementSize,
  LastAffFunc,

  LoopBound,
  LoopHasNoExit,
  LoopHasMultipleExits,
  LoopOnlySomeLatches,

  FuncCall,
  NonSimpleMemoryAccess,

  Alias,

  // Other
  Other,
  IntToPtr,
  Alloca,
  UnknownInst,
  Entry,
  Unprofitable,
  LastOther
};

//===----------------------------------------------------------------------===//
/// Base class of all reject reasons found during Scop detection.
///
/// Subclasses of RejectReason should provide means to capture enough
/// diagnostic information to help clients figure out what and where something
/// went wrong in the Scop detection.
class RejectReason {
private:
  const RejectReasonKind Kind;

protected:
  static const DebugLoc Unknown;

public:
  RejectReason(RejectReasonKind K);

  virtual ~RejectReason() = default;

  RejectReasonKind getKind() const { return Kind; }

  /// Generate the remark name to identify this remark.
  ///
  /// @return A short string that identifies the error.
  virtual std::string getRemarkName() const = 0;

  /// Get the Basic Block containing this remark.
  ///
  /// @return The Basic Block containing this remark.
  virtual const Value *getRemarkBB() const = 0;

  /// Generate a reasonable diagnostic message describing this error.
  ///
  /// @return A debug message representing this error.
  virtual std::string getMessage() const = 0;

  /// Generate a message for the end-user describing this error.
  ///
  /// The message provided has to be suitable for the end-user. So it should
  /// not reference any LLVM internal data structures or terminology.
  /// Ideally, the message helps the end-user to increase the size of the
  /// regions amenable to Polly.
  ///
  /// @return A short message representing this error.
  virtual std::string getEndUserMessage() const { return "Unspecified error."; }

  /// Get the source location of this error.
  ///
  /// @return The debug location for this error.
  virtual const DebugLoc &getDebugLoc() const;
};

using RejectReasonPtr = std::shared_ptr<RejectReason>;

/// Stores all errors that occurred during the detection.
class RejectLog {
  Region *R;
  SmallVector<RejectReasonPtr, 1> ErrorReports;

public:
  explicit RejectLog(Region *R) : R(R) {}

  using iterator = SmallVector<RejectReasonPtr, 1>::const_iterator;

  iterator begin() const { return ErrorReports.begin(); }
  iterator end() const { return ErrorReports.end(); }
  size_t size() const { return ErrorReports.size(); }

  /// Returns true, if we store at least one error.
  ///
  /// @return true, if we store at least one error.
  bool hasErrors() const { return size() > 0; }

  void print(raw_ostream &OS, int level = 0) const;

  const Region *region() const { return R; }
  void report(RejectReasonPtr Reject) { ErrorReports.push_back(Reject); }
};

//===----------------------------------------------------------------------===//
/// Base class for CFG related reject reasons.
///
/// Scop candidates that violate structural restrictions can be grouped under
/// this reject reason class.
class ReportCFG : public RejectReason {
public:
  ReportCFG(const RejectReasonKind K);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures bad terminator within a Scop candidate.
class ReportInvalidTerminator : public ReportCFG {
  BasicBlock *BB;

public:
  ReportInvalidTerminator(BasicBlock *BB)
      : ReportCFG(RejectReasonKind::InvalidTerminator), BB(BB) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures irreducible regions in CFG.
class ReportIrreducibleRegion : public ReportCFG {
  Region *R;
  DebugLoc DbgLoc;

public:
  ReportIrreducibleRegion(Region *R, DebugLoc DbgLoc)
      : ReportCFG(RejectReasonKind::IrreducibleRegion), R(R), DbgLoc(DbgLoc) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  std::string getEndUserMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures regions with an unreachable in the exit block.
class ReportUnreachableInExit : public ReportCFG {
  BasicBlock *BB;
  DebugLoc DbgLoc;

public:
  ReportUnreachableInExit(BasicBlock *BB, DebugLoc DbgLoc)
      : ReportCFG(RejectReasonKind::UnreachableInExit), BB(BB), DbgLoc(DbgLoc) {
  }

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  std::string getEndUserMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Base class for non-affine reject reasons.
///
/// Scop candidates that violate restrictions to affinity are reported under
/// this class.
class ReportAffFunc : public RejectReason {
protected:
  // The instruction that caused non-affinity to occur.
  const Instruction *Inst;

public:
  ReportAffFunc(const RejectReasonKind K, const Instruction *Inst);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  const DebugLoc &getDebugLoc() const override { return Inst->getDebugLoc(); }
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures a condition that is based on an 'undef' value.
class ReportUndefCond : public ReportAffFunc {
  // The BasicBlock we found the broken condition in.
  BasicBlock *BB;

public:
  ReportUndefCond(const Instruction *Inst, BasicBlock *BB)
      : ReportAffFunc(RejectReasonKind::UndefCond, Inst), BB(BB) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures an invalid condition
///
/// Conditions have to be either constants or icmp instructions.
class ReportInvalidCond : public ReportAffFunc {
  // The BasicBlock we found the broken condition in.
  BasicBlock *BB;

public:
  ReportInvalidCond(const Instruction *Inst, BasicBlock *BB)
      : ReportAffFunc(RejectReasonKind::InvalidCond, Inst), BB(BB) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures an undefined operand.
class ReportUndefOperand : public ReportAffFunc {
  // The BasicBlock we found the undefined operand in.
  BasicBlock *BB;

public:
  ReportUndefOperand(BasicBlock *BB, const Instruction *Inst)
      : ReportAffFunc(RejectReasonKind::UndefOperand, Inst), BB(BB) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures a non-affine branch.
class ReportNonAffBranch : public ReportAffFunc {
  // The BasicBlock we found the non-affine branch in.
  BasicBlock *BB;

  /// LHS & RHS of the failed condition.
  //@{
  const SCEV *LHS;
  const SCEV *RHS;
  //@}

public:
  ReportNonAffBranch(BasicBlock *BB, const SCEV *LHS, const SCEV *RHS,
                     const Instruction *Inst)
      : ReportAffFunc(RejectReasonKind::NonAffBranch, Inst), BB(BB), LHS(LHS),
        RHS(RHS) {}

  const SCEV *lhs() { return LHS; }
  const SCEV *rhs() { return RHS; }

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures a missing base pointer.
class ReportNoBasePtr : public ReportAffFunc {
public:
  ReportNoBasePtr(const Instruction *Inst)
      : ReportAffFunc(RejectReasonKind::NoBasePtr, Inst) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures an undefined base pointer.
class ReportUndefBasePtr : public ReportAffFunc {
public:
  ReportUndefBasePtr(const Instruction *Inst)
      : ReportAffFunc(RejectReasonKind::UndefBasePtr, Inst) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures a base pointer that is not invariant in the region.
class ReportVariantBasePtr : public ReportAffFunc {
  // The variant base pointer.
  Value *BaseValue;

public:
  ReportVariantBasePtr(Value *BaseValue, const Instruction *Inst)
      : ReportAffFunc(RejectReasonKind::VariantBasePtr, Inst),
        BaseValue(BaseValue) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures a non-affine access function.
class ReportNonAffineAccess : public ReportAffFunc {
  // The non-affine access function.
  const SCEV *AccessFunction;

  // The base pointer of the memory access.
  const Value *BaseValue;

public:
  ReportNonAffineAccess(const SCEV *AccessFunction, const Instruction *Inst,
                        const Value *V)
      : ReportAffFunc(RejectReasonKind::NonAffineAccess, Inst),
        AccessFunction(AccessFunction), BaseValue(V) {}

  const SCEV *get() { return AccessFunction; }

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Report array accesses with differing element size.
class ReportDifferentArrayElementSize : public ReportAffFunc {
  // The base pointer of the memory access.
  const Value *BaseValue;

public:
  ReportDifferentArrayElementSize(const Instruction *Inst, const Value *V)
      : ReportAffFunc(RejectReasonKind::DifferentElementSize, Inst),
        BaseValue(V) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with non affine loop bounds.
class ReportLoopBound : public RejectReason {
  // The offending loop.
  Loop *L;

  // The non-affine loop bound.
  const SCEV *LoopCount;

  // A copy of the offending loop's debug location.
  const DebugLoc Loc;

public:
  ReportLoopBound(Loop *L, const SCEV *LoopCount);

  const SCEV *loopCount() { return LoopCount; }

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors when loop has no exit.
class ReportLoopHasNoExit : public RejectReason {
  /// The loop that has no exit.
  Loop *L;

  const DebugLoc Loc;

public:
  ReportLoopHasNoExit(Loop *L)
      : RejectReason(RejectReasonKind::LoopHasNoExit), L(L),
        Loc(L->getStartLoc()) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors when a loop has multiple exists.
class ReportLoopHasMultipleExits : public RejectReason {
  /// The loop that has multiple exits.
  Loop *L;

  const DebugLoc Loc;

public:
  ReportLoopHasMultipleExits(Loop *L)
      : RejectReason(RejectReasonKind::LoopHasMultipleExits), L(L),
        Loc(L->getStartLoc()) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors when not all loop latches are part of the scop.
class ReportLoopOnlySomeLatches : public RejectReason {
  /// The loop for which not all loop latches are part of the scop.
  Loop *L;

  const DebugLoc Loc;

public:
  ReportLoopOnlySomeLatches(Loop *L)
      : RejectReason(RejectReasonKind::LoopOnlySomeLatches), L(L),
        Loc(L->getStartLoc()) {}

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with non-side-effect-known function calls.
class ReportFuncCall : public RejectReason {
  // The offending call instruction.
  Instruction *Inst;

public:
  ReportFuncCall(Instruction *Inst);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with aliasing.
class ReportAlias : public RejectReason {
public:
  using PointerSnapshotTy = std::vector<const Value *>;

private:
  /// Format an invalid alias set.
  ///
  //  @param Prefix A prefix string to put before the list of aliasing pointers.
  //  @param Suffix A suffix string to put after the list of aliasing pointers.
  std::string formatInvalidAlias(std::string Prefix = "",
                                 std::string Suffix = "") const;

  Instruction *Inst;

  // A snapshot of the llvm values that took part in the aliasing error.
  mutable PointerSnapshotTy Pointers;

public:
  ReportAlias(Instruction *Inst, AliasSet &AS);

  const PointerSnapshotTy &getPointers() const { return Pointers; }

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  std::string getEndUserMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Base class for otherwise ungrouped reject reasons.
class ReportOther : public RejectReason {
public:
  ReportOther(const RejectReasonKind K);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  std::string getMessage() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with bad IntToPtr instructions.
class ReportIntToPtr : public ReportOther {
  // The offending base value.
  Instruction *BaseValue;

public:
  ReportIntToPtr(Instruction *BaseValue);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with alloca instructions.
class ReportAlloca : public ReportOther {
  Instruction *Inst;

public:
  ReportAlloca(Instruction *Inst);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with unknown instructions.
class ReportUnknownInst : public ReportOther {
  Instruction *Inst;

public:
  ReportUnknownInst(Instruction *Inst);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with regions containing the function entry block.
class ReportEntry : public ReportOther {
  BasicBlock *BB;

public:
  ReportEntry(BasicBlock *BB);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  std::string getEndUserMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Report regions that seem not profitable to be optimized.
class ReportUnprofitable : public ReportOther {
  Region *R;

public:
  ReportUnprofitable(Region *R);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  std::string getEndUserMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  //@}
};

//===----------------------------------------------------------------------===//
/// Captures errors with non-simple memory accesses.
class ReportNonSimpleMemoryAccess : public ReportOther {
  // The offending call instruction.
  Instruction *Inst;

public:
  ReportNonSimpleMemoryAccess(Instruction *Inst);

  /// @name LLVM-RTTI interface
  //@{
  static bool classof(const RejectReason *RR);
  //@}

  /// @name RejectReason interface
  //@{
  std::string getRemarkName() const override;
  const Value *getRemarkBB() const override;
  std::string getMessage() const override;
  const DebugLoc &getDebugLoc() const override;
  std::string getEndUserMessage() const override;
  //@}
};
} // namespace polly

#endif // POLLY_SCOPDETECTIONDIAGNOSTIC_H
