mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:00:13 +01:00
Instead of tracking the cleanup scope depth on entry to each scope, track an "ambient" cleanup scope depth that's *after* the destructors of local variables in that scope. This gets increased to include the destructors of local variables when we create a name-binding declaration. Then, when we reach a point where temporaries should be destroyed, run cleanups that are after the ambient cleanup scope depth on the stack. This happens: * At the `;` of a statement expression. * At the `)` of an `if` or `while` statement. * After performing the implied `HasValue()` call in a `for` statement. Per informal agreement with leads, this means we lifetime-extend all temporaries created in the initializer of a name-binding declaration to the full scope of that declaration, but that temporaries created in an expression statement are destroyed at the `;`.
130 lines
6.0 KiB
C++
130 lines
6.0 KiB
C++
// 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/check/scope_stack.h"
|
|
#include "toolchain/parse/node_ids.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 any cleanups for temporaries within the current statement and discards
|
|
// them from cleanup tracking.
|
|
auto AddAndDiscardTemporaryCleanups(Context& context) -> void;
|
|
|
|
// Adds any cleanups for variables and temporaries within the current scope and
|
|
// discards them from cleanup tracking.
|
|
auto AddAndDiscardScopeCleanups(Context& context) -> void;
|
|
|
|
// Adds a branch to the given target block, which should be in the current scope
|
|
// or an enclosing scope, along with cleanups for all variables and temporaries
|
|
// created since the given depth, which should be the cleanup depth of the
|
|
// branch target.
|
|
auto AddBranchWithCleanups(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstBlockId target_id,
|
|
ScopeStack::CleanupScopeDepth depth) -> void;
|
|
|
|
// Adds a return instruction, along with cleanups for all live variables or
|
|
// temporaries in the current function. If no instruction is passed, it
|
|
// defaults to `SemIR::Return{}`.
|
|
auto AddReturnInstWithCleanups(Context& context,
|
|
SemIR::LocIdAndInst loc_id_and_inst) -> void;
|
|
|
|
template <typename LocT>
|
|
auto AddReturnInstWithCleanups(Context& context, LocT loc) -> void {
|
|
AddReturnInstWithCleanups(context, SemIR::LocIdAndInst(loc, SemIR::Return{}));
|
|
}
|
|
template <typename LocT>
|
|
auto AddReturnInstWithCleanups(Context& context, LocT loc,
|
|
SemIR::ReturnExpr inst) -> void {
|
|
AddReturnInstWithCleanups(context, SemIR::LocIdAndInst(loc, inst));
|
|
}
|
|
|
|
} // namespace Carbon::Check
|
|
|
|
#endif // CARBON_TOOLCHAIN_CHECK_CONTROL_FLOW_H_
|