Skip to content

Commit caecdc7

Browse files
authored
Refactor finalization of initializers. (#3250)
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.
1 parent 31d55da commit caecdc7

File tree

2 files changed

+75
-105
lines changed

2 files changed

+75
-105
lines changed

toolchain/check/context.cpp

Lines changed: 66 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -472,121 +472,92 @@ auto Context::ConvertToValueOrReferenceExpression(SemIR::NodeId expr_id,
472472
}
473473
}
474474

475-
auto Context::MarkInitializerFor(SemIR::NodeId init_id, SemIR::NodeId target_id)
476-
-> void {
477-
SemIR::Node init = semantics_ir().GetNode(init_id);
478-
CARBON_CHECK(SemIR::GetExpressionCategory(semantics_ir(), init_id) ==
479-
SemIR::ExpressionCategory::Initializing)
480-
<< "initialization from non-initializing node " << init;
481-
475+
// Given an initializing expression, find its return slot. Returns `Invalid` if
476+
// there is no return slot, because the initialization is not performed in
477+
// place.
478+
static auto FindReturnSlotForInitializer(SemIR::File& semantics_ir,
479+
SemIR::NodeId init_id)
480+
-> SemIR::NodeId {
481+
SemIR::Node init = semantics_ir.GetNode(init_id);
482482
switch (init.kind()) {
483483
default:
484484
CARBON_FATAL() << "Initialization from unexpected node " << init;
485485

486486
case SemIR::NodeKind::StructInit:
487487
case SemIR::NodeKind::TupleInit:
488-
case SemIR::NodeKind::InitializeFrom:
489-
CARBON_FATAL() << init << " should already have a destination";
488+
// TODO: Track a return slot for these initializers.
489+
CARBON_FATAL() << init
490+
<< " should be created with its return slot already "
491+
"filled in properly";
492+
493+
case SemIR::NodeKind::InitializeFrom: {
494+
auto [src_id, dest_id] = init.GetAsInitializeFrom();
495+
return dest_id;
496+
}
490497

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

509506
case SemIR::NodeKind::ArrayInit: {
510-
// Rewrite the return slot as a reference to our target. We can't just
511-
// update the index in `refs_id`, like we do for a Call, because there
512-
// will be other references to the return slot for the individual array
513-
// element initializers.
514507
auto [src_id, refs_id] = init.GetAsArrayInit();
515-
auto temporary_id = semantics_ir().GetNodeBlock(refs_id).back();
516-
CARBON_CHECK(semantics_ir().GetNode(temporary_id).kind() ==
517-
SemIR::NodeKind::TemporaryStorage)
518-
<< "Return slot for array init does not contain a temporary; "
519-
<< "initialized multiple times? Have "
520-
<< semantics_ir().GetNode(temporary_id);
521-
semantics_ir().ReplaceNode(
522-
temporary_id,
523-
SemIR::Node::StubReference::Make(
524-
init.parse_node(), semantics_ir().GetNode(target_id).type_id(),
525-
target_id));
526-
return;
508+
return semantics_ir.GetNodeBlock(refs_id).back();
527509
}
528510
}
529511
}
530512

513+
auto Context::MarkInitializerFor(SemIR::NodeId init_id, SemIR::NodeId target_id)
514+
-> void {
515+
auto return_slot_id = FindReturnSlotForInitializer(semantics_ir(), init_id);
516+
if (return_slot_id.is_valid()) {
517+
// Replace the temporary in the return slot with a reference to our target.
518+
CARBON_CHECK(semantics_ir().GetNode(return_slot_id).kind() ==
519+
SemIR::NodeKind::TemporaryStorage)
520+
<< "Return slot for initializer does not contain a temporary; "
521+
<< "initialized multiple times? Have "
522+
<< semantics_ir().GetNode(return_slot_id);
523+
semantics_ir().ReplaceNode(
524+
return_slot_id,
525+
SemIR::Node::StubReference::Make(
526+
semantics_ir().GetNode(init_id).parse_node(),
527+
semantics_ir().GetNode(target_id).type_id(), target_id));
528+
}
529+
}
530+
531531
auto Context::FinalizeTemporary(SemIR::NodeId init_id, bool discarded)
532532
-> SemIR::NodeId {
533-
SemIR::Node init = semantics_ir().GetNode(init_id);
534-
CARBON_CHECK(SemIR::GetExpressionCategory(semantics_ir(), init_id) ==
535-
SemIR::ExpressionCategory::Initializing)
536-
<< "initialization from non-initializing node " << init;
537-
538-
switch (init.kind()) {
539-
default:
540-
CARBON_FATAL() << "Initialization from unexpected node " << init;
541-
542-
case SemIR::NodeKind::StructInit:
543-
case SemIR::NodeKind::TupleInit:
544-
case SemIR::NodeKind::InitializeFrom:
545-
CARBON_FATAL() << init << " should already have a destination";
546-
547-
case SemIR::NodeKind::Call: {
548-
auto [refs_id, callee_id] = init.GetAsCall();
549-
if (semantics_ir().GetFunction(callee_id).return_slot_id.is_valid()) {
550-
// The return slot should have a materialized temporary in it.
551-
auto temporary_id = semantics_ir().GetNodeBlock(refs_id).back();
552-
CARBON_CHECK(semantics_ir().GetNode(temporary_id).kind() ==
553-
SemIR::NodeKind::TemporaryStorage)
554-
<< "Return slot for function call does not contain a temporary; "
555-
<< "initialized multiple times? Have "
556-
<< semantics_ir().GetNode(temporary_id);
557-
return AddNode(SemIR::Node::Temporary::Make(
558-
init.parse_node(), init.type_id(), temporary_id, init_id));
559-
}
560-
561-
if (discarded) {
562-
// Don't invent a temporary that we're going to discard.
563-
return SemIR::NodeId::Invalid;
564-
}
565-
566-
// The function has no return slot, but we want to produce a temporary
567-
// object. Materialize one now.
568-
// TODO: Consider using an invalid ID to mean that we immediately
569-
// materialize and initialize a temporary, rather than two separate
570-
// nodes.
571-
auto temporary_id = AddNode(SemIR::Node::TemporaryStorage::Make(
572-
init.parse_node(), init.type_id()));
573-
return AddNode(SemIR::Node::Temporary::Make(
574-
init.parse_node(), init.type_id(), temporary_id, init_id));
575-
}
576-
577-
case SemIR::NodeKind::ArrayInit: {
578-
auto [src_id, refs_id] = init.GetAsArrayInit();
579-
// The return slot should have a materialized temporary in it.
580-
auto temporary_id = semantics_ir().GetNodeBlock(refs_id).back();
581-
CARBON_CHECK(semantics_ir().GetNode(temporary_id).kind() ==
582-
SemIR::NodeKind::TemporaryStorage)
583-
<< "Return slot for array init does not contain a temporary; "
584-
<< "initialized multiple times? Have "
585-
<< semantics_ir().GetNode(temporary_id);
586-
return AddNode(SemIR::Node::Temporary::Make(
587-
init.parse_node(), init.type_id(), temporary_id, init_id));
588-
}
589-
}
533+
auto return_slot_id = FindReturnSlotForInitializer(semantics_ir(), init_id);
534+
if (return_slot_id.is_valid()) {
535+
// The return slot should already have a materialized temporary in it.
536+
CARBON_CHECK(semantics_ir().GetNode(return_slot_id).kind() ==
537+
SemIR::NodeKind::TemporaryStorage)
538+
<< "Return slot for initializer does not contain a temporary; "
539+
<< "initialized multiple times? Have "
540+
<< semantics_ir().GetNode(return_slot_id);
541+
auto init = semantics_ir().GetNode(init_id);
542+
return AddNode(SemIR::Node::Temporary::Make(
543+
init.parse_node(), init.type_id(), return_slot_id, init_id));
544+
}
545+
546+
if (discarded) {
547+
// Don't invent a temporary that we're going to discard.
548+
return SemIR::NodeId::Invalid;
549+
}
550+
551+
// The initializer has no return slot, but we want to produce a temporary
552+
// object. Materialize one now.
553+
// TODO: Consider using an invalid ID to mean that we immediately
554+
// materialize and initialize a temporary, rather than two separate
555+
// nodes.
556+
auto init = semantics_ir().GetNode(init_id);
557+
auto temporary_id = AddNode(
558+
SemIR::Node::TemporaryStorage::Make(init.parse_node(), init.type_id()));
559+
return AddNode(SemIR::Node::Temporary::Make(init.parse_node(), init.type_id(),
560+
temporary_id, init_id));
590561
}
591562

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

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

601571
auto Context::ImplicitAsForArgs(Parse::Node call_parse_node,

toolchain/check/testdata/expression_category/in_place_tuple_initialization.carbon

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ fn H() -> i32 {
2828
// CHECK:STDOUT: !entry:
2929
// CHECK:STDOUT: %.loc10_19: (type, type) = tuple_literal (i32, i32)
3030
// CHECK:STDOUT: %v: ref (i32, i32) = var "v"
31-
// CHECK:STDOUT: no_op
32-
// CHECK:STDOUT: %.loc10_24: init (i32, i32) = call @F() to %v
33-
// CHECK:STDOUT: assign %v, %.loc10_24
34-
// CHECK:STDOUT: no_op
35-
// CHECK:STDOUT: %.loc11: init (i32, i32) = call @F() to %v
36-
// CHECK:STDOUT: assign %v, %.loc11
37-
// CHECK:STDOUT: no_op
38-
// CHECK:STDOUT: %.loc12: init (i32, i32) = call @F() to %return
39-
// CHECK:STDOUT: return %.loc12
31+
// CHECK:STDOUT: %.loc10_24.1: ref (i32, i32) = stub_reference %v
32+
// CHECK:STDOUT: %.loc10_24.2: init (i32, i32) = call @F() to %.loc10_24.1
33+
// CHECK:STDOUT: assign %v, %.loc10_24.2
34+
// CHECK:STDOUT: %.loc11_8.1: ref (i32, i32) = stub_reference %v
35+
// CHECK:STDOUT: %.loc11_8.2: init (i32, i32) = call @F() to %.loc11_8.1
36+
// CHECK:STDOUT: assign %v, %.loc11_8.2
37+
// CHECK:STDOUT: %.loc12_11.1: ref (i32, i32) = stub_reference %return
38+
// CHECK:STDOUT: %.loc12_11.2: init (i32, i32) = call @F() to %.loc12_11.1
39+
// CHECK:STDOUT: return %.loc12_11.2
4040
// CHECK:STDOUT: }
4141
// CHECK:STDOUT:
4242
// CHECK:STDOUT: fn @H() -> i32 {

0 commit comments

Comments
 (0)