//===--- Context.swift - defines the context types ------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 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 AST
import SIL
import OptimizerBridging

/// The base type of all contexts.
protocol Context {
  var _bridged: BridgedPassContext { get }
}

extension Context {
  var options: Options { Options(_bridged: _bridged) }

  var diagnosticEngine: DiagnosticEngine {
    return DiagnosticEngine(bridged: _bridged.getDiagnosticEngine())
  }

  // The calleeAnalysis is not specific to a function and therefore can be provided in
  // all contexts.
  var calleeAnalysis: CalleeAnalysis {
    let bridgeCA = _bridged.getCalleeAnalysis()
    return CalleeAnalysis(bridged: bridgeCA)
  }

  var hadError: Bool { _bridged.hadError() }

  var silStage: SILStage {
    switch _bridged.getSILStage() {
      case .Raw:       return .raw
      case .Canonical: return .canonical
      case .Lowered:   return .lowered
      default:         fatalError("unhandled SILStage case")
    }
  }

  var moduleIsSerialized: Bool { _bridged.moduleIsSerialized() }

  /// Enable diagnostics requiring WMO (for @noLocks, @noAllocation
  /// annotations, Embedded Swift, and class specialization). SourceKit is the
  /// only consumer that has this disabled today (as it disables WMO
  /// explicitly).
  var enableWMORequiredDiagnostics: Bool {
    _bridged.enableWMORequiredDiagnostics()
  }

  func canMakeStaticObjectReadOnly(objectType: Type) -> Bool {
    _bridged.canMakeStaticObjectReadOnly(objectType.bridged)
  }

  func lookupDeinit(ofNominal: NominalTypeDecl) -> Function? {
    _bridged.lookUpNominalDeinitFunction(ofNominal.bridged).function
  }

  func getBuiltinIntegerType(bitWidth: Int) -> Type { _bridged.getBuiltinIntegerType(bitWidth).type }

  func lookupFunction(name: String) -> Function? {
    name._withBridgedStringRef {
      _bridged.lookupFunction($0).function
    }
  }

  func lookupWitnessTable(for conformance: Conformance) -> WitnessTable? {
    return _bridged.lookupWitnessTable(conformance.bridged).witnessTable
  }

  func lookupVTable(for classDecl: NominalTypeDecl) -> VTable? {
    return _bridged.lookupVTable(classDecl.bridged).vTable
  }

  func lookupSpecializedVTable(for classType: Type) -> VTable? {
    return _bridged.lookupSpecializedVTable(classType.bridged).vTable
  }

  func getSpecializedConformance(of genericConformance: Conformance,
                                 for type: AST.`Type`,
                                 substitutions: SubstitutionMap) -> Conformance
  {
    let c = _bridged.getSpecializedConformance(genericConformance.bridged, type.bridged, substitutions.bridged)
    return Conformance(bridged: c)
  }

  func notifyNewFunction(function: Function, derivedFrom: Function) {
    _bridged.addFunctionToPassManagerWorklist(function.bridged, derivedFrom.bridged)
  }
}

/// A context which allows mutation of a function's SIL.
protocol MutatingContext : Context {
  // Called by all instruction mutations, including inserted new instructions.
  var notifyInstructionChanged: (Instruction) -> () { get }
}

extension MutatingContext {
  func notifyInvalidatedStackNesting() { _bridged.notifyInvalidatedStackNesting() }
  var needFixStackNesting: Bool { _bridged.getNeedFixStackNesting() }

  func verifyIsTransforming(function: Function) {
    precondition(_bridged.isTransforming(function.bridged), "pass modifies wrong function")
  }

  /// Splits the basic block, which contains `inst`, before `inst` and returns the
  /// new block.
  ///
  /// `inst` and all subsequent instructions are moved to the new block, while all
  /// instructions _before_ `inst` remain in the original block.
  func splitBlock(before inst: Instruction) -> BasicBlock {
    notifyBranchesChanged()
    return _bridged.splitBlockBefore(inst.bridged).block
  }

  /// Splits the basic block, which contains `inst`, after `inst` and returns the
  /// new block.
  ///
  /// All subsequent instructions after `inst` are moved to the new block, while `inst` and all
  /// instructions _before_ `inst` remain in the original block.
  func splitBlock(after inst: Instruction) -> BasicBlock {
    notifyBranchesChanged()
    return _bridged.splitBlockAfter(inst.bridged).block
  }

  func createBlock(after block: BasicBlock) -> BasicBlock {
    notifyBranchesChanged()
    return _bridged.createBlockAfter(block.bridged).block
  }

  func erase(instruction: Instruction) {
    if !instruction.isInStaticInitializer {
      verifyIsTransforming(function: instruction.parentFunction)
    }
    if instruction is FullApplySite {
      notifyCallsChanged()
    }
    if instruction is TermInst {
      notifyBranchesChanged()
    }
    notifyInstructionsChanged()

    _bridged.eraseInstruction(instruction.bridged)
  }

  func erase(instructionIncludingAllUsers inst: Instruction) {
    if inst.isDeleted {
      return
    }
    for result in inst.results {
      for use in result.uses {
        erase(instructionIncludingAllUsers: use.instruction)
      }
    }
    erase(instruction: inst)
  }

  func erase<S: Sequence>(instructions: S) where S.Element: Instruction {
    for inst in instructions {
      erase(instruction: inst)
    }
  }

  func erase(instructionIncludingDebugUses inst: Instruction) {
    precondition(inst.results.allSatisfy { $0.uses.ignoreDebugUses.isEmpty })
    erase(instructionIncludingAllUsers: inst)
  }

  func erase(block: BasicBlock) {
    _bridged.eraseBlock(block.bridged)
  }

  func tryOptimizeApplyOfPartialApply(closure: PartialApplyInst) -> Bool {
    if _bridged.tryOptimizeApplyOfPartialApply(closure.bridged) {
      notifyInstructionsChanged()
      notifyCallsChanged()

      for use in closure.callee.uses {
        if use.instruction is FullApplySite {
          notifyInstructionChanged(use.instruction)
        }
      }
      return true
    }
    return false
  }

  func tryDeleteDeadClosure(closure: SingleValueInstruction, needKeepArgsAlive: Bool = true) -> Bool {
    if _bridged.tryDeleteDeadClosure(closure.bridged, needKeepArgsAlive) {
      notifyInstructionsChanged()
      return true
    }
    return false
  }

  func tryDevirtualize(apply: FullApplySite, isMandatory: Bool) -> ApplySite? {
    let result = _bridged.tryDevirtualizeApply(apply.bridged, isMandatory)
    if let newApply = result.newApply.instruction {
      erase(instruction: apply)
      notifyInstructionsChanged()
      notifyCallsChanged()
      if result.cfgChanged {
        notifyBranchesChanged()
      }
      notifyInstructionChanged(newApply)
      return newApply as! FullApplySite
    }
    return nil
  }
  
  func tryOptimizeKeypath(apply: FullApplySite) -> Bool {
    return _bridged.tryOptimizeKeypath(apply.bridged)
  }

  func inlineFunction(apply: FullApplySite, mandatoryInline: Bool) {
    // This is only a best-effort attempt to notify the new cloned instructions as changed.
    // TODO: get a list of cloned instructions from the `inlineFunction`
    let instAfterInling: Instruction?
    switch apply {
    case is ApplyInst:
      instAfterInling = apply.next
    case let beginApply as BeginApplyInst:
      let next = beginApply.next!
      instAfterInling = (next is EndApplyInst ? nil : next)
    case is TryApplyInst:
      instAfterInling = apply.parentBlock.next?.instructions.first
    default:
      instAfterInling = nil
    }

    _bridged.inlineFunction(apply.bridged, mandatoryInline)

    if let instAfterInling = instAfterInling {
      notifyNewInstructions(from: apply, to: instAfterInling)
    }
  }

  func loadFunction(function: Function, loadCalleesRecursively: Bool) -> Bool {
    if function.isDefinition {
      return true
    }
    _bridged.loadFunction(function.bridged, loadCalleesRecursively)
    return function.isDefinition
  }

  private func notifyNewInstructions(from: Instruction, to: Instruction) {
    var inst = from
    while inst != to {
      if !inst.isDeleted {
        notifyInstructionChanged(inst)
      }
      if let next = inst.next {
        inst = next
      } else {
        inst = inst.parentBlock.next!.instructions.first!
      }
    }
  }

  func getContextSubstitutionMap(for type: Type) -> SubstitutionMap {
    SubstitutionMap(bridged: _bridged.getContextSubstitutionMap(type.bridged))
  }

  func notifyInstructionsChanged() {
    _bridged.asNotificationHandler().notifyChanges(.instructionsChanged)
  }

  func notifyCallsChanged() {
    _bridged.asNotificationHandler().notifyChanges(.callsChanged)
  }

  func notifyBranchesChanged() {
    _bridged.asNotificationHandler().notifyChanges(.branchesChanged)
  }

  /// Notifies the pass manager that the optimization result of the current pass depends
  /// on the body (i.e. SIL instructions) of another function than the currently optimized one.
  func notifyDependency(onBodyOf otherFunction: Function) {
    _bridged.notifyDependencyOnBodyOf(otherFunction.bridged)
  }
}

/// The context which is passed to the run-function of a FunctionPass.
struct FunctionPassContext : MutatingContext {
  let _bridged: BridgedPassContext

  // A no-op.
  var notifyInstructionChanged: (Instruction) -> () { return { inst in } }

  func continueWithNextSubpassRun(for inst: Instruction? = nil) -> Bool {
    return _bridged.continueWithNextSubpassRun(inst.bridged)
  }

  func createSimplifyContext(preserveDebugInfo: Bool, notifyInstructionChanged: @escaping (Instruction) -> ()) -> SimplifyContext {
    SimplifyContext(_bridged: _bridged, notifyInstructionChanged: notifyInstructionChanged, preserveDebugInfo: preserveDebugInfo)
  }

  var deadEndBlocks: DeadEndBlocksAnalysis {
    let bridgeDEA = _bridged.getDeadEndBlocksAnalysis()
    return DeadEndBlocksAnalysis(bridged: bridgeDEA)
  }

  var dominatorTree: DominatorTree {
    let bridgedDT = _bridged.getDomTree()
    return DominatorTree(bridged: bridgedDT)
  }

  var postDominatorTree: PostDominatorTree {
    let bridgedPDT = _bridged.getPostDomTree()
    return PostDominatorTree(bridged: bridgedPDT)
  }

  var swiftArrayDecl: NominalTypeDecl {
    _bridged.getSwiftArrayDecl().getAs(NominalTypeDecl.self)
  }

  func loadFunction(name: StaticString, loadCalleesRecursively: Bool) -> Function? {
    return name.withUTF8Buffer { (nameBuffer: UnsafeBufferPointer<UInt8>) in
      let nameStr = BridgedStringRef(data: nameBuffer.baseAddress, count: nameBuffer.count)
      return _bridged.loadFunction(nameStr, loadCalleesRecursively).function
    }
  }

  /// Looks up a function in the `Swift` module.
  /// The `name` is the source name of the function and not the mangled name.
  /// Returns nil if no such function or multiple matching functions are found.
  func lookupStdlibFunction(name: StaticString) -> Function? {
    return name.withUTF8Buffer { (nameBuffer: UnsafeBufferPointer<UInt8>) in
      let nameStr = BridgedStringRef(data: nameBuffer.baseAddress, count: nameBuffer.count)
      return _bridged.lookupStdlibFunction(nameStr).function
    }
  }

  func modifyEffects(in function: Function, _ body: (inout FunctionEffects) -> ()) {
    notifyEffectsChanged()
    function._modifyEffects(body)
  }

  fileprivate func notifyEffectsChanged() {
    _bridged.asNotificationHandler().notifyChanges(.effectsChanged)
  }

  func optimizeMemoryAccesses(in function: Function) -> Bool {
    if _bridged.optimizeMemoryAccesses(function.bridged) {
      notifyInstructionsChanged()
      return true
    }
    return false
  }

  func eliminateDeadAllocations(in function: Function) -> Bool {
    if _bridged.eliminateDeadAllocations(function.bridged) {
      notifyInstructionsChanged()
      return true
    }
    return false
  }

  func specializeClassMethodInst(_ cm: ClassMethodInst) -> Bool {
    if _bridged.specializeClassMethodInst(cm.bridged) {
      notifyInstructionsChanged()
      notifyCallsChanged()
      return true
    }
    return false
  }

  func specializeWitnessMethodInst(_ wm: WitnessMethodInst) -> Bool {
    if _bridged.specializeWitnessMethodInst(wm.bridged) {
      notifyInstructionsChanged()
      notifyCallsChanged()
      return true
    }
    return false
  }

  func specializeApplies(in function: Function, isMandatory: Bool) -> Bool {
    if _bridged.specializeAppliesInFunction(function.bridged, isMandatory) {
      notifyInstructionsChanged()
      notifyCallsChanged()
      return true
    }
    return false
  }

  func mangleOutlinedVariable(from function: Function) -> String {
    return String(taking: _bridged.mangleOutlinedVariable(function.bridged))
  }

  func mangle(withClosureArguments closureArgs: [Value], closureArgIndices: [Int], from applySiteCallee: Function) -> String {
    closureArgs.withBridgedValues { bridgedClosureArgsRef in
      closureArgIndices.withBridgedArrayRef{bridgedClosureArgIndicesRef in 
        String(taking: _bridged.mangleWithClosureArgs(
          bridgedClosureArgsRef, 
          bridgedClosureArgIndicesRef, 
          applySiteCallee.bridged
        ))
      }
    }
  }

  func createGlobalVariable(name: String, type: Type, linkage: Linkage, isLet: Bool) -> GlobalVariable {
    let gv = name._withBridgedStringRef {
      _bridged.createGlobalVariable($0, type.bridged, linkage.bridged, isLet)
    }
    return gv.globalVar
  }

  func createFunctionForClosureSpecialization(from applySiteCallee: Function, withName specializedFunctionName: String, 
                                              withParams specializedParameters: [ParameterInfo], 
                                              withSerialization isSerialized: Bool) -> Function 
  {
    return specializedFunctionName._withBridgedStringRef { nameRef in
      let bridgedParamInfos = specializedParameters.map { $0._bridged }

      return bridgedParamInfos.withUnsafeBufferPointer { paramBuf in
        _bridged.ClosureSpecializer_createEmptyFunctionWithSpecializedSignature(nameRef, paramBuf.baseAddress, 
                                                                                paramBuf.count, 
                                                                                applySiteCallee.bridged, 
                                                                                isSerialized).function
      }
    }
  }

  func buildSpecializedFunction<T>(specializedFunction: Function, buildFn: (Function, FunctionPassContext) -> T) -> T {
    let nestedFunctionPassContext = 
        FunctionPassContext(_bridged: _bridged.initializeNestedPassContext(specializedFunction.bridged))

      defer { _bridged.deinitializedNestedPassContext() }

      return buildFn(specializedFunction, nestedFunctionPassContext)
  }

  /// Makes sure that the lifetime of `value` ends at all control flow paths, even in dead-end blocks.
  /// Inserts destroys in dead-end blocks if those are missing.
  func completeLifetime(of value: Value) {
    if _bridged.completeLifetime(value.bridged) {
      notifyInstructionsChanged()
    }
  }
}

struct SimplifyContext : MutatingContext {
  let _bridged: BridgedPassContext
  let notifyInstructionChanged: (Instruction) -> ()
  let preserveDebugInfo: Bool
}

extension Type {
  func getStaticSize(context: SimplifyContext) -> Int? {
    let v = context._bridged.getStaticSize(self.bridged)
    return v == -1 ? nil : v
  }
  
  func getStaticAlignment(context: SimplifyContext) -> Int? {
    let v = context._bridged.getStaticAlignment(self.bridged)
    return v == -1 ? nil : v
  }
  
  func getStaticStride(context: SimplifyContext) -> Int? {
    let v = context._bridged.getStaticStride(self.bridged)
    return v == -1 ? nil : v
  }
}

//===----------------------------------------------------------------------===//
//                          Builder initialization
//===----------------------------------------------------------------------===//

private extension Instruction {
  /// Returns self, unless self is a meta instruction, in which case the next
  /// non-meta instruction is returned. Returns nil if there are no non-meta
  /// instructions in the basic block.
  var nextNonMetaInstruction: Instruction? {
    for inst in InstructionList(first: self) where !(inst is MetaInstruction) {
      return inst
    }
    return nil
  }

  /// Returns the next interesting location. As it is impossible to set a
  /// breakpoint on a meta instruction, those are skipped.
  /// However, we don't want to take a location with different inlining
  /// information than this instruction, so in that case, we will return the
  /// location of the meta instruction. If the meta instruction is the only
  /// instruction in the basic block, we also take its location.
  var locationOfNextNonMetaInstruction: Location {
    let location = self.location
    guard !location.isInlined,
          let nextLocation = nextNonMetaInstruction?.location,
          !nextLocation.isInlined else {
      return location
    }
    return nextLocation
  }
}

extension Builder {
  /// Creates a builder which inserts _before_ `insPnt`, using a custom `location`.
  init(before insPnt: Instruction, location: Location, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: insPnt.parentFunction)
    self.init(insertAt: .before(insPnt), location: location,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  /// Creates a builder which inserts before `insPnt`, using `insPnt`'s next
  /// non-meta instruction's location.
  /// This function should be used when moving code to an unknown insert point,
  /// when we want to inherit the location of the closest non-meta instruction.
  /// For replacing an existing meta instruction with another, use
  /// ``Builder.init(replacing:_:)``.
  init(before insPnt: Instruction, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: insPnt.parentFunction)
    self.init(insertAt: .before(insPnt),
              location: insPnt.locationOfNextNonMetaInstruction,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  /// Creates a builder which inserts _before_ `insPnt`, using the exact location of `insPnt`,
  /// for the purpose of replacing that meta instruction with an equivalent instruction.
  /// This function does not delete `insPnt`.
  init(replacing insPnt: MetaInstruction, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: insPnt.parentFunction)
    self.init(insertAt: .before(insPnt), location: insPnt.location,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  /// Creates a builder which inserts _after_ `insPnt`, using a custom `location`.
  ///
  /// TODO: this is usually incorrect for terminator instructions. Instead use
  /// `Builder.insert(after:location:_:insertFunc)` from OptUtils.swift. Rename this to afterNonTerminator.
  init(after insPnt: Instruction, location: Location, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: insPnt.parentFunction)
    guard let nextInst = insPnt.next else {
      fatalError("cannot insert an instruction after a block terminator.")
    }
    self.init(insertAt: .before(nextInst), location: location,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  /// Creates a builder which inserts _after_ `insPnt`, using `insPnt`'s next
  /// non-meta instruction's location.
  ///
  /// TODO: this is incorrect for terminator instructions. Instead use `Builder.insert(after:location:_:insertFunc)`
  /// from OptUtils.swift. Rename this to afterNonTerminator.
  init(after insPnt: Instruction, _ context: some MutatingContext) {
    self.init(after: insPnt, location: insPnt.locationOfNextNonMetaInstruction, context)
  }

  /// Creates a builder which inserts at the end of `block`, using a custom `location`.
  init(atEndOf block: BasicBlock, location: Location, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: block.parentFunction)
    self.init(insertAt: .atEndOf(block), location: location,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  /// Creates a builder which inserts at the begin of `block`, using a custom `location`.
  init(atBeginOf block: BasicBlock, location: Location, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: block.parentFunction)
    let firstInst = block.instructions.first!
    self.init(insertAt: .before(firstInst), location: location,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  /// Creates a builder which inserts at the begin of `block`, using the location of the first
  /// non-meta instruction of `block`.
  init(atBeginOf block: BasicBlock, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: block.parentFunction)
    let firstInst = block.instructions.first!
    self.init(insertAt: .before(firstInst),
              location: firstInst.locationOfNextNonMetaInstruction,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  /// Creates a builder which inserts instructions into an empty function, using the location of the function itself.
  init(atStartOf function: Function, _ context: some MutatingContext) {
    context.verifyIsTransforming(function: function)
    self.init(insertAt: .atStartOf(function), location: function.location,
              context.notifyInstructionChanged, context._bridged.asNotificationHandler())
  }

  init(staticInitializerOf global: GlobalVariable, _ context: some MutatingContext) {
    self.init(insertAt: .staticInitializer(global),
              location: Location.artificialUnreachableLocation,
              { _ in }, context._bridged.asNotificationHandler())
  }
}

//===----------------------------------------------------------------------===//
//                          Modifying the SIL
//===----------------------------------------------------------------------===//

extension Undef {
  static func get(type: Type, _ context: some MutatingContext) -> Undef {
    context._bridged.getSILUndef(type.bridged).value as! Undef
  }
}

extension BasicBlock {
  func addArgument(type: Type, ownership: Ownership, _ context: some MutatingContext) -> Argument {
    context.notifyInstructionsChanged()
    return bridged.addBlockArgument(type.bridged, ownership._bridged).argument
  }
  
  func addFunctionArgument(type: Type, _ context: some MutatingContext) -> FunctionArgument {
    context.notifyInstructionsChanged()
    return bridged.addFunctionArgument(type.bridged).argument as! FunctionArgument
  }

  func eraseArgument(at index: Int, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.eraseArgument(index)
  }

  func moveAllInstructions(toBeginOf otherBlock: BasicBlock, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    context.notifyBranchesChanged()
    bridged.moveAllInstructionsToBegin(otherBlock.bridged)
  }

  func moveAllInstructions(toEndOf otherBlock: BasicBlock, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    context.notifyBranchesChanged()
    bridged.moveAllInstructionsToEnd(otherBlock.bridged)
  }

  func eraseAllArguments(_ context: some MutatingContext) {
    // Arguments are stored in an array. We need to erase in reverse order to avoid quadratic complexity.
    for argIdx in (0 ..< arguments.count).reversed() {
      eraseArgument(at: argIdx, context)
    }
  }

  func moveAllArguments(to otherBlock: BasicBlock, _ context: some MutatingContext) {
    bridged.moveArgumentsTo(otherBlock.bridged)
  }
}

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

extension AllocRefInstBase {
  func setIsStackAllocatable(_ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.AllocRefInstBase_setIsStackAllocatable()
    context.notifyInstructionChanged(self)
  }
}

extension Sequence where Element == Operand {
  func replaceAll(with replacement: Value, _ context: some MutatingContext) {
    for use in self {
      use.set(to: replacement, context)
    }
  }
}

extension Operand {
  func set(to value: Value, _ context: some MutatingContext) {
    instruction.setOperand(at: index, to: value, context)
  }

  func changeOwnership(from: Ownership, to: Ownership, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.changeOwnership(from._bridged, to._bridged)
    context.notifyInstructionChanged(instruction)
  }
}

extension Instruction {
  func setOperand(at index : Int, to value: Value, _ context: some MutatingContext) {
    if let apply = self as? FullApplySite, apply.isCallee(operand: operands[index]) {
      context.notifyCallsChanged()
    }
    context.notifyInstructionsChanged()
    bridged.setOperand(index, value.bridged)
    context.notifyInstructionChanged(self)
  }

  func move(before otherInstruction: Instruction, _ context: some MutatingContext) {
    BridgedPassContext.moveInstructionBefore(bridged, otherInstruction.bridged)
    context.notifyInstructionsChanged()
  }
}

extension BuiltinInst {
  func constantFold(_ context: some MutatingContext) -> Value? {
    context._bridged.constantFoldBuiltin(bridged).value
  }
}

extension RefCountingInst {
  func setAtomicity(isAtomic: Bool, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.RefCountingInst_setIsAtomic(isAtomic)
    context.notifyInstructionChanged(self)
  }
}

extension AllocRefInst {
  func setIsBare(_ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.AllocRefInst_setIsBare()
    context.notifyInstructionChanged(self)
  }
}

extension RefElementAddrInst {
  func set(isImmutable: Bool, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.RefElementAddrInst_setImmutable(isImmutable)
    context.notifyInstructionChanged(self)
  }
}

extension GlobalAddrInst {
  func clearToken(_ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.GlobalAddrInst_clearToken()
    context.notifyInstructionChanged(self)
  }
}

extension GlobalValueInst {
  func setIsBare(_ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.GlobalValueInst_setIsBare()
    context.notifyInstructionChanged(self)
  }
}

extension LoadInst {
  func set(ownership: LoadInst.LoadOwnership, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.LoadInst_setOwnership(ownership.rawValue)
    context.notifyInstructionChanged(self)
  }
}

extension PointerToAddressInst {
  func set(alignment: Int?, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.PointerToAddressInst_setAlignment(UInt64(alignment ?? 0))
    context.notifyInstructionChanged(self)
  }
}

extension TermInst {
  func replaceBranchTarget(from fromBlock: BasicBlock, to toBlock: BasicBlock, _ context: some MutatingContext) {
    context.notifyBranchesChanged()
    bridged.TermInst_replaceBranchTarget(fromBlock.bridged, toBlock.bridged)
  }
}

extension ForwardingInstruction {
  func setForwardingOwnership(to ownership: Ownership, _ context: some MutatingContext) {
    context.notifyInstructionsChanged()
    bridged.ForwardingInst_setForwardingOwnership(ownership._bridged)
  }
}

extension Function {
  func set(needStackProtection: Bool, _ context: FunctionPassContext) {
    context.notifyEffectsChanged()
    bridged.setNeedStackProtection(needStackProtection)
  }

  func set(thunkKind: ThunkKind, _ context: FunctionPassContext) {
    context.notifyEffectsChanged()
    switch thunkKind {
    case .noThunk:                 bridged.setThunk(.IsNotThunk)
    case .thunk:                   bridged.setThunk(.IsThunk)
    case .reabstractionThunk:      bridged.setThunk(.IsReabstractionThunk)
    case .signatureOptimizedThunk: bridged.setThunk(.IsSignatureOptimizedThunk)
    }
  }

  func set(isPerformanceConstraint: Bool, _ context: FunctionPassContext) {
    context.notifyEffectsChanged()
    bridged.setIsPerformanceConstraint(isPerformanceConstraint)
  }

  func fixStackNesting(_ context: FunctionPassContext) {
    context._bridged.fixStackNesting(bridged)
  }

  func appendNewBlock(_ context: FunctionPassContext) -> BasicBlock {
    context.notifyBranchesChanged()
    return context._bridged.appendBlock(bridged).block
  }
}
