Move control flow block functions to their own file. (#4921)

context.cpp is getting large, so I'm looking at a few ways to cut out
clusters of functions. This felt like a logical cluster of functions to
move to their own file.

Note this doesn't touch the implementation at all, beyond what's needed
to change from `Context` members to context args.
This commit is contained in:
Jon Ross-Perkins
2025-02-11 21:38:09 +00:00
committed by GitHub
parent 8eb4e24cb6
commit b0d49ba957
10 changed files with 226 additions and 189 deletions
+2
View File
@@ -17,6 +17,7 @@ cc_library(
srcs = [
"call.cpp",
"context.cpp",
"control_flow.cpp",
"convert.cpp",
"decl_name_stack.cpp",
"deduce.cpp",
@@ -44,6 +45,7 @@ cc_library(
hdrs = [
"call.h",
"context.h",
"control_flow.h",
"convert.h",
"decl_introducer_state.h",
"decl_name_stack.h",
-120
View File
@@ -745,110 +745,6 @@ auto Context::LookupNameInCore(SemIR::LocId loc_id, llvm::StringRef name)
return constant_values().GetConstantInstId(scope_result.target_inst_id());
}
template <typename BranchNode, typename... Args>
static auto AddDominatedBlockAndBranchImpl(Context& context,
Parse::NodeId node_id, Args... args)
-> SemIR::InstBlockId {
if (!context.inst_block_stack().is_current_block_reachable()) {
return SemIR::InstBlockId::Unreachable;
}
auto block_id = context.inst_blocks().AddDefaultValue();
context.AddInst<BranchNode>(node_id, {block_id, args...});
return block_id;
}
auto Context::AddDominatedBlockAndBranch(Parse::NodeId node_id)
-> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::Branch>(*this, node_id);
}
auto Context::AddDominatedBlockAndBranchWithArg(Parse::NodeId node_id,
SemIR::InstId arg_id)
-> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::BranchWithArg>(*this, node_id,
arg_id);
}
auto Context::AddDominatedBlockAndBranchIf(Parse::NodeId node_id,
SemIR::InstId cond_id)
-> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::BranchIf>(*this, node_id,
cond_id);
}
auto Context::AddConvergenceBlockAndPush(Parse::NodeId node_id, int num_blocks)
-> void {
CARBON_CHECK(num_blocks >= 2, "no convergence");
SemIR::InstBlockId new_block_id = SemIR::InstBlockId::Unreachable;
for ([[maybe_unused]] auto _ : llvm::seq(num_blocks)) {
if (inst_block_stack().is_current_block_reachable()) {
if (new_block_id == SemIR::InstBlockId::Unreachable) {
new_block_id = inst_blocks().AddDefaultValue();
}
CARBON_CHECK(node_id.has_value());
AddInst<SemIR::Branch>(node_id, {.target_id = new_block_id});
}
inst_block_stack().Pop();
}
inst_block_stack().Push(new_block_id);
AddToRegion(new_block_id, node_id);
}
auto Context::AddConvergenceBlockWithArgAndPush(
Parse::NodeId node_id, std::initializer_list<SemIR::InstId> block_args)
-> SemIR::InstId {
CARBON_CHECK(block_args.size() >= 2, "no convergence");
SemIR::InstBlockId new_block_id = SemIR::InstBlockId::Unreachable;
for (auto arg_id : block_args) {
if (inst_block_stack().is_current_block_reachable()) {
if (new_block_id == SemIR::InstBlockId::Unreachable) {
new_block_id = inst_blocks().AddDefaultValue();
}
AddInst<SemIR::BranchWithArg>(
node_id, {.target_id = new_block_id, .arg_id = arg_id});
}
inst_block_stack().Pop();
}
inst_block_stack().Push(new_block_id);
AddToRegion(new_block_id, node_id);
// Acquire the result value.
SemIR::TypeId result_type_id = insts().Get(*block_args.begin()).type_id();
return AddInst<SemIR::BlockArg>(
node_id, {.type_id = result_type_id, .block_id = new_block_id});
}
auto Context::SetBlockArgResultBeforeConstantUse(SemIR::InstId select_id,
SemIR::InstId cond_id,
SemIR::InstId if_true,
SemIR::InstId if_false)
-> void {
CARBON_CHECK(insts().Is<SemIR::BlockArg>(select_id));
// Determine the constant result based on the condition value.
SemIR::ConstantId const_id = SemIR::ConstantId::NotConstant;
auto cond_const_id = constant_values().Get(cond_id);
if (!cond_const_id.is_template()) {
// Symbolic or non-constant condition means a non-constant result.
} else if (auto literal = insts().TryGetAs<SemIR::BoolLiteral>(
constant_values().GetInstId(cond_const_id))) {
const_id = constant_values().Get(literal.value().value.ToBool() ? if_true
: if_false);
} else {
CARBON_CHECK(cond_const_id == SemIR::ErrorInst::SingletonConstantId,
"Unexpected constant branch condition.");
const_id = SemIR::ErrorInst::SingletonConstantId;
}
if (const_id.is_constant()) {
CARBON_VLOG("Constant: {0} -> {1}\n", insts().Get(select_id),
constant_values().GetInstId(const_id));
constant_values().Set(select_id, const_id);
}
}
auto Context::AddToRegion(SemIR::InstBlockId block_id, SemIR::LocId loc_id)
-> void {
if (region_stack_.empty()) {
@@ -936,22 +832,6 @@ auto Context::InsertHere(SemIR::ExprRegionId region_id) -> SemIR::InstId {
return region.result_id;
}
auto Context::is_current_position_reachable() -> bool {
if (!inst_block_stack().is_current_block_reachable()) {
return false;
}
// Our current position is at the end of a reachable block. That position is
// reachable unless the previous instruction is a terminator instruction.
auto block_contents = inst_block_stack().PeekCurrentBlockContents();
if (block_contents.empty()) {
return true;
}
const auto& last_inst = insts().Get(block_contents.back());
return last_inst.kind().terminator_kind() !=
SemIR::TerminatorKind::Terminator;
}
auto Context::Finalize() -> void {
// Pop information for the file-level scope.
sem_ir().set_top_inst_block_id(inst_block_stack().Pop());
+2 -51
View File
@@ -314,57 +314,6 @@ class Context {
return result;
}
// 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(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(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(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(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(
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(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 is_current_position_reachable() -> bool;
// Returns the type ID for a constant of type `type`.
auto GetTypeIdForTypeConstant(SemIR::ConstantId constant_id) -> SemIR::TypeId;
@@ -489,6 +438,8 @@ class Context {
return parse_tree().tokens();
}
auto vlog_stream() -> llvm::raw_ostream* { return vlog_stream_; }
auto node_stack() -> NodeStack& { return node_stack_; }
auto inst_block_stack() -> InstBlockStack& { return inst_block_stack_; }
+132
View File
@@ -0,0 +1,132 @@
// 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
#include "toolchain/check/control_flow.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
template <typename BranchNode, typename... Args>
static auto AddDominatedBlockAndBranchImpl(Context& context,
Parse::NodeId node_id, Args... args)
-> SemIR::InstBlockId {
if (!context.inst_block_stack().is_current_block_reachable()) {
return SemIR::InstBlockId::Unreachable;
}
auto block_id = context.inst_blocks().AddDefaultValue();
context.AddInst<BranchNode>(node_id, {block_id, args...});
return block_id;
}
auto AddDominatedBlockAndBranch(Context& context, Parse::NodeId node_id)
-> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::Branch>(context, node_id);
}
auto AddDominatedBlockAndBranchWithArg(Context& context, Parse::NodeId node_id,
SemIR::InstId arg_id)
-> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::BranchWithArg>(context, node_id,
arg_id);
}
auto AddDominatedBlockAndBranchIf(Context& context, Parse::NodeId node_id,
SemIR::InstId cond_id) -> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::BranchIf>(context, node_id,
cond_id);
}
auto AddConvergenceBlockAndPush(Context& context, Parse::NodeId node_id,
int num_blocks) -> void {
CARBON_CHECK(num_blocks >= 2, "no convergence");
SemIR::InstBlockId new_block_id = SemIR::InstBlockId::Unreachable;
for ([[maybe_unused]] auto _ : llvm::seq(num_blocks)) {
if (context.inst_block_stack().is_current_block_reachable()) {
if (new_block_id == SemIR::InstBlockId::Unreachable) {
new_block_id = context.inst_blocks().AddDefaultValue();
}
CARBON_CHECK(node_id.has_value());
context.AddInst<SemIR::Branch>(node_id, {.target_id = new_block_id});
}
context.inst_block_stack().Pop();
}
context.inst_block_stack().Push(new_block_id);
context.AddToRegion(new_block_id, node_id);
}
auto AddConvergenceBlockWithArgAndPush(
Context& context, Parse::NodeId node_id,
std::initializer_list<SemIR::InstId> block_args) -> SemIR::InstId {
CARBON_CHECK(block_args.size() >= 2, "no convergence");
SemIR::InstBlockId new_block_id = SemIR::InstBlockId::Unreachable;
for (auto arg_id : block_args) {
if (context.inst_block_stack().is_current_block_reachable()) {
if (new_block_id == SemIR::InstBlockId::Unreachable) {
new_block_id = context.inst_blocks().AddDefaultValue();
}
context.AddInst<SemIR::BranchWithArg>(
node_id, {.target_id = new_block_id, .arg_id = arg_id});
}
context.inst_block_stack().Pop();
}
context.inst_block_stack().Push(new_block_id);
context.AddToRegion(new_block_id, node_id);
// Acquire the result value.
SemIR::TypeId result_type_id =
context.insts().Get(*block_args.begin()).type_id();
return context.AddInst<SemIR::BlockArg>(
node_id, {.type_id = result_type_id, .block_id = new_block_id});
}
auto SetBlockArgResultBeforeConstantUse(Context& context,
SemIR::InstId select_id,
SemIR::InstId cond_id,
SemIR::InstId if_true,
SemIR::InstId if_false) -> void {
CARBON_CHECK(context.insts().Is<SemIR::BlockArg>(select_id));
// Determine the constant result based on the condition value.
SemIR::ConstantId const_id = SemIR::ConstantId::NotConstant;
auto cond_const_id = context.constant_values().Get(cond_id);
if (!cond_const_id.is_template()) {
// Symbolic or non-constant condition means a non-constant result.
} else if (auto literal = context.insts().TryGetAs<SemIR::BoolLiteral>(
context.constant_values().GetInstId(cond_const_id))) {
const_id = context.constant_values().Get(
literal.value().value.ToBool() ? if_true : if_false);
} else {
CARBON_CHECK(cond_const_id == SemIR::ErrorInst::SingletonConstantId,
"Unexpected constant branch condition.");
const_id = SemIR::ErrorInst::SingletonConstantId;
}
if (const_id.is_constant()) {
CARBON_VLOG_TO(context.vlog_stream(), "Constant: {0} -> {1}\n",
context.insts().Get(select_id),
context.constant_values().GetInstId(const_id));
context.constant_values().Set(select_id, const_id);
}
}
auto IsCurrentPositionReachable(Context& context) -> bool {
if (!context.inst_block_stack().is_current_block_reachable()) {
return false;
}
// Our current position is at the end of a reachable block. That position is
// reachable unless the previous instruction is a terminator instruction.
auto block_contents = context.inst_block_stack().PeekCurrentBlockContents();
if (block_contents.empty()) {
return true;
}
const auto& last_inst = context.insts().Get(block_contents.back());
return last_inst.kind().terminator_kind() !=
SemIR::TerminatorKind::Terminator;
}
} // namespace Carbon::Check
+67
View File
@@ -0,0 +1,67 @@
// 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/sem_ir/ids.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;
} // namespace Carbon::Check
#endif // CARBON_TOOLCHAIN_CHECK_CONTROL_FLOW_H_
+2 -1
View File
@@ -4,6 +4,7 @@
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/control_flow.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/decl_introducer_state.h"
#include "toolchain/check/decl_name_stack.h"
@@ -445,7 +446,7 @@ auto HandleParseNode(Context& context, Parse::FunctionDefinitionId node_id)
// If the `}` of the function is reachable, reject if we need a return value
// and otherwise add an implicit `return;`.
if (context.is_current_position_reachable()) {
if (IsCurrentPositionReachable(context)) {
if (context.functions()
.Get(function_id)
.return_slot_pattern_id.has_value()) {
+7 -6
View File
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/context.h"
#include "toolchain/check/control_flow.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/literal.h"
@@ -19,8 +20,8 @@ auto HandleParseNode(Context& context, Parse::IfExprIfId node_id) -> bool {
cond_value_id = ConvertToBoolValue(context, if_node, cond_value_id);
context.node_stack().Push(cond_node, cond_value_id);
auto then_block_id =
context.AddDominatedBlockAndBranchIf(if_node, cond_value_id);
auto else_block_id = context.AddDominatedBlockAndBranch(if_node);
AddDominatedBlockAndBranchIf(context, if_node, cond_value_id);
auto else_block_id = AddDominatedBlockAndBranch(context, if_node);
// Start emitting the `then` block.
context.inst_block_stack().Pop();
@@ -85,10 +86,10 @@ auto HandleParseNode(Context& context, Parse::IfExprElseId node_id) -> bool {
ConvertToValueOfType(context, else_node, else_value_id, result_type_id);
// Create a resumption block and branches to it.
auto chosen_value_id = context.AddConvergenceBlockWithArgAndPush(
if_node, {else_value_id, then_value_id});
context.SetBlockArgResultBeforeConstantUse(chosen_value_id, cond_value_id,
then_value_id, else_value_id);
auto chosen_value_id = AddConvergenceBlockWithArgAndPush(
context, if_node, {else_value_id, then_value_id});
SetBlockArgResultBeforeConstantUse(context, chosen_value_id, cond_value_id,
then_value_id, else_value_id);
// Push the result value.
context.node_stack().Push(else_node, chosen_value_id);
+4 -3
View File
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/context.h"
#include "toolchain/check/control_flow.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/handle.h"
@@ -22,8 +23,8 @@ auto HandleParseNode(Context& context, Parse::IfConditionId node_id) -> bool {
// there is no `else`, the then block will terminate with a branch to the
// else block, which will be reused as the resumption block.
auto then_block_id =
context.AddDominatedBlockAndBranchIf(node_id, cond_value_id);
auto else_block_id = context.AddDominatedBlockAndBranch(node_id);
AddDominatedBlockAndBranchIf(context, node_id, cond_value_id);
auto else_block_id = AddDominatedBlockAndBranch(context, node_id);
// Start emitting the `then` block.
context.inst_block_stack().Pop();
@@ -64,7 +65,7 @@ auto HandleParseNode(Context& context, Parse::IfStatementId node_id) -> bool {
// Branch from the then and else blocks to a new resumption block.
context.node_stack()
.PopAndDiscardSoloNodeId<Parse::NodeKind::IfStatementElse>();
context.AddConvergenceBlockAndPush(node_id, /*num_blocks=*/2);
AddConvergenceBlockAndPush(context, node_id, /*num_blocks=*/2);
break;
}
+4 -3
View File
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/context.h"
#include "toolchain/check/control_flow.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/handle.h"
@@ -16,7 +17,7 @@ auto HandleParseNode(Context& context, Parse::WhileConditionStartId node_id)
// Branch to the loop header block. Note that we create a new block here even
// if the current block is empty; this ensures that the loop always has a
// preheader block.
auto loop_header_id = context.AddDominatedBlockAndBranch(node_id);
auto loop_header_id = AddDominatedBlockAndBranch(context, node_id);
context.inst_block_stack().Pop();
// Start emitting the loop header block.
@@ -36,8 +37,8 @@ auto HandleParseNode(Context& context, Parse::WhileConditionId node_id)
// Branch to either the loop body or the loop exit block.
auto loop_body_id =
context.AddDominatedBlockAndBranchIf(node_id, cond_value_id);
auto loop_exit_id = context.AddDominatedBlockAndBranch(node_id);
AddDominatedBlockAndBranchIf(context, node_id, cond_value_id);
auto loop_exit_id = AddDominatedBlockAndBranch(context, node_id);
context.inst_block_stack().Pop();
// Start emitting the loop body.
+6 -5
View File
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/context.h"
#include "toolchain/check/control_flow.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/operator.h"
@@ -349,9 +350,9 @@ static auto HandleShortCircuitOperand(Context& context, Parse::NodeId node_id,
// Create a block for the right-hand side and for the continuation.
auto rhs_block_id =
context.AddDominatedBlockAndBranchIf(node_id, branch_value_id);
auto end_block_id = context.AddDominatedBlockAndBranchWithArg(
node_id, short_circuit_result_id);
AddDominatedBlockAndBranchIf(context, node_id, branch_value_id);
auto end_block_id = AddDominatedBlockAndBranchWithArg(
context, node_id, short_circuit_result_id);
// Push the branch condition and result for use when handling the complete
// expression.
@@ -410,8 +411,8 @@ static auto HandleShortCircuitOperator(Context& context, Parse::NodeId node_id)
auto result_id = context.AddInst<SemIR::BlockArg>(
node_id, {.type_id = context.insts().Get(rhs_id).type_id(),
.block_id = resume_block_id});
context.SetBlockArgResultBeforeConstantUse(result_id, branch_value_id, rhs_id,
short_circuit_result_id);
SetBlockArgResultBeforeConstantUse(context, result_id, branch_value_id,
rhs_id, short_circuit_result_id);
context.node_stack().Push(node_id, result_id);
return true;
}