Skip to content

Commit

Permalink
Refactor finalization of initializers. (#3250)
Browse files Browse the repository at this point in the history
Factor out the common code to find the return slot for an initializing
expression, and use it to simplify the two different ways we finalize
initializers, as either initializing a temporary or initializing some
specific object.

This slightly changes the SemIR we create for function calls: instead of
rewriting the Call node to have a different destination and replacing
its temporary with a `no_op`, we now replace its temporary with a
`stub_reference` to the new destination. This results in the same amount
of SemIR being produced, but allows calls and other kinds of
initializers to be handled uniformly.
  • Loading branch information
zygoloid authored Sep 21, 2023
1 parent 31d55da commit caecdc7
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 105 deletions.
162 changes: 66 additions & 96 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,121 +472,92 @@ auto Context::ConvertToValueOrReferenceExpression(SemIR::NodeId expr_id,
}
}

auto Context::MarkInitializerFor(SemIR::NodeId init_id, SemIR::NodeId target_id)
-> void {
SemIR::Node init = semantics_ir().GetNode(init_id);
CARBON_CHECK(SemIR::GetExpressionCategory(semantics_ir(), init_id) ==
SemIR::ExpressionCategory::Initializing)
<< "initialization from non-initializing node " << init;

// Given an initializing expression, find its return slot. Returns `Invalid` if
// there is no return slot, because the initialization is not performed in
// place.
static auto FindReturnSlotForInitializer(SemIR::File& semantics_ir,
SemIR::NodeId init_id)
-> SemIR::NodeId {
SemIR::Node init = semantics_ir.GetNode(init_id);
switch (init.kind()) {
default:
CARBON_FATAL() << "Initialization from unexpected node " << init;

case SemIR::NodeKind::StructInit:
case SemIR::NodeKind::TupleInit:
case SemIR::NodeKind::InitializeFrom:
CARBON_FATAL() << init << " should already have a destination";
// TODO: Track a return slot for these initializers.
CARBON_FATAL() << init
<< " should be created with its return slot already "
"filled in properly";

case SemIR::NodeKind::InitializeFrom: {
auto [src_id, dest_id] = init.GetAsInitializeFrom();
return dest_id;
}

case SemIR::NodeKind::Call: {
// If the callee has a return slot, point it at our target.
auto [refs_id, callee_id] = init.GetAsCall();
if (semantics_ir().GetFunction(callee_id).return_slot_id.is_valid()) {
// Replace the return slot with our given target, and remove the
// tentatively-created temporary.
auto temporary_id = std::exchange(
semantics_ir().GetNodeBlock(refs_id).back(), target_id);
auto temporary = semantics_ir().GetNode(temporary_id);
CARBON_CHECK(temporary.kind() == SemIR::NodeKind::TemporaryStorage)
<< "Return slot for function call does not contain a temporary; "
<< "initialized multiple times? Have " << temporary;
semantics_ir().ReplaceNode(
temporary_id, SemIR::Node::NoOp::Make(temporary.parse_node()));
if (!semantics_ir.GetFunction(callee_id).return_slot_id.is_valid()) {
return SemIR::NodeId::Invalid;
}
return;
return semantics_ir.GetNodeBlock(refs_id).back();
}

case SemIR::NodeKind::ArrayInit: {
// Rewrite the return slot as a reference to our target. We can't just
// update the index in `refs_id`, like we do for a Call, because there
// will be other references to the return slot for the individual array
// element initializers.
auto [src_id, refs_id] = init.GetAsArrayInit();
auto temporary_id = semantics_ir().GetNodeBlock(refs_id).back();
CARBON_CHECK(semantics_ir().GetNode(temporary_id).kind() ==
SemIR::NodeKind::TemporaryStorage)
<< "Return slot for array init does not contain a temporary; "
<< "initialized multiple times? Have "
<< semantics_ir().GetNode(temporary_id);
semantics_ir().ReplaceNode(
temporary_id,
SemIR::Node::StubReference::Make(
init.parse_node(), semantics_ir().GetNode(target_id).type_id(),
target_id));
return;
return semantics_ir.GetNodeBlock(refs_id).back();
}
}
}

auto Context::MarkInitializerFor(SemIR::NodeId init_id, SemIR::NodeId target_id)
-> void {
auto return_slot_id = FindReturnSlotForInitializer(semantics_ir(), init_id);
if (return_slot_id.is_valid()) {
// Replace the temporary in the return slot with a reference to our target.
CARBON_CHECK(semantics_ir().GetNode(return_slot_id).kind() ==
SemIR::NodeKind::TemporaryStorage)
<< "Return slot for initializer does not contain a temporary; "
<< "initialized multiple times? Have "
<< semantics_ir().GetNode(return_slot_id);
semantics_ir().ReplaceNode(
return_slot_id,
SemIR::Node::StubReference::Make(
semantics_ir().GetNode(init_id).parse_node(),
semantics_ir().GetNode(target_id).type_id(), target_id));
}
}

auto Context::FinalizeTemporary(SemIR::NodeId init_id, bool discarded)
-> SemIR::NodeId {
SemIR::Node init = semantics_ir().GetNode(init_id);
CARBON_CHECK(SemIR::GetExpressionCategory(semantics_ir(), init_id) ==
SemIR::ExpressionCategory::Initializing)
<< "initialization from non-initializing node " << init;

switch (init.kind()) {
default:
CARBON_FATAL() << "Initialization from unexpected node " << init;

case SemIR::NodeKind::StructInit:
case SemIR::NodeKind::TupleInit:
case SemIR::NodeKind::InitializeFrom:
CARBON_FATAL() << init << " should already have a destination";

case SemIR::NodeKind::Call: {
auto [refs_id, callee_id] = init.GetAsCall();
if (semantics_ir().GetFunction(callee_id).return_slot_id.is_valid()) {
// The return slot should have a materialized temporary in it.
auto temporary_id = semantics_ir().GetNodeBlock(refs_id).back();
CARBON_CHECK(semantics_ir().GetNode(temporary_id).kind() ==
SemIR::NodeKind::TemporaryStorage)
<< "Return slot for function call does not contain a temporary; "
<< "initialized multiple times? Have "
<< semantics_ir().GetNode(temporary_id);
return AddNode(SemIR::Node::Temporary::Make(
init.parse_node(), init.type_id(), temporary_id, init_id));
}

if (discarded) {
// Don't invent a temporary that we're going to discard.
return SemIR::NodeId::Invalid;
}

// The function has no return slot, but we want to produce a temporary
// object. Materialize one now.
// TODO: Consider using an invalid ID to mean that we immediately
// materialize and initialize a temporary, rather than two separate
// nodes.
auto temporary_id = AddNode(SemIR::Node::TemporaryStorage::Make(
init.parse_node(), init.type_id()));
return AddNode(SemIR::Node::Temporary::Make(
init.parse_node(), init.type_id(), temporary_id, init_id));
}

case SemIR::NodeKind::ArrayInit: {
auto [src_id, refs_id] = init.GetAsArrayInit();
// The return slot should have a materialized temporary in it.
auto temporary_id = semantics_ir().GetNodeBlock(refs_id).back();
CARBON_CHECK(semantics_ir().GetNode(temporary_id).kind() ==
SemIR::NodeKind::TemporaryStorage)
<< "Return slot for array init does not contain a temporary; "
<< "initialized multiple times? Have "
<< semantics_ir().GetNode(temporary_id);
return AddNode(SemIR::Node::Temporary::Make(
init.parse_node(), init.type_id(), temporary_id, init_id));
}
}
auto return_slot_id = FindReturnSlotForInitializer(semantics_ir(), init_id);
if (return_slot_id.is_valid()) {
// The return slot should already have a materialized temporary in it.
CARBON_CHECK(semantics_ir().GetNode(return_slot_id).kind() ==
SemIR::NodeKind::TemporaryStorage)
<< "Return slot for initializer does not contain a temporary; "
<< "initialized multiple times? Have "
<< semantics_ir().GetNode(return_slot_id);
auto init = semantics_ir().GetNode(init_id);
return AddNode(SemIR::Node::Temporary::Make(
init.parse_node(), init.type_id(), return_slot_id, init_id));
}

if (discarded) {
// Don't invent a temporary that we're going to discard.
return SemIR::NodeId::Invalid;
}

// The initializer has no return slot, but we want to produce a temporary
// object. Materialize one now.
// TODO: Consider using an invalid ID to mean that we immediately
// materialize and initialize a temporary, rather than two separate
// nodes.
auto init = semantics_ir().GetNode(init_id);
auto temporary_id = AddNode(
SemIR::Node::TemporaryStorage::Make(init.parse_node(), init.type_id()));
return AddNode(SemIR::Node::Temporary::Make(init.parse_node(), init.type_id(),
temporary_id, init_id));
}

auto Context::HandleDiscardedExpression(SemIR::NodeId expr_id) -> void {
Expand All @@ -595,7 +566,6 @@ auto Context::HandleDiscardedExpression(SemIR::NodeId expr_id) -> void {
ConvertToValueOrReferenceExpression(expr_id, /*discarded=*/true);

// TODO: This will eventually need to do some "do not discard" analysis.
(void)expr_id;
}

auto Context::ImplicitAsForArgs(Parse::Node call_parse_node,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ fn H() -> i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %.loc10_19: (type, type) = tuple_literal (i32, i32)
// CHECK:STDOUT: %v: ref (i32, i32) = var "v"
// CHECK:STDOUT: no_op
// CHECK:STDOUT: %.loc10_24: init (i32, i32) = call @F() to %v
// CHECK:STDOUT: assign %v, %.loc10_24
// CHECK:STDOUT: no_op
// CHECK:STDOUT: %.loc11: init (i32, i32) = call @F() to %v
// CHECK:STDOUT: assign %v, %.loc11
// CHECK:STDOUT: no_op
// CHECK:STDOUT: %.loc12: init (i32, i32) = call @F() to %return
// CHECK:STDOUT: return %.loc12
// CHECK:STDOUT: %.loc10_24.1: ref (i32, i32) = stub_reference %v
// CHECK:STDOUT: %.loc10_24.2: init (i32, i32) = call @F() to %.loc10_24.1
// CHECK:STDOUT: assign %v, %.loc10_24.2
// CHECK:STDOUT: %.loc11_8.1: ref (i32, i32) = stub_reference %v
// CHECK:STDOUT: %.loc11_8.2: init (i32, i32) = call @F() to %.loc11_8.1
// CHECK:STDOUT: assign %v, %.loc11_8.2
// CHECK:STDOUT: %.loc12_11.1: ref (i32, i32) = stub_reference %return
// CHECK:STDOUT: %.loc12_11.2: init (i32, i32) = call @F() to %.loc12_11.1
// CHECK:STDOUT: return %.loc12_11.2
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @H() -> i32 {
Expand Down

0 comments on commit caecdc7

Please sign in to comment.