//===--- Argument.swift - Defines the Argument classes --------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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
//
//===----------------------------------------------------------------------===//

import Basic
import AST
import SILBridging

/// A basic block argument.
///
/// Maps to both, SILPhiArgument and SILFunctionArgument.
public class Argument : Value, Hashable {
  public var definingInstruction: Instruction? { nil }

  public var parentBlock: BasicBlock {
    return bridged.getParent().block
  }

  public var bridged: BridgedArgument { BridgedArgument(obj: SwiftObject(self)) }

  public var index: Int {
    return parentBlock.arguments.firstIndex(of: self)!
  }

  public var isReborrow: Bool { bridged.isReborrow() }

  public func set(reborrow: Bool, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.setReborrow(reborrow)
  }

  public func set(ownership: Ownership, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.setOwnership(ownership._bridged)
  }

  public var isLexical: Bool { false }

  public var decl: ValueDecl? { bridged.getDecl().getAs(ValueDecl.self) }

  public func findVarDecl() -> VarDecl? {
    if let varDecl = decl as? VarDecl {
      return varDecl
    }
    return findVarDeclFromDebugUsers()
  }

  public var sourceLoc: SourceLoc? { findVarDecl()?.nameLoc }

  public static func ==(lhs: Argument, rhs: Argument) -> Bool {
    lhs === rhs
  }

  public func hash(into hasher: inout Hasher) {
    hasher.combine(ObjectIdentifier(self))
  }
}

final public class FunctionArgument : Argument {
  public var convention: ArgumentConvention {
    parentFunction.argumentConventions[index]
  }

  public override var isLexical: Bool {
    bridged.FunctionArgument_isLexical()
  }

  public var isClosureCapture: Bool {
    bridged.FunctionArgument_isClosureCapture()
  }

  public var isSelf: Bool {
    parentFunction.argumentConventions.selfIndex == index
  }

  // FIXME: This is incorrect in two cases: it does not include the
  // indirect error result, and, prior to address lowering, does not
  // include pack results.
  public var isIndirectResult: Bool {
    return index < parentFunction.numIndirectResultArguments
  }

  /// If the function's result depends on this argument, return the
  /// kind of dependence.
  public var resultDependence: LifetimeDependenceConvention? {
    parentFunction.argumentConventions[resultDependsOn: index]
  }

  /// Copies the following flags from `arg`:
  /// 1. noImplicitCopy
  /// 2. lifetimeAnnotation
  /// 3. closureCapture
  /// 4. parameterPack
  public func copyFlags(from arg: FunctionArgument, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.copyFlags(arg.bridged)
  }
}

public struct Phi {
  public let value: Argument

  // TODO: Remove the CondBr case. All passes avoid critical edges. It
  // is only included here for compatibility with .sil tests that have
  // not been migrated.
  public init?(_ value: Value) {
    guard let argument = value as? Argument else {
      return nil
    }
    var preds = argument.parentBlock.predecessors
    if let pred = preds.next() {
      let term = pred.terminator
      guard term is BranchInst else {
        return nil
      }
    } else {
      // No predecessors indicates an unreachable block (except for function arguments).
      // Treat this like a degenerate phi so we don't consider it a terminator result.
      if argument is FunctionArgument {
        return nil
      }
    }
    self.value = argument
  }

  public init?(using operand: Operand) {
    switch operand.instruction {
    case let br as BranchInst:
      self.init(br.getArgument(for: operand))
    default:
      return nil
    }
  }

  public var predecessors: PredecessorList {
    return value.parentBlock.predecessors
  }

  public var successor: BasicBlock {
    return value.parentBlock
  }

  public func incomingOperand(inPredecessor predecessor: BasicBlock)
  -> Operand {
    let blockArgIdx = value.index
    switch predecessor.terminator {
    case let br as BranchInst:
      return br.operands[blockArgIdx]
    default:
      fatalError("wrong terminator for phi-argument")
    }
  }

  public var incomingOperands: LazyMapSequence<PredecessorList, Operand> {
    predecessors.lazy.map { incomingOperand(inPredecessor: $0) }
  }

  public var incomingValues: LazyMapSequence<LazyMapSequence<PredecessorList, Operand>, Value> {
    incomingOperands.values
  }

  public var isReborrow: Bool { value.isReborrow }

  public var endsLifetime: Bool {
    value.ownership == .owned || value.isReborrow
  }

  public var borrowedFrom: BorrowedFromInst? {
    for use in value.uses {
      if let bfi = use.forwardingBorrowedFromUser {
        return bfi
      }
    }
    return nil
  }

  // Returns true if the phi has an end_borrow or a re-borrowing branch as user.
  // This should be consistent with the `isReborrow` flag.
  public var hasBorrowEndingUse: Bool {
    let phiValue: Value = borrowedFrom ?? value
    return phiValue.uses.contains {
      switch $0.ownership {
        case .endBorrow, .reborrow: return true
        default:                    return false
      }
    }
  }

  public static func ==(lhs: Phi, rhs: Phi) -> Bool {
    lhs.value === rhs.value
  }

  public func hash(into hasher: inout Hasher) {
    value.hash(into: &hasher)
  }
}

extension Phi {
  /// Return true of this phi is directly returned with no side effects between the phi and the return.
  public var isReturnValue: Bool {
    if let singleUse = value.uses.singleUse, let ret = singleUse.instruction as? ReturnInstruction,
       ret.parentBlock == successor {
      for inst in successor.instructions {
        if inst.mayHaveSideEffects {
          return false
        }
      }
      return true
    }
    return false
  }
}

extension Operand {
  public var forwardingBorrowedFromUser: BorrowedFromInst? {
    if let bfi = instruction as? BorrowedFromInst, index == 0 {
      return bfi
    }
    return nil
  }
}

public struct TerminatorResult {
  public let value: Argument

  public init?(_ value: Value) {
    guard let argument = value as? Argument else { return nil }
    var preds = argument.parentBlock.predecessors
    guard let pred = preds.next() else { return nil }
    let term = pred.terminator
    if term is BranchInst || term is CondBranchInst { return nil }
    self.value = argument
  }

  public var terminator: TermInst {
    var preds = value.parentBlock.predecessors
    return preds.next()!.terminator
  }
  
  public var predecessor: BasicBlock {
    return terminator.parentBlock
  }

  public var successor: BasicBlock {
    return value.parentBlock
  }

  public static func ==(lhs: TerminatorResult, rhs: TerminatorResult) -> Bool {
    lhs.value == rhs.value
  }

  public func hash(into hasher: inout Hasher) {
    value.hash(into: &hasher)
  }
}

/// ArgumentConventions indexed on a SIL function's argument index.
/// When derived from an ApplySite, this corresponds to the callee
/// function's argument index.
///
/// When derived from an ApplySite, `convention` is the substituted
/// convention. Substitution only affects the type inside ResultInfo
/// and ParameterInfo. It does not change the resulting
/// ArgumentConvention, ResultConvention, or LifetimeDependenceInfo.
public struct ArgumentConventions : Collection, CustomStringConvertible {
  public let convention: FunctionConvention

  public var startIndex: Int { 0 }

  public var endIndex: Int {
    firstParameterIndex + convention.parameters.count
  }

  public func index(after index: Int) -> Int {
    return index + 1
  }

  public subscript(_ argumentIndex: Int) -> ArgumentConvention {
    if let paramIdx = parameterIndex(ofArgumentIndex: argumentIndex) {
      return convention.parameters[paramIdx].convention
    }
    let resultInfo = convention.indirectSILResult(at: argumentIndex)
    return ArgumentConvention(result: resultInfo.convention)
  }

  public subscript(result argumentIndex: Int) -> ResultInfo? {
    if parameterIndex(ofArgumentIndex: argumentIndex) != nil {
      return nil
    }
    return convention.indirectSILResult(at: argumentIndex)
  }

  public subscript(parameter argumentIndex: Int) -> ParameterInfo? {
    guard let paramIdx = parameterIndex(ofArgumentIndex: argumentIndex) else {
      return nil
    }
    return convention.parameters[paramIdx]
  }

  public subscript(parameterDependencies targetArgumentIndex: Int) -> FunctionConvention.LifetimeDependencies? {
    guard let targetParamIdx = parameterIndex(ofArgumentIndex: targetArgumentIndex) else {
      return nil
    }
    return convention.parameterDependencies(for: targetParamIdx)
  }

  /// Return a parameter dependence of the target argument on the source argument.
  public func parameterDependence(targetArgumentIndex: Int, sourceArgumentIndex: Int) -> LifetimeDependenceConvention? {
    findDependence(source: sourceArgumentIndex, in: self[parameterDependencies: targetArgumentIndex])
  }

  /// Return a dependence of the function results on the indexed parameter.
  public subscript(resultDependsOn argumentIndex: Int) -> LifetimeDependenceConvention? {
    findDependence(source: argumentIndex, in: convention.resultDependencies)
  }

  /// Number of SIL arguments for the function type's results
  /// including the error result. Use this to avoid lazy iteration
  /// over indirectSILResults to find the count.
  var indirectSILResultCount: Int {
    convention.indirectSILResultCount
  }

  /// The SIL argument index of the function type's first parameter.
  public var firstParameterIndex: Int { indirectSILResultCount }

  /// The SIL argument index of the 'self' parameter.
  public var selfIndex: Int? {
    guard convention.hasSelfParameter else { return nil }
    // self is the last parameter
    return endIndex - 1
  }

  public var description: String {
    var str = convention.functionType.description
    for idx in startIndex..<indirectSILResultCount {
      str += "\n[\(idx)]  indirect result: " + self[idx].description
    }
    for idx in indirectSILResultCount..<endIndex {
      str += "\n[\(idx)]        parameter: " + self[idx].description
      if let deps = self[parameterDependencies: idx] {
        str += "\n          lifetime: \(deps)"
      }
      if let dep = self[resultDependsOn: idx] {
        str += "\n   result dependence: " + dep.description
      }
    }
    return str
  }
}

extension ArgumentConventions {
  private func parameterIndex(ofArgumentIndex argIdx: Int) -> Int? {
    let firstParamIdx = firstParameterIndex  // bridging call
    return argIdx < firstParamIdx ? nil : argIdx - firstParamIdx
  }

  private func findDependence(source argumentIndex: Int, in dependencies: FunctionConvention.LifetimeDependencies?)
    -> LifetimeDependenceConvention? {
    guard let paramIdx = parameterIndex(ofArgumentIndex: argumentIndex) else {
      return nil
    }
    return dependencies?[paramIdx]
  }
}

public struct YieldConventions : Collection, CustomStringConvertible {
  public let convention: FunctionConvention

  public var yields: FunctionConvention.Yields {
    return convention.yields
  }

  public var startIndex: Int { 0 }

  public var endIndex: Int { yields.count }

  public func index(after index: Int) -> Int {
    return index + 1
  }

  public subscript(_ index: Int) -> ArgumentConvention {
    return yields[index].convention
  }

  public var description: String {
    var str = convention.functionType.description
    yields.forEach {
      str += "\n      yield: " + $0.description
    }
    return str
  }
}

public enum ArgumentConvention : CustomStringConvertible {
  /// This argument is passed indirectly, i.e. by directly passing the address
  /// of an object in memory.  The callee is responsible for destroying the
  /// object.  The callee may assume that the address does not alias any valid
  /// object.
  case indirectIn

  /// This argument is passed indirectly, i.e. by directly passing the address
  /// of an object in memory.  The callee may not modify and does not destroy
  /// the object.
  case indirectInGuaranteed

  /// This argument is passed indirectly, i.e. by directly passing the address
  /// of an object in memory.  The object is always valid, but the callee may
  /// assume that the address does not alias any valid object and reorder loads
  /// stores to the parameter as long as the whole object remains valid. Invalid
  /// single-threaded aliasing may produce inconsistent results, but should
  /// remain memory safe.
  case indirectInout

  /// This argument is passed indirectly, i.e. by directly passing the address
  /// of an object in memory. The object is allowed to be aliased by other
  /// well-typed references, but is not allowed to be escaped. This is the
  /// convention used by mutable captures in @noescape closures.
  case indirectInoutAliasable

  /// This argument is passed indirectly, i.e. by directly passing the address
  /// of an object in memory. The callee may modify, but does not destroy the
  /// object. This corresponds to the parameter-passing convention of the
  /// Itanium C++ ABI, which is used ubiquitously on non-Windows targets.
  case indirectInCXX

  /// This argument represents an indirect return value address. The callee stores
  /// the returned value to this argument. At the time when the function is called,
  /// the memory location referenced by the argument is uninitialized.
  case indirectOut

  /// This argument is passed directly.  Its type is non-trivial, and the callee
  /// is responsible for destroying it.
  case directOwned

  /// This argument is passed directly.  Its type may be trivial, or it may
  /// simply be that the callee is not responsible for destroying it. Its
  /// validity is guaranteed only at the instant the call begins.
  case directUnowned

  /// This argument is passed directly.  Its type is non-trivial, and the caller
  /// guarantees its validity for the entirety of the call.
  case directGuaranteed

  /// This argument is a value pack of mutable references to storage,
  /// which the function is being given exclusive access to.  The elements
  /// must be passed indirectly.
  case packInout

  /// This argument is a value pack, and ownership of the elements is being
  /// transferred into this function.  Whether the elements are passed
  /// indirectly is recorded in the pack type.
  case packOwned

  /// This argument is a value pack, and ownership of the elements is not
  /// being transferred into this function.  Whether the elements are passed
  /// indirectly is recorded in the pack type.
  case packGuaranteed

  /// This argument is a pack of indirect return value addresses.  The
  /// addresses are stored in the pack by the caller and read out by the
  /// callee; within the callee, they are individually treated like
  /// indirectOut arguments.
  case packOut

  public init(result: ResultConvention) {
    switch result {
    case .indirect:
      self = .indirectOut
    case .owned:
      self = .directOwned
    case .unowned, .unownedInnerPointer, .autoreleased:
      self = .directUnowned
    case .pack:
      self = .packOut
    case .guaranteed, .guaranteedAddress, .inout:
      fatalError("Result conventions @guaranteed, @guaranteed_address and @inout are always returned directly")
    }
  }

  public var isIndirect: Bool {
    switch self {
    case .indirectIn, .indirectInGuaranteed,
         .indirectInout, .indirectInoutAliasable, .indirectInCXX,
         .indirectOut, .packOut, .packInout, .packOwned,
         .packGuaranteed:
      return true
    case .directOwned, .directUnowned, .directGuaranteed:
      return false
    }
  }

  public var isIndirectIn: Bool {
    switch self {
    case .indirectIn, .indirectInGuaranteed, .indirectInCXX,
         .packOwned, .packGuaranteed:
      return true
    case .directOwned, .directUnowned, .directGuaranteed,
         .indirectInout, .indirectInoutAliasable,
         .indirectOut,
         .packOut, .packInout:
      return false
    }
  }

  public var isIndirectOut: Bool {
    switch self {
    case .indirectOut, .packOut:
      return true
    case .indirectInGuaranteed, .directGuaranteed, .packGuaranteed,
         .indirectIn, .directOwned, .directUnowned,
         .indirectInout, .indirectInoutAliasable, .indirectInCXX,
         .packInout, .packOwned:
      return false
    }
  }

  public var isGuaranteed: Bool {
    switch self {
    case .indirectInGuaranteed, .directGuaranteed, .packGuaranteed:
      return true
    case .indirectIn, .directOwned, .directUnowned,
         .indirectInout, .indirectInoutAliasable, .indirectInCXX,
         .indirectOut, .packOut, .packInout, .packOwned:
      return false
    }
  }

  public var isConsumed: Bool {
    switch self {
    case .indirectIn, .indirectInCXX, .directOwned, .packOwned:
      return true
    case .indirectInGuaranteed, .directGuaranteed, .packGuaranteed,
          .indirectInout, .indirectInoutAliasable, .indirectOut,
          .packOut, .packInout, .directUnowned:
      return false
    }
  }

  public var isExclusiveIndirect: Bool {
    switch self {
    case .indirectIn,
         .indirectOut,
         .indirectInGuaranteed,
         .indirectInout,
         .indirectInCXX,
         .packOut,
         .packInout,
         .packOwned,
         .packGuaranteed:
      return true

    case .indirectInoutAliasable,
         .directUnowned,
         .directGuaranteed,
         .directOwned:
      return false
    }
  }

  public var isInout: Bool {
    switch self {
    case .indirectInout,
         .indirectInoutAliasable,
         .packInout:
      return true

    case .indirectIn,
         .indirectOut,
         .indirectInGuaranteed,
         .indirectInCXX,
         .directUnowned,
         .directGuaranteed,
         .directOwned,
         .packOut,
         .packOwned,
         .packGuaranteed:
      return false
    }
  }

  public var description: String {
    switch self {
    case .indirectIn:
      return "indirectIn"
    case .indirectInGuaranteed:
      return "indirectInGuaranteed"
    case .indirectInout:
      return "indirectInout"
    case .indirectInoutAliasable:
      return "indirectInoutAliasable"
    case .indirectInCXX:
      return "indirectInCXX"
    case .indirectOut:
      return "indirectOut"
    case .directOwned:
      return "directOwned"
    case .directUnowned:
      return "directUnowned"
    case .directGuaranteed:
      return "directGuaranteed"
    case .packInout:
      return "packInout"
    case .packOwned:
      return "packOwned"
    case .packGuaranteed:
      return "packGuaranteed"
    case .packOut:
      return "packOut"
    }
  }
}

// Bridging utilities

extension BridgedArgument {
  public var argument: Argument { obj.getAs(Argument.self) }
  public var functionArgument: FunctionArgument { obj.getAs(FunctionArgument.self) }
}

extension Optional where Wrapped == Argument {
  public var bridged: OptionalBridgedArgument {
    OptionalBridgedArgument(obj: self?.bridged.obj)
  }
}

extension BridgedArgumentConvention {
  var convention: ArgumentConvention {
    switch self {
      case .Indirect_In:             return .indirectIn
      case .Indirect_In_Guaranteed:  return .indirectInGuaranteed
      case .Indirect_Inout:          return .indirectInout
      case .Indirect_InoutAliasable: return .indirectInoutAliasable
      case .Indirect_In_CXX:         return .indirectInCXX
      case .Indirect_Out:            return .indirectOut
      case .Direct_Owned:            return .directOwned
      case .Direct_Unowned:          return .directUnowned
      case .Direct_Guaranteed:       return .directGuaranteed
      case .Pack_Out:                return .packOut
      case .Pack_Inout:              return .packInout
      case .Pack_Owned:              return .packOwned
      case .Pack_Guaranteed:         return .packGuaranteed
      default:
        fatalError("unsupported argument convention")
    }
  }
}

extension ArgumentConvention {
  var bridged: BridgedArgumentConvention {
    switch self {
      case .indirectIn:             return .Indirect_In
      case .indirectInGuaranteed:   return .Indirect_In_Guaranteed
      case .indirectInout:          return .Indirect_Inout
      case .indirectInoutAliasable: return .Indirect_InoutAliasable
      case .indirectInCXX:          return .Indirect_In_CXX
      case .indirectOut:            return .Indirect_Out
      case .directOwned:            return .Direct_Owned
      case .directUnowned:          return .Direct_Unowned
      case .directGuaranteed:       return .Direct_Guaranteed
      case .packOut:                return .Pack_Out
      case .packInout:              return .Pack_Inout
      case .packOwned:              return .Pack_Owned
      case .packGuaranteed:         return .Pack_Guaranteed
    }
  }
}
