Add implied return; at end of non-value-returning functions. (#2942)

Add validation that every code block in a function is terminated by a sequence of terminating instructions, and that terminators don't appear anywhere else in code blocks.

This required tracking whether we're in a reachable code block. That's done on the fly when we create a new code block; the new `SemanticsNodeBlockId::Unreachable` is used to represent the case where we're not actually creating a code block because we're in unreachable code.
This commit is contained in:
Richard Smith
2023-06-26 15:17:47 -07:00
committed by GitHub
parent 92d73985df
commit 4b69264cb1
76 changed files with 1150 additions and 302 deletions
+106 -5
View File
@@ -28,11 +28,9 @@ SemanticsContext::SemanticsContext(const TokenizedBuffer& tokens,
semantics_ir_(&semantics_ir),
vlog_stream_(vlog_stream),
node_stack_(parse_tree, vlog_stream),
node_block_stack_("node_block_stack_", semantics_ir.node_blocks(),
vlog_stream),
params_or_args_stack_("params_or_args_stack_", semantics_ir.node_blocks(),
vlog_stream),
args_type_info_stack_("args_type_info_stack_", semantics_ir.node_blocks(),
node_block_stack_("node_block_stack_", semantics_ir, vlog_stream),
params_or_args_stack_("params_or_args_stack_", semantics_ir, vlog_stream),
args_type_info_stack_("args_type_info_stack_", semantics_ir,
vlog_stream) {
// Inserts the "Invalid" and "Type" types as "used types" so that
// canonicalization can skip them. We don't emit either for lowering.
@@ -130,11 +128,92 @@ auto SemanticsContext::PopScope() -> void {
}
}
template <typename BranchNode, typename... Args>
static auto AddDominatedBlockAndBranchImpl(SemanticsContext& context,
ParseTree::Node parse_node,
Args... args)
-> SemanticsNodeBlockId {
if (!context.node_block_stack().is_current_block_reachable()) {
return SemanticsNodeBlockId::Unreachable;
}
auto block_id = context.semantics_ir().AddNodeBlock();
context.AddNode(BranchNode::Make(parse_node, block_id, args...));
return block_id;
}
auto SemanticsContext::AddDominatedBlockAndBranch(ParseTree::Node parse_node)
-> SemanticsNodeBlockId {
return AddDominatedBlockAndBranchImpl<SemanticsNode::Branch>(*this,
parse_node);
}
auto SemanticsContext::AddDominatedBlockAndBranchWithArg(
ParseTree::Node parse_node, SemanticsNodeId arg_id)
-> SemanticsNodeBlockId {
return AddDominatedBlockAndBranchImpl<SemanticsNode::BranchWithArg>(
*this, parse_node, arg_id);
}
auto SemanticsContext::AddDominatedBlockAndBranchIf(ParseTree::Node parse_node,
SemanticsNodeId cond_id)
-> SemanticsNodeBlockId {
return AddDominatedBlockAndBranchImpl<SemanticsNode::BranchIf>(
*this, parse_node, cond_id);
}
auto SemanticsContext::AddConvergenceBlockAndPush(
ParseTree::Node parse_node,
std::initializer_list<SemanticsNodeBlockId> blocks) -> void {
CARBON_CHECK(blocks.size() >= 2) << "no convergence";
SemanticsNodeBlockId new_block_id = SemanticsNodeBlockId::Unreachable;
for (SemanticsNodeBlockId block_id : blocks) {
if (block_id != SemanticsNodeBlockId::Unreachable) {
if (new_block_id == SemanticsNodeBlockId::Unreachable) {
new_block_id = semantics_ir().AddNodeBlock();
}
AddNodeToBlock(block_id,
SemanticsNode::Branch::Make(parse_node, new_block_id));
}
}
node_block_stack().Push(new_block_id);
}
auto SemanticsContext::AddConvergenceBlockWithArgAndPush(
ParseTree::Node parse_node,
std::initializer_list<std::pair<SemanticsNodeBlockId, SemanticsNodeId>>
blocks_and_args) -> SemanticsNodeId {
CARBON_CHECK(blocks_and_args.size() >= 2) << "no convergence";
SemanticsNodeBlockId new_block_id = SemanticsNodeBlockId::Unreachable;
for (auto [block_id, arg_id] : blocks_and_args) {
if (block_id != SemanticsNodeBlockId::Unreachable) {
if (new_block_id == SemanticsNodeBlockId::Unreachable) {
new_block_id = semantics_ir().AddNodeBlock();
}
AddNodeToBlock(block_id, SemanticsNode::BranchWithArg::Make(
parse_node, new_block_id, arg_id));
}
}
node_block_stack().Push(new_block_id);
// Acquire the result value.
SemanticsTypeId result_type_id =
semantics_ir().GetNode(blocks_and_args.begin()->second).type_id();
return AddNode(
SemanticsNode::BlockArg::Make(parse_node, result_type_id, new_block_id));
}
// Add the current code block to the enclosing function.
auto SemanticsContext::AddCurrentCodeBlockToFunction() -> void {
CARBON_CHECK(!node_block_stack().empty()) << "no current code block";
CARBON_CHECK(!return_scope_stack().empty()) << "no current function";
if (!node_block_stack().is_current_block_reachable()) {
// Don't include unreachable blocks in the function.
return;
}
auto function_id = semantics_ir()
.GetNode(return_scope_stack().back())
.GetAsFunctionDeclaration();
@@ -143,6 +222,28 @@ auto SemanticsContext::AddCurrentCodeBlockToFunction() -> void {
.body_block_ids.push_back(node_block_stack().PeekForAdd());
}
auto SemanticsContext::is_current_position_reachable() -> bool {
switch (auto block_id = node_block_stack().Peek(); block_id.index) {
case SemanticsNodeBlockId::Unreachable.index: {
return false;
}
case SemanticsNodeBlockId::Invalid.index: {
return true;
}
default: {
// Our current position is at the end of a real block. That position is
// reachable unless the previous instruction is a terminator instruction.
const auto& block_contents = semantics_ir().GetNodeBlock(block_id);
if (block_contents.empty()) {
return true;
}
const auto& last_node = semantics_ir().GetNode(block_contents.back());
return last_node.kind().terminator_kind() !=
SemanticsTerminatorKind::Terminator;
}
}
}
auto SemanticsContext::ImplicitAsForArgs(
SemanticsNodeBlockId arg_refs_id, ParseTree::Node param_parse_node,
SemanticsNodeBlockId param_refs_id,