Files
carbon-lang/toolchain/check/control_flow.h
T
Richard Smith 703529fc55 Destroy temporaries at the end of expression statements. (#7513)
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 `;`.
2026-07-16 15:12:39 +00:00

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_