// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef CARBON_TOOLCHAIN_CHECK_CONTROL_FLOW_H_
#define CARBON_TOOLCHAIN_CHECK_CONTROL_FLOW_H_

#include "toolchain/check/context.h"
#include "toolchain/check/inst.h"
#include "toolchain/parse/typed_nodes.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"

namespace Carbon::Check {

// Adds a `Branch` instruction branching to a new instruction block, and
// returns the ID of the new block. All paths to the branch target must go
// through the current block, though not necessarily through this branch.
auto AddDominatedBlockAndBranch(Context& context, Parse::NodeId node_id)
    -> SemIR::InstBlockId;

// Adds a `Branch` instruction branching to a new instruction block with a
// value, and returns the ID of the new block. All paths to the branch target
// must go through the current block.
auto AddDominatedBlockAndBranchWithArg(Context& context, Parse::NodeId node_id,
                                       SemIR::InstId arg_id)
    -> SemIR::InstBlockId;

// Adds a `BranchIf` instruction branching to a new instruction block, and
// returns the ID of the new block. All paths to the branch target must go
// through the current block.
auto AddDominatedBlockAndBranchIf(Context& context, Parse::NodeId node_id,
                                  SemIR::InstId cond_id) -> SemIR::InstBlockId;

// Handles recovergence of control flow. Adds branches from the top
// `num_blocks` on the instruction block stack to a new block, pops the
// existing blocks, pushes the new block onto the instruction block stack,
// and adds it to the most recently pushed region.
auto AddConvergenceBlockAndPush(Context& context, Parse::NodeId node_id,
                                int num_blocks) -> void;

// Handles recovergence of control flow with a result value. Adds branches
// from the top few blocks on the instruction block stack to a new block, pops
// the existing blocks,  pushes the new block onto the instruction block
// stack, and adds it to the most recently pushed region. The number of blocks
// popped is the size of `block_args`, and the corresponding result values are
// the elements of `block_args`. Returns an instruction referring to the
// result value.
auto AddConvergenceBlockWithArgAndPush(
    Context& context, Parse::NodeId node_id,
    std::initializer_list<SemIR::InstId> block_args) -> SemIR::InstId;

// Sets the constant value of a block argument created as the result of a
// branch.  `select_id` should be a `BlockArg` that selects between two
// values. `cond_id` is the condition, `if_false` is the value to use if the
// condition is false, and `if_true` is the value to use if the condition is
// true.  We don't track enough information in the `BlockArg` inst for
// `TryEvalInst` to do this itself.
auto SetBlockArgResultBeforeConstantUse(Context& context,
                                        SemIR::InstId select_id,
                                        SemIR::InstId cond_id,
                                        SemIR::InstId if_true,
                                        SemIR::InstId if_false) -> void;

// Returns whether the current position in the current block is reachable.
auto IsCurrentPositionReachable(Context& context) -> bool;

// Determines whether the instruction requires cleanup and, if so, adds it for
// cleanup blocks. Note for example that a class may not need destruction when
// neither it nor its members have `destroy` functions.
auto MaybeAddCleanupForInst(Context& context, SemIR::InstId inst_id) -> void;

// Adds an instruction that has cleanup associated.
template <typename InstT, typename LocT>
  requires(InstT::Kind.has_cleanup() && std::convertible_to<LocT, SemIR::LocId>)
auto AddInstWithCleanup(Context& context, LocT loc, InstT inst)
    -> SemIR::InstId {
  auto inst_id = AddInst(context, SemIR::LocIdAndInst(loc, inst));
  MaybeAddCleanupForInst(context, inst_id);
  return inst_id;
}

// Adds an instruction that has cleanup associated.
template <typename InstT, typename LocT>
  requires(InstT::Kind.has_cleanup() && std::convertible_to<LocT, SemIR::LocId>)
auto AddInstWithCleanupInNoBlock(Context& context, LocT loc, InstT inst)
    -> SemIR::InstId {
  auto inst_id = AddInstInNoBlock(context, SemIR::LocIdAndInst(loc, inst));
  MaybeAddCleanupForInst(context, inst_id);
  return inst_id;
}

// Adds a return cleanup block, including the returning instruction.
//
// Cleanup blocks are an effort to share cleanup instructions across equivalent
// scope-ending instructions (for example, all `return;` instructions are
// equivalent). Structurally, they should first run non-shared cleanup, then
// either dispatch to a cleanup block that includes shared cleanup, or invoke
// the control flow instruction.
//
// For example:
//
//   fn F() {
//     var a: C;
//     if (...) {
//       // Cleanup block 1: destroy a, return
//       return;
//     }
//
//     var b: C;
//     if (...) {
//       // Cleanup block 2: destroy b, reuse cleanup block 1.
//       return;
//     }
//
//     DoSomethingMore();
//     // Cleanup block 3: reuse cleanup block 2.
//   }
//
// TODO: Add support for `break;` and `continue;`.
// TODO: Add reuse (described above but not done).
auto AddReturnCleanupBlock(Context& context,
                           SemIR::LocIdAndInst loc_id_and_inst) -> void;

template <typename LocT>
auto AddReturnCleanupBlock(Context& context, LocT loc) -> void {
  AddReturnCleanupBlock(context, SemIR::LocIdAndInst(loc, SemIR::Return{}));
}
template <typename LocT>
auto AddReturnCleanupBlockWithExpr(Context& context, LocT loc,
                                   SemIR::ReturnExpr inst) -> void {
  AddReturnCleanupBlock(context, SemIR::LocIdAndInst(loc, inst));
}

}  // namespace Carbon::Check

#endif  // CARBON_TOOLCHAIN_CHECK_CONTROL_FLOW_H_
