diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index db4c6e60d2f4..5b526edd6d41 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -24,6 +24,7 @@ #include "toolchain/check/member_access.h" #include "toolchain/check/operator.h" #include "toolchain/check/pattern_match.h" +#include "toolchain/check/pending_block.h" #include "toolchain/check/type.h" #include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/emitter.h" @@ -2048,16 +2049,24 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, return expr_id; } -auto Initialize(Context& context, SemIR::LocId loc_id, SemIR::InstId storage_id, - SemIR::InstId value_id, bool for_return) -> SemIR::InstId { +auto InitializeExisting(Context& context, SemIR::LocId loc_id, + SemIR::InstId storage_id, SemIR::InstId value_id, + bool for_return) -> SemIR::InstId { auto type_id = context.insts().Get(storage_id).type_id(); if (for_return && !SemIR::InitRepr::ForType(context.sem_ir(), type_id).MightBeInPlace()) { - // TODO: is it safe to use storage_id when the init repr is dependent? + // TODO: Is it safe to use storage_id when the init repr is dependent? storage_id = SemIR::InstId::None; } - // TODO: add CHECK that storage_id.index < value_id.index to enforce the - // precondition, once existing violations have been cleaned up. + + // TODO: This is only an approximation of a dominance check. Add a general + // end-of-phase dominance check and remove the check here and the one in + // `MergeReplacing`. + CARBON_CHECK(!storage_id.has_value() || + value_id == SemIR::ErrorInst::InstId || + context.insts().GetRawIndex(storage_id) <= + context.insts().GetRawIndex(value_id), + "Storage might not dominate initializer"); PendingBlock target_block(&context); return Convert(context, loc_id, value_id, {.kind = ConversionTarget::Initializing, @@ -2066,6 +2075,36 @@ auto Initialize(Context& context, SemIR::LocId loc_id, SemIR::InstId storage_id, .storage_access_block = &target_block}); } +auto Initialize(Context& context, SemIR::LocId loc_id, + SemIR::InstId&& storage_id, PendingBlock&& storage_access_block, + SemIR::InstId value_id) -> InitializeResult { + CARBON_CHECK(storage_id.has_value()); + auto type_id = context.insts().Get(storage_id).type_id(); + auto result_id = Convert(context, loc_id, value_id, + {.kind = ConversionTarget::Initializing, + .type_id = type_id, + .storage_id = storage_id, + .storage_access_block = &storage_access_block}); + + // Insert the storage block now, in case it wasn't used by the initializer. + storage_access_block.InsertHere(); + if (result_id == SemIR::ErrorInst::InstId) { + return {.storage_id = SemIR::ErrorInst::InstId, + .init_id = SemIR::ErrorInst::InstId}; + } + + // Find the storage argument. If the storage block was spliced or written over + // an existing storage argument by `Convert`, the resulting expression will + // have a storage argument that points to the possibly-rewritten storage + // instruction, and we can use that. Otherwise, the storage access block will + // have been inserted above, and we can use `storage_id` unchanged. + auto storage_arg_id = + SemIR::FindStorageArgForInitializer(context.sem_ir(), result_id); + return { + .storage_id = storage_arg_id.has_value() ? storage_arg_id : storage_id, + .init_id = result_id}; +} + auto ConvertToValueExpr(Context& context, SemIR::InstId expr_id) -> SemIR::InstId { return Convert(context, SemIR::LocId(expr_id), expr_id, diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 2bd3e817f718..c5721f7f4bd0 100644 --- a/toolchain/check/convert.h +++ b/toolchain/check/convert.h @@ -55,7 +55,7 @@ struct ConversionTarget { // be present if the initializing representation might be in-place. Initializing, // Convert to an in-place initializing expression whose storage is - // designated by `storage_id` (which must not be `None`) + // designated by `storage_id` (which must not be `None`). InPlaceInitializing, Last = InPlaceInitializing }; @@ -63,11 +63,14 @@ struct ConversionTarget { Kind kind; // The target type for the conversion. SemIR::TypeId type_id; - // The storage being initialized, if any. + // The storage being initialized, if any. It must be valid to reference this + // instruction after splicing in `storage_access_block` (if specified), so it + // must either dominate the initializer or be one of the instructions in + // `storage_access_block`. SemIR::InstId storage_id = SemIR::InstId::None; // For an initializer, a block of pending instructions that `storage_id` - // depends on, and that can be discarded if `storage_id` is not accessed. - // If this is not null or empty, its last element must be storage_id. + // depends on. This block will be spliced or merged before any reference to + // `storage_id`, and may be discarded if `storage_id` is not accessed. PendingBlock* storage_access_block = nullptr; // Whether failure of conversion is an error and is diagnosed to the user. // When looking for a possible conversion but with graceful fallback, @@ -89,12 +92,17 @@ struct ConversionTarget { auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, ConversionTarget target) -> SemIR::InstId; -// Converts `value_id` to an initializing expression of the type of -// `storage_id`, and returns the possibly-converted initializing expression. +// Performs initialization of `storage_id` from the expression `value_id`, which +// is converted to an initializing expression of the type of `storage_id` if +// necessary, and returns the possibly-converted initializing expression. +// // `storage_id` is used as the storage argument of the resulting expression -// except as noted below, and when it is used as the storage argument it must -// precede `value_id`. The caller is responsible for passing the result to an -// inst that is documented as consuming it, such as `Assign`. +// except as noted below. As a consequence, `storage_id` must dominate +// `value_id` and its subexpressions. This will typically only be the case if +// `storage_id` syntactically precedes `value_id`. Otherwise, some action will +// need to be taken to reorder the code, such as instead calling `Initialize` +// with a pending block containing `storage_id`, or creating a separate +// `InstBlock` to hold either the storage or the initializer. // // `for_return` indicates that this conversion is initializing the operand of a // `return` statement. This means that `storage_id` will be the return slot @@ -102,11 +110,58 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, // representation is not in-place, so in that case `storage_id` will be used // solely for its type. // -// TODO: Consider making the target type a separate parameter, and making -// storage_id optional. -auto Initialize(Context& context, SemIR::LocId loc_id, SemIR::InstId storage_id, - SemIR::InstId value_id, bool for_return = false) - -> SemIR::InstId; +// This function does not guarantee to perform an in-place initialization, so +// the caller is responsible for passing the returned `InstId` to an inst that +// is documented as consuming it, such as `Assign`. +auto InitializeExisting(Context& context, SemIR::LocId loc_id, + SemIR::InstId storage_id, SemIR::InstId value_id, + bool for_return = false) -> SemIR::InstId; + +// Result of Initialize. +struct InitializeResult { + // The storage location that contains the initialized value. This may be + // different from the `storage_id` that was passed to `Initialize` if the + // storage block was written over existing instructions rather than being + // spliced in. + SemIR::InstId storage_id; + // The converted initializing expression used to initialize the storage. + SemIR::InstId init_id; +}; + +// Performs initialization of `storage_id` from the expression `value_id`, which +// is converted to an initializing expression of the type of `storage_id` if +// necessary. `storage_access_block` should be used to supply a pending block +// that allocates the storage, and typically contains `storage_id`. The target +// of the initialization will be either `storage_id` itself, or an existing +// storage argument instruction that is overwritten to hold a copy of +// `storage_id` as an optimization for SemIR compactness. +// +// The storage instruction will only be written over an existing instruction if +// it is the sole instruction in the pending block. This is expected to be a +// common case. After this happens, the copy of the instruction in the pending +// block is expected to be unreachable from the SemIR::File. As a result, the +// `storage_id` instruction should not be referenced again after calling this +// function, and this function takes it by rvalue reference to remind the caller +// of this. +// +// If the overwrite optimization is not performed, `storage_access_block` will +// be inserted before any use of the storage by the initializer, and will be +// inserted even if the initializer does not actually use the storage. It must +// be valid to reference `storage_id` after splicing in `storage_access_block`, +// so `storage_id` must either dominate the initializer (but see the TODO below) +// or be one of the instructions in `storage_access_block`. If `storage_id` is +// known to always dominate the initializer, `InitializeExisting` should be used +// instead. +// +// TODO: We don't have an implementation of a proper dominance check, so we +// fake one up by comparing the order in which the insts were created. +// +// This function does not guarantee to perform an in-place initialization, so +// the caller is responsible for passing the returned `inst_id` to an inst that +// is documented as consuming it, such as `Assign`. +auto Initialize(Context& context, SemIR::LocId loc_id, + SemIR::InstId&& storage_id, PendingBlock&& storage_access_block, + SemIR::InstId value_id) -> InitializeResult; // Convert the given expression to a value expression of the same type. auto ConvertToValueExpr(Context& context, SemIR::InstId expr_id) diff --git a/toolchain/check/handle_loop_statement.cpp b/toolchain/check/handle_loop_statement.cpp index e536d9cacbe7..76eb5e74a849 100644 --- a/toolchain/check/handle_loop_statement.cpp +++ b/toolchain/check/handle_loop_statement.cpp @@ -16,6 +16,7 @@ #include "toolchain/check/pattern_match.h" #include "toolchain/check/type.h" #include "toolchain/sem_ir/absolute_node_ref.h" +#include "toolchain/sem_ir/expr_info.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { @@ -169,12 +170,19 @@ auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool { .op_name = CoreIdentifier::NewCursor}, range_id); auto cursor_type_id = context.insts().Get(cursor_id).type_id(); - auto cursor_var_id = AddInstWithCleanup( - context, node_id, + PendingBlock cursor_var_block(&context); + auto cursor_var_id = cursor_var_block.AddInstWithCleanup( + node_id, {.type_id = cursor_type_id, .pattern_id = SemIR::AbsoluteInstId::None}); - auto init_id = Initialize(context, node_id, cursor_var_id, cursor_id); - AddInst(context, node_id, - {.lhs_id = cursor_var_id, .rhs_id = init_id}); + // Disable broken lint that suggests a "fix" that doesn't compile. + auto init_result = Initialize(context, node_id, + // NOLINTNEXTLINE(performance-move-const-arg) + std::move(cursor_var_id), + std::move(cursor_var_block), cursor_id); + AddInst( + context, node_id, + {.lhs_id = init_result.storage_id, .rhs_id = init_result.init_id}); + cursor_var_id = init_result.storage_id; // Start emitting the loop header block. auto loop_header_id = StartLoopHeader(context, start_node_id); diff --git a/toolchain/check/handle_operator.cpp b/toolchain/check/handle_operator.cpp index a47b16a484b7..98711d2e7c0a 100644 --- a/toolchain/check/handle_operator.cpp +++ b/toolchain/check/handle_operator.cpp @@ -113,7 +113,7 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorEqualId node_id) } // TODO: Destroy the old value before reinitializing. This will require // building the destruction code before we build the RHS subexpression. - rhs_id = Initialize(context, node_id, lhs_id, rhs_id); + rhs_id = InitializeExisting(context, node_id, lhs_id, rhs_id); AddInst(context, node_id, {.lhs_id = lhs_id, .rhs_id = rhs_id}); // We model assignment as an expression, so we need to push a value for diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index a22b536357a6..bf575813517a 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -634,7 +634,6 @@ auto MatchContext::DoPreWork(State state, SemIR::VarPattern var_pattern, auto MatchContext::DoVarPreWorkImpl(State state, SemIR::TypeId pattern_type_id, SemIR::InstId scrutinee_id, WorkItem entry) const -> SemIR::InstId { - auto storage_id = SemIR::InstId::None; CARBON_KIND_SWITCH(state) { case CARBON_KIND(CalleeState* _): { // We're emitting pattern-match IR for the callee, but we're still on @@ -646,52 +645,56 @@ auto MatchContext::DoVarPreWorkImpl(State state, SemIR::TypeId pattern_type_id, return scrutinee_id; } case CARBON_KIND(LocalState* _): { + // TODO: Find a more efficient way to put these insts in the global_init + // block (or drop the distinction between the global_init block and the + // file scope?) + if (context_.scope_stack().PeekIndex() == ScopeIndex::Package) { + context_.global_init().Resume(); + } + // In a `var`/`let` declaration, the `VarStorage` inst is created before // we start pattern matching. auto lookup_result = context_.var_storage_map().Lookup(entry.pattern_id); CARBON_CHECK(lookup_result); - storage_id = lookup_result.value(); - break; + auto storage_id = lookup_result.value(); + if (scrutinee_id.has_value()) { + auto init_id = + InitializeExisting(context_, SemIR::LocId(entry.pattern_id), + storage_id, scrutinee_id, /*for_return=*/false); + // TODO: It's a bit weird to use an `Assign` instruction to model + // initialization. Consider adding a different instruction for this + // purpose. + AddInst(context_, SemIR::LocId(entry.pattern_id), + {.lhs_id = storage_id, .rhs_id = init_id}); + } + + if (context_.scope_stack().PeekIndex() == ScopeIndex::Package) { + context_.global_init().Suspend(); + } + return storage_id; } case CARBON_KIND(CallerState* _): { - storage_id = AddInst( + // TODO: This variable's lifetime should end at the end of the call or the + // full-expression. We don't use a temporary here, because temporaries are + // treated as being immutable. + PendingBlock storage_block(&context_); + auto storage_id = storage_block.AddInstWithCleanup( + SemIR::LocId(entry.pattern_id), + {.type_id = pattern_type_id, .pattern_id = entry.pattern_id}); + // Disable broken lint that suggests a "fix" that doesn't compile. + auto init_result = Initialize( context_, SemIR::LocId(entry.pattern_id), - {.type_id = pattern_type_id}); - CARBON_CHECK(scrutinee_id.has_value()); - break; + // NOLINTNEXTLINE(performance-move-const-arg) + std::move(storage_id), std::move(storage_block), scrutinee_id); + // TODO: Consider instead creating something like a `Temporary` + // instruction that returns a reference so that constant evaluation can + // obtain the value of the var parameter. + AddInst( + context_, SemIR::LocId(entry.pattern_id), + {.lhs_id = init_result.storage_id, .rhs_id = init_result.init_id}); + return init_result.storage_id; } } - // TODO: Find a more efficient way to put these insts in the global_init - // block (or drop the distinction between the global_init block and the - // file scope?) - if (context_.scope_stack().PeekIndex() == ScopeIndex::Package) { - context_.global_init().Resume(); - } - if (scrutinee_id.has_value()) { - auto init_id = Initialize(context_, SemIR::LocId(entry.pattern_id), - storage_id, scrutinee_id); - // If we created a `TemporaryStorage` to hold the var, create a - // corresponding `Temporary` to model that its initialization is complete. - // TODO: If the subpattern is a binding, we may want to destroy the - // parameter variable in the callee instead of the caller so that we can - // support destructive move from it. - if (std::holds_alternative(state)) { - storage_id = AddInstWithCleanup( - context_, SemIR::LocId(entry.pattern_id), - {.type_id = context_.insts().Get(storage_id).type_id(), - .storage_id = storage_id, - .init_id = init_id}); - } else { - // TODO: Consider using different instruction kinds for assignment - // versus initialization. - AddInst(context_, SemIR::LocId(entry.pattern_id), - {.lhs_id = storage_id, .rhs_id = init_id}); - } - } - if (context_.scope_stack().PeekIndex() == ScopeIndex::Package) { - context_.global_init().Suspend(); - } - return storage_id; } auto MatchContext::DoPostWork(State /*state*/, diff --git a/toolchain/check/pending_block.h b/toolchain/check/pending_block.h index 643e67dd649a..a77471bc880e 100644 --- a/toolchain/check/pending_block.h +++ b/toolchain/check/pending_block.h @@ -69,22 +69,21 @@ class PendingBlock { // Replace the instruction at target_id with the instructions in this block. // The new value for target_id should be value_id. Returns the InstId that - // should be used to refer to the result from now on. value_id must precede - // target_id, or be the last ID in this block, in order to preserve the - // property that SemIR is topologically sorted. + // should be used to refer to the result from now on. value_id must dominate + // target_id (but see below), or refer to an instruction within this block, in + // order to preserve the property that SemIR is topologically sorted. // - // TODO: we could also allow value_id to be one of the other insts in this - // block, but that would be costlier to enforce. + // TODO: We don't have an implementation of a proper dominance check, so we + // fake one up by comparing the order in which the insts were created. + // Add a general end-of-phase dominance check and remove the one here and in + // `InitializeExisting`. auto MergeReplacing(SemIR::InstId target_id, SemIR::InstId value_id) -> SemIR::InstId { CARBON_CHECK(target_id != value_id); - - // TODO: consider adding an end-of-phase check that the SemIR::File is in - // SSA form, and dropping this check and the ordering preconditions here and - // on Initialize. - CARBON_CHECK(value_id.index <= target_id.index || - (!insts_.empty() && insts_.back() == value_id), - "Splice would break topological sorting of insts"); + CARBON_CHECK(context_->insts().GetRawIndex(value_id) <= + context_->insts().GetRawIndex(target_id) || + llvm::is_contained(insts_, value_id), + "Splice might break dominance condition"); SemIR::LocIdAndInst value = context_->insts().GetWithLocId(value_id); auto result_id = value_id; diff --git a/toolchain/check/return.cpp b/toolchain/check/return.cpp index 13dac0a7a0b8..2492278ab798 100644 --- a/toolchain/check/return.cpp +++ b/toolchain/check/return.cpp @@ -191,8 +191,8 @@ auto BuildReturnWithExpr(Context& context, SemIR::LocId loc_id, out_param_id = call_params[function.call_param_ranges.return_begin().index]; CARBON_CHECK(out_param_id.has_value()); - expr_id = Initialize(context, loc_id, out_param_id, expr_id, - /*for_return=*/true); + expr_id = InitializeExisting(context, loc_id, out_param_id, expr_id, + /*for_return=*/true); if (!SemIR::InitRepr::ForType(context.sem_ir(), return_type_id) .MightBeInPlace()) { out_param_id = SemIR::InstId::None; diff --git a/toolchain/check/testdata/function/call/form.carbon b/toolchain/check/testdata/function/call/form.carbon index e3bc1193d8be..b298fb1032b5 100644 --- a/toolchain/check/testdata/function/call/form.carbon +++ b/toolchain/check/testdata/function/call/form.carbon @@ -286,10 +286,8 @@ fn F(Form:! Core.Form()) ->? Form; // CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] // CHECK:STDOUT: %bound_method: = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] // CHECK:STDOUT: %int_0.6a9: %i32 = int_value 0 [concrete] -// CHECK:STDOUT: %.eea: ref %i32 = temporary invalid, %int_0.6a9 [concrete] // CHECK:STDOUT: %Destroy.Op.type.bae255.2: type = fn_type @Destroy.Op.loc4_7.2 [concrete] // CHECK:STDOUT: %Destroy.Op.651ba6.2: %Destroy.Op.type.bae255.2 = struct_value () [concrete] -// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %.eea, %Destroy.Op.651ba6.2 [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -317,16 +315,17 @@ fn F(Form:! Core.Form()) ->? Form; // CHECK:STDOUT: !entry: // CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] // CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6] -// CHECK:STDOUT: %.loc4_7.1: ref %i32 = temporary_storage // CHECK:STDOUT: %impl.elem0: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5] // CHECK:STDOUT: %bound_method.loc4_7.1: = bound_method %int_0, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound] // CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] // CHECK:STDOUT: %bound_method.loc4_7.2: = bound_method %int_0, %specific_fn [concrete = constants.%bound_method] // CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc4_7.2(%int_0) [concrete = constants.%int_0.6a9] -// CHECK:STDOUT: %.loc4_7.2: init %i32 = converted %int_0, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9] -// CHECK:STDOUT: %.loc4_7.3: ref %i32 = temporary %.loc4_7.1, %.loc4_7.2 [concrete = constants.%.eea] -// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc4_7.3) -// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call constants.%Destroy.Op.bound(constants.%.eea) +// CHECK:STDOUT: %.loc4: init %i32 = converted %int_0, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9] +// CHECK:STDOUT: %x.var: ref %i32 = var @F.%.loc4_7 +// CHECK:STDOUT: assign %x.var, %.loc4 +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%x.var) +// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %x.var, constants.%Destroy.Op.651ba6.2 +// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(%x.var) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/generic/var_param.carbon b/toolchain/check/testdata/generic/var_param.carbon index 7273e4b07099..183a1312db1a 100644 --- a/toolchain/check/testdata/generic/var_param.carbon +++ b/toolchain/check/testdata/generic/var_param.carbon @@ -110,16 +110,16 @@ fn Test(x: i32) { // CHECK:STDOUT: %I.facet: %I.type = facet_value constants.%i32, (constants.%I.impl_witness) [concrete = constants.%I.facet] // CHECK:STDOUT: %.loc27: %I.type = converted constants.%i32, %I.facet [concrete = constants.%I.facet] // CHECK:STDOUT: %TestOp.specific_fn: = specific_function %TestOp.ref, @TestOp(constants.%I.facet) [concrete = constants.%TestOp.specific_fn] -// CHECK:STDOUT: %.loc22_25.1: ref %i32 = temporary_storage // CHECK:STDOUT: %impl.elem0: %.8e2 = impl_witness_access constants.%Copy.impl_witness.f17, element0 [concrete = constants.%Int.as.Copy.impl.Op.664] // CHECK:STDOUT: %bound_method.loc27_10.1: = bound_method %x.ref, %impl.elem0 // CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Int.as.Copy.impl.Op(constants.%int_32) [concrete = constants.%Int.as.Copy.impl.Op.specific_fn] // CHECK:STDOUT: %bound_method.loc27_10.2: = bound_method %x.ref, %specific_fn // CHECK:STDOUT: %Int.as.Copy.impl.Op.call: init %i32 = call %bound_method.loc27_10.2(%x.ref) -// CHECK:STDOUT: %.loc22_25.2: ref %i32 = temporary %.loc22_25.1, %Int.as.Copy.impl.Op.call -// CHECK:STDOUT: %TestOp.call: init %empty_tuple.type = call %TestOp.specific_fn(%.loc22_25.2) -// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %.loc22_25.2, constants.%Destroy.Op.651ba6.2 -// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(%.loc22_25.2) +// CHECK:STDOUT: %x.var: ref %i32 = var @TestOp.%x.param_patt +// CHECK:STDOUT: assign %x.var, %Int.as.Copy.impl.Op.call +// CHECK:STDOUT: %TestOp.call: init %empty_tuple.type = call %TestOp.specific_fn(%x.var) +// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %x.var, constants.%Destroy.Op.651ba6.2 +// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(%x.var) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/var/var_pattern.carbon b/toolchain/check/testdata/var/var_pattern.carbon index 63328eecff6a..b6cb531452d4 100644 --- a/toolchain/check/testdata/var/var_pattern.carbon +++ b/toolchain/check/testdata/var/var_pattern.carbon @@ -2,7 +2,7 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon // TODO: Add ranges and switch to "--dump-sem-ir-ranges=only". // EXTRA-ARGS: --dump-sem-ir-ranges=if-present // @@ -145,6 +145,38 @@ fn G() { let (unused x: (), var unused y: (), unused z: ()) = (F(), F(), F()); } +// --- var_param_from_init.carbon + +library "[[@TEST_NAME]]"; + +class X {} + +//@dump-sem-ir-begin +fn TakeVar(var a: X); +//@dump-sem-ir-end + +fn MakeInit() -> X; + +fn PassInitToVar() { + //@dump-sem-ir-begin + TakeVar(MakeInit()); + //@dump-sem-ir-end +} + +// --- var_param_from_int_literal.carbon + +library "[[@TEST_NAME]]"; + +//@dump-sem-ir-begin +fn TakeVar(var n: i32); +//@dump-sem-ir-end + +fn Call() { + //@dump-sem-ir-begin + TakeVar(123); + //@dump-sem-ir-end +} + // CHECK:STDOUT: --- basic.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { @@ -439,7 +471,6 @@ fn G() { // CHECK:STDOUT: %F: %F.type = struct_value () [concrete] // CHECK:STDOUT: %G.type: type = fn_type @G [concrete] // CHECK:STDOUT: %G: %G.type = struct_value () [concrete] -// CHECK:STDOUT: %.a4d: ref %empty_tuple.type = temporary invalid, %empty_tuple [concrete] // CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete] // CHECK:STDOUT: %Destroy.Op.type: type = fn_type @Destroy.Op [concrete] // CHECK:STDOUT: %Destroy.Op: %Destroy.Op.type = struct_value () [concrete] @@ -506,13 +537,15 @@ fn G() { // CHECK:STDOUT: %.loc8_9.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] // CHECK:STDOUT: %tuple: %empty_tuple.type = tuple_value () [concrete = constants.%empty_tuple] // CHECK:STDOUT: %.loc8_5: %empty_tuple.type = converted %v.ref, %tuple [concrete = constants.%empty_tuple] -// CHECK:STDOUT: %.loc4_13.1: ref %empty_tuple.type = temporary_storage +// CHECK:STDOUT: %y.var: ref %empty_tuple.type = var @F.%y.param_patt // CHECK:STDOUT: %.loc8_9.2: init %empty_tuple.type = tuple_init () [concrete = constants.%empty_tuple] -// CHECK:STDOUT: %.loc4_13.2: init %empty_tuple.type = converted %.loc8_9.1, %.loc8_9.2 [concrete = constants.%empty_tuple] -// CHECK:STDOUT: %.loc4_13.3: ref %empty_tuple.type = temporary %.loc4_13.1, %.loc4_13.2 [concrete = constants.%.a4d] -// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc8_5, %.loc4_13.3) -// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %v.var, constants.%Destroy.Op -// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(%v.var) +// CHECK:STDOUT: %.loc4: init %empty_tuple.type = converted %.loc8_9.1, %.loc8_9.2 [concrete = constants.%empty_tuple] +// CHECK:STDOUT: assign %y.var, %.loc4 +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc8_5, %y.var) +// CHECK:STDOUT: %Destroy.Op.bound.loc4: = bound_method %y.var, constants.%Destroy.Op +// CHECK:STDOUT: %Destroy.Op.call.loc4: init %empty_tuple.type = call %Destroy.Op.bound.loc4(%y.var) +// CHECK:STDOUT: %Destroy.Op.bound.loc7: = bound_method %v.var, constants.%Destroy.Op +// CHECK:STDOUT: %Destroy.Op.call.loc7: init %empty_tuple.type = call %Destroy.Op.bound.loc7(%v.var) // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -623,8 +656,8 @@ fn G() { // CHECK:STDOUT: %G: %G.type = struct_value () [concrete] // CHECK:STDOUT: %F.type: type = fn_type @F [concrete] // CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %pattern_type.cb1: type = pattern_type %empty_tuple.type [concrete] // CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete] -// CHECK:STDOUT: %.a4d: ref %empty_tuple.type = temporary invalid, %empty_tuple [concrete] // CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete] // CHECK:STDOUT: %Self: %Destroy.type = symbolic_binding Self, 0 [symbolic] // CHECK:STDOUT: %Destroy.WithSelf.Op.type.cb2: type = fn_type @Destroy.WithSelf.Op, @Destroy.WithSelf(%Self) [symbolic] @@ -637,7 +670,6 @@ fn G() { // CHECK:STDOUT: %Destroy.facet.5ae: %Destroy.type = facet_value %empty_tuple.type, (%custom_witness.8d7) [concrete] // CHECK:STDOUT: %Destroy.WithSelf.Op.type.89a: type = fn_type @Destroy.WithSelf.Op, @Destroy.WithSelf(%Destroy.facet.5ae) [concrete] // CHECK:STDOUT: %.518: type = fn_type_with_self_type %Destroy.WithSelf.Op.type.89a, %Destroy.facet.5ae [concrete] -// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %.a4d, %Destroy.Op [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -651,6 +683,8 @@ fn G() { // CHECK:STDOUT: import P//default // CHECK:STDOUT: } // CHECK:STDOUT: %P.F: %F.type = import_ref P//default, F, loaded [concrete = constants.%F] +// CHECK:STDOUT: %y.patt: %pattern_type.cb1 = ref_binding_pattern y [concrete] +// CHECK:STDOUT: %y.param_patt: %pattern_type.cb1 = var_param_pattern %y.patt [concrete] // CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type] // CHECK:STDOUT: %Core.import_ref.06b: %Destroy.assoc_type = import_ref Core//prelude/parts/destroy, loc{{\d+_\d+}}, loaded [concrete = constants.%assoc0] // CHECK:STDOUT: %Core.import_ref.8bb: @Destroy.WithSelf.%Destroy.WithSelf.Op.type (%Destroy.WithSelf.Op.type.cb2) = import_ref Core//prelude/parts/destroy, loc{{\d+_\d+}}, loaded [symbolic = @Destroy.WithSelf.%Destroy.WithSelf.Op (constants.%Destroy.WithSelf.Op.9f1)] @@ -675,15 +709,15 @@ fn G() { // CHECK:STDOUT: %.loc7_12.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] // CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete = constants.%empty_tuple] // CHECK:STDOUT: %.loc7_8.2: %empty_tuple.type = converted %.loc7_8.1, %empty_tuple [concrete = constants.%empty_tuple] -// CHECK:STDOUT: %.1: ref %empty_tuple.type = temporary_storage +// CHECK:STDOUT: %y.var: ref %empty_tuple.type = var imports.%y.param_patt // CHECK:STDOUT: %.loc7_12.2: init %empty_tuple.type = tuple_init () [concrete = constants.%empty_tuple] -// CHECK:STDOUT: %.2: init %empty_tuple.type = converted %.loc7_12.1, %.loc7_12.2 [concrete = constants.%empty_tuple] -// CHECK:STDOUT: %.3: ref %empty_tuple.type = temporary %.1, %.2 [concrete = constants.%.a4d] -// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc7_8.2, %.3) +// CHECK:STDOUT: %.1: init %empty_tuple.type = converted %.loc7_12.1, %.loc7_12.2 [concrete = constants.%empty_tuple] +// CHECK:STDOUT: assign %y.var, %.1 +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc7_8.2, %y.var) // CHECK:STDOUT: %Op.ref: %Destroy.assoc_type = name_ref Op, imports.%Core.import_ref.06b [concrete = constants.%assoc0] // CHECK:STDOUT: %impl.elem0: %.518 = impl_witness_access constants.%custom_witness.8d7, element0 [concrete = constants.%Destroy.Op] -// CHECK:STDOUT: %bound_method: = bound_method %.3, %impl.elem0 [concrete = constants.%Destroy.Op.bound] -// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %bound_method(%.3) [concrete = constants.%empty_tuple] +// CHECK:STDOUT: %bound_method: = bound_method %y.var, %impl.elem0 +// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %bound_method(%y.var) // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -950,3 +984,123 @@ fn G() { // CHECK:STDOUT: // CHECK:STDOUT: fn @Destroy.Op(%self.param: ref %empty_tuple.type) = "no_op"; // CHECK:STDOUT: +// CHECK:STDOUT: --- var_param_from_init.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %X: type = class_type @X [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %pattern_type.05f: type = pattern_type %X [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %TakeVar.type: type = fn_type @TakeVar [concrete] +// CHECK:STDOUT: %TakeVar: %TakeVar.type = struct_value () [concrete] +// CHECK:STDOUT: %MakeInit.type: type = fn_type @MakeInit [concrete] +// CHECK:STDOUT: %MakeInit: %MakeInit.type = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.Op.type.bae255.2: type = fn_type @Destroy.Op.loc7_12.2 [concrete] +// CHECK:STDOUT: %Destroy.Op.651ba6.2: %Destroy.Op.type.bae255.2 = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %TakeVar.decl: %TakeVar.type = fn_decl @TakeVar [concrete = constants.%TakeVar] { +// CHECK:STDOUT: %a.patt: %pattern_type.05f = ref_binding_pattern a [concrete] +// CHECK:STDOUT: %a.param_patt: %pattern_type.05f = var_param_pattern %a.patt [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %a.param: ref %X = ref_param call_param0 +// CHECK:STDOUT: %X.ref: type = name_ref X, file.%X.decl [concrete = constants.%X] +// CHECK:STDOUT: %a: ref %X = ref_binding a, %a.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @TakeVar(%a.param: ref %X); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @PassInitToVar() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %TakeVar.ref: %TakeVar.type = name_ref TakeVar, file.%TakeVar.decl [concrete = constants.%TakeVar] +// CHECK:STDOUT: %MakeInit.ref: %MakeInit.type = name_ref MakeInit, file.%MakeInit.decl [concrete = constants.%MakeInit] +// CHECK:STDOUT: %a.var: ref %X = var @TakeVar.%a.param_patt +// CHECK:STDOUT: %MakeInit.call: init %X to %a.var = call %MakeInit.ref() +// CHECK:STDOUT: assign %a.var, %MakeInit.call +// CHECK:STDOUT: %TakeVar.call: init %empty_tuple.type = call %TakeVar.ref(%a.var) +// CHECK:STDOUT: %Destroy.Op.bound: = bound_method .inst{{[0-9A-F]+}}.loc7_12, constants.%Destroy.Op.651ba6.2 +// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(.inst{{[0-9A-F]+}}.loc7_12) +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.Op.loc7_12.1(%self.param: ref %empty_struct_type) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.Op.loc7_12.2(%self.param: ref %X) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- var_param_from_int_literal.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %pattern_type.7ce: type = pattern_type %i32 [concrete] +// CHECK:STDOUT: %i32.builtin: type = int_type signed, %int_32 [concrete] +// CHECK:STDOUT: %TakeVar.type: type = fn_type @TakeVar [concrete] +// CHECK:STDOUT: %TakeVar: %TakeVar.type = struct_value () [concrete] +// CHECK:STDOUT: %int_123.fff: Core.IntLiteral = int_value 123 [concrete] +// CHECK:STDOUT: %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete] +// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.3c2: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6 = struct_value () [symbolic] +// CHECK:STDOUT: %ImplicitAs.impl_witness.6bc: = impl_witness imports.%ImplicitAs.impl_witness_table.74f, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e0d: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e0d = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.e8c = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.6bc) [concrete] +// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.b37: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%i32, %ImplicitAs.facet) [concrete] +// CHECK:STDOUT: %.545: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.b37, %ImplicitAs.facet [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: = bound_method %int_123.fff, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] +// CHECK:STDOUT: %bound_method: = bound_method %int_123.fff, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] +// CHECK:STDOUT: %int_123.f7f: %i32 = int_value 123 [concrete] +// CHECK:STDOUT: %Destroy.Op.type.bae255.2: type = fn_type @Destroy.Op.loc5_12.2 [concrete] +// CHECK:STDOUT: %Destroy.Op.651ba6.2: %Destroy.Op.type.bae255.2 = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core.import_ref.42d: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.3c2)] +// CHECK:STDOUT: %ImplicitAs.impl_witness_table.74f = impl_witness_table (%Core.import_ref.42d), @Core.IntLiteral.as.ImplicitAs.impl [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %TakeVar.decl: %TakeVar.type = fn_decl @TakeVar [concrete = constants.%TakeVar] { +// CHECK:STDOUT: %n.patt: %pattern_type.7ce = ref_binding_pattern n [concrete] +// CHECK:STDOUT: %n.param_patt: %pattern_type.7ce = var_param_pattern %n.patt [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %n.param: ref %i32 = ref_param call_param0 +// CHECK:STDOUT: %i32: type = type_literal constants.%i32 [concrete = constants.%i32] +// CHECK:STDOUT: %n: ref %i32 = ref_binding n, %n.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @TakeVar(%n.param: ref %i32); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Call() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %TakeVar.ref: %TakeVar.type = name_ref TakeVar, file.%TakeVar.decl [concrete = constants.%TakeVar] +// CHECK:STDOUT: %int_123: Core.IntLiteral = int_value 123 [concrete = constants.%int_123.fff] +// CHECK:STDOUT: %impl.elem0: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5] +// CHECK:STDOUT: %bound_method.loc5_12.1: = bound_method %int_123, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound] +// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc5_12.2: = bound_method %int_123, %specific_fn [concrete = constants.%bound_method] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc5_12.2(%int_123) [concrete = constants.%int_123.f7f] +// CHECK:STDOUT: %.loc5: init %i32 = converted %int_123, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_123.f7f] +// CHECK:STDOUT: %n.var: ref %i32 = var @TakeVar.%n.param_patt +// CHECK:STDOUT: assign %n.var, %.loc5 +// CHECK:STDOUT: %TakeVar.call: init %empty_tuple.type = call %TakeVar.ref(%n.var) +// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %n.var, constants.%Destroy.Op.651ba6.2 +// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(%n.var) +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.Op.loc5_12.1(%self.param: ref %i32.builtin) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.Op.loc5_12.2(%self.param: ref %i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 6c6cf0e17421..7c3760a37d2c 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -435,8 +435,8 @@ auto BuildThunkDefinitionForExport(Context& context, context.inst_blocks().Get(thunk_function.call_params_id).back(); SemIR::LocId loc_id(out_param_id); - auto init_id = - Initialize(context, loc_id, out_param_id, call_id, /*for_return=*/true); + auto init_id = InitializeExisting(context, loc_id, out_param_id, call_id, + /*for_return=*/true); AddInst(context, loc_id, SemIR::Assign{ .lhs_id = out_param_id, diff --git a/toolchain/lower/aggregate.cpp b/toolchain/lower/aggregate.cpp index 076a653ca351..2cbd416964c6 100644 --- a/toolchain/lower/aggregate.cpp +++ b/toolchain/lower/aggregate.cpp @@ -7,6 +7,7 @@ #include "llvm/ADT/STLExtras.h" #include "toolchain/sem_ir/expr_info.h" #include "toolchain/sem_ir/inst.h" +#include "toolchain/sem_ir/inst_kind.h" #include "toolchain/sem_ir/type_info.h" #include "toolchain/sem_ir/typed_insts.h" diff --git a/toolchain/lower/testdata/function/call/form.carbon b/toolchain/lower/testdata/function/call/form.carbon index 904db3ee0882..2eb01b933773 100644 --- a/toolchain/lower/testdata/function/call/form.carbon +++ b/toolchain/lower/testdata/function/call/form.carbon @@ -116,17 +116,16 @@ fn G() { // CHECK:STDOUT: ; ModuleID = 'var_form_param.carbon' // CHECK:STDOUT: source_filename = "var_form_param.carbon" // CHECK:STDOUT: -// CHECK:STDOUT: @int_0.6a9 = internal constant i32 0 -// CHECK:STDOUT: // CHECK:STDOUT: declare void @_CF.Main(ptr) // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: nounwind // CHECK:STDOUT: define void @_CG.Main() #0 !dbg !4 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: %.loc3_7.1.temp = alloca i32, align 4, !dbg !7 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc3_7.1.temp), !dbg !7 -// CHECK:STDOUT: call void @_CF.Main(ptr @int_0.6a9), !dbg !8 -// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr @int_0.6a9), !dbg !7 +// CHECK:STDOUT: %x.var = alloca i32, align 4, !dbg !7 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %x.var), !dbg !7 +// CHECK:STDOUT: store i32 0, ptr %x.var, align 4, !dbg !7 +// CHECK:STDOUT: call void @_CF.Main(ptr %x.var), !dbg !8 +// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr %x.var), !dbg !7 // CHECK:STDOUT: ret void, !dbg !9 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/lower/testdata/function/definition/destroy.carbon b/toolchain/lower/testdata/function/definition/destroy.carbon index 39708ad00330..6f810dcaff75 100644 --- a/toolchain/lower/testdata/function/definition/destroy.carbon +++ b/toolchain/lower/testdata/function/definition/destroy.carbon @@ -137,7 +137,7 @@ fn InitLet() { // CHECK:STDOUT: ; ModuleID = 'var_param.carbon' // CHECK:STDOUT: source_filename = "var_param.carbon" // CHECK:STDOUT: -// CHECK:STDOUT: @C.val = internal constant {} zeroinitializer +// CHECK:STDOUT: @C.val.loc6 = internal constant {} zeroinitializer // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: nounwind // CHECK:STDOUT: define void @_CF.Main(ptr %_) #0 !dbg !4 { @@ -150,11 +150,12 @@ fn InitLet() { // CHECK:STDOUT: ; Function Attrs: nounwind // CHECK:STDOUT: define void @_CCallF.Main() #0 !dbg !11 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: %.loc6_6.1.temp = alloca {}, align 8, !dbg !14 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc6_6.1.temp), !dbg !14 -// CHECK:STDOUT: call void @_CF.Main(ptr @C.val), !dbg !15 +// CHECK:STDOUT: %_.var = alloca {}, align 8, !dbg !14 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %_.var), !dbg !14 +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %_.var, ptr align 1 @C.val.loc6, i64 0, i1 false), !dbg !14 +// CHECK:STDOUT: call void @_CF.Main(ptr %_.var), !dbg !15 // CHECK:STDOUT: call void @_CG.Main(), !dbg !16 -// CHECK:STDOUT: call void @"_COp.71e25083d63c4d6c:core.Destroy.Core"(ptr @C.val), !dbg !14 +// CHECK:STDOUT: call void @"_COp.71e25083d63c4d6c:core.Destroy.Core"(ptr %_.var), !dbg !14 // CHECK:STDOUT: ret void, !dbg !17 // CHECK:STDOUT: } // CHECK:STDOUT: @@ -167,8 +168,12 @@ fn InitLet() { // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) // CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1 // CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #2 +// CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { nounwind } // CHECK:STDOUT: attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1} // CHECK:STDOUT: !llvm.dbg.cu = !{!2} diff --git a/toolchain/lower/testdata/function/definition/var_param.carbon b/toolchain/lower/testdata/function/definition/var_param.carbon index 5fe2d9fd1020..3275db0e5b85 100644 --- a/toolchain/lower/testdata/function/definition/var_param.carbon +++ b/toolchain/lower/testdata/function/definition/var_param.carbon @@ -31,9 +31,8 @@ fn Call() { // CHECK:STDOUT: ; ModuleID = 'var_param.carbon' // CHECK:STDOUT: source_filename = "var_param.carbon" // CHECK:STDOUT: -// CHECK:STDOUT: @int_1.5d2 = internal constant i32 1 // CHECK:STDOUT: @X.val = internal constant {} zeroinitializer -// CHECK:STDOUT: @X.val.loc27_18.6 = internal constant {} zeroinitializer +// CHECK:STDOUT: @X.val.loc16 = internal constant {} zeroinitializer // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: nounwind // CHECK:STDOUT: define void @_COneVar_i32.Main(ptr %_) #0 !dbg !4 { @@ -68,32 +67,38 @@ fn Call() { // CHECK:STDOUT: ; Function Attrs: nounwind // CHECK:STDOUT: define void @_CCall.Main() #0 !dbg !35 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: %.loc15_15.1.temp = alloca i32, align 4, !dbg !38 -// CHECK:STDOUT: %.loc16_13.1.temp = alloca {}, align 8, !dbg !39 -// CHECK:STDOUT: %.loc18_12.1.temp = alloca i32, align 4, !dbg !40 -// CHECK:STDOUT: %.loc18_24.1.temp = alloca {}, align 8, !dbg !41 -// CHECK:STDOUT: %.loc20_15.1.temp = alloca i32, align 4, !dbg !42 +// CHECK:STDOUT: %_.var.loc15 = alloca i32, align 4, !dbg !38 +// CHECK:STDOUT: %_.var.loc16 = alloca {}, align 8, !dbg !39 +// CHECK:STDOUT: %_.var.loc18_12 = alloca i32, align 4, !dbg !40 +// CHECK:STDOUT: %_.var.loc18_24 = alloca {}, align 8, !dbg !41 +// CHECK:STDOUT: %_.var.loc20 = alloca i32, align 4, !dbg !42 // CHECK:STDOUT: %.loc27_18.2.temp = alloca {}, align 8, !dbg !43 -// CHECK:STDOUT: %.loc21_23.1.temp = alloca {}, align 8, !dbg !44 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc15_15.1.temp), !dbg !38 -// CHECK:STDOUT: call void @_COneVar_i32.Main(ptr @int_1.5d2), !dbg !45 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc16_13.1.temp), !dbg !39 -// CHECK:STDOUT: call void @_COneVar_X.Main(ptr @X.val), !dbg !46 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc18_12.1.temp), !dbg !40 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc18_24.1.temp), !dbg !41 -// CHECK:STDOUT: call void @_CTwoVars.Main(ptr @int_1.5d2, ptr @X.val), !dbg !47 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc20_15.1.temp), !dbg !42 +// CHECK:STDOUT: %_.var.loc21 = alloca {}, align 8, !dbg !44 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %_.var.loc15), !dbg !38 +// CHECK:STDOUT: store i32 1, ptr %_.var.loc15, align 4, !dbg !38 +// CHECK:STDOUT: call void @_COneVar_i32.Main(ptr %_.var.loc15), !dbg !45 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %_.var.loc16), !dbg !39 +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %_.var.loc16, ptr align 1 @X.val.loc16, i64 0, i1 false), !dbg !39 +// CHECK:STDOUT: call void @_COneVar_X.Main(ptr %_.var.loc16), !dbg !46 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %_.var.loc18_12), !dbg !40 +// CHECK:STDOUT: store i32 1, ptr %_.var.loc18_12, align 4, !dbg !40 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %_.var.loc18_24), !dbg !41 +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %_.var.loc18_24, ptr align 1 @X.val.loc16, i64 0, i1 false), !dbg !41 +// CHECK:STDOUT: call void @_CTwoVars.Main(ptr %_.var.loc18_12, ptr %_.var.loc18_24), !dbg !47 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %_.var.loc20), !dbg !42 +// CHECK:STDOUT: store i32 1, ptr %_.var.loc20, align 4, !dbg !42 // CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc27_18.2.temp), !dbg !43 -// CHECK:STDOUT: call void @_CVarThenLet.Main(ptr @int_1.5d2, ptr @X.val.loc27_18.6), !dbg !48 -// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc21_23.1.temp), !dbg !44 -// CHECK:STDOUT: call void @_CLetThenVar.Main(i32 1, ptr @X.val), !dbg !49 -// CHECK:STDOUT: call void @"_COp.607f187990247af9:core.Destroy.Core"(ptr @X.val), !dbg !44 +// CHECK:STDOUT: call void @_CVarThenLet.Main(ptr %_.var.loc20, ptr @X.val.loc16), !dbg !48 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %_.var.loc21), !dbg !44 +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %_.var.loc21, ptr align 1 @X.val.loc16, i64 0, i1 false), !dbg !44 +// CHECK:STDOUT: call void @_CLetThenVar.Main(i32 1, ptr %_.var.loc21), !dbg !49 +// CHECK:STDOUT: call void @"_COp.607f187990247af9:core.Destroy.Core"(ptr %_.var.loc21), !dbg !44 // CHECK:STDOUT: call void @"_COp.607f187990247af9:core.Destroy.Core"(ptr @X.val), !dbg !43 -// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr @int_1.5d2), !dbg !42 -// CHECK:STDOUT: call void @"_COp.607f187990247af9:core.Destroy.Core"(ptr @X.val), !dbg !41 -// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr @int_1.5d2), !dbg !40 -// CHECK:STDOUT: call void @"_COp.607f187990247af9:core.Destroy.Core"(ptr @X.val), !dbg !39 -// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr @int_1.5d2), !dbg !38 +// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr %_.var.loc20), !dbg !42 +// CHECK:STDOUT: call void @"_COp.607f187990247af9:core.Destroy.Core"(ptr %_.var.loc18_24), !dbg !41 +// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr %_.var.loc18_12), !dbg !40 +// CHECK:STDOUT: call void @"_COp.607f187990247af9:core.Destroy.Core"(ptr %_.var.loc16), !dbg !39 +// CHECK:STDOUT: call void @"_COp.7e389eab4a7e5487:core.Destroy.Core"(ptr %_.var.loc15), !dbg !38 // CHECK:STDOUT: ret void, !dbg !50 // CHECK:STDOUT: } // CHECK:STDOUT: @@ -112,13 +117,18 @@ fn Call() { // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) // CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1 // CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #2 +// CHECK:STDOUT: // CHECK:STDOUT: ; uselistorder directives // CHECK:STDOUT: uselistorder ptr @"_COp.607f187990247af9:core.Destroy.Core", { 3, 2, 1, 0 } // CHECK:STDOUT: uselistorder ptr @"_COp.7e389eab4a7e5487:core.Destroy.Core", { 2, 1, 0 } // CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 6, 5, 4, 3, 2, 1, 0 } +// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 2, 1, 0 } // CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { nounwind } // CHECK:STDOUT: attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1} // CHECK:STDOUT: !llvm.dbg.cu = !{!2} diff --git a/toolchain/sem_ir/pattern.cpp b/toolchain/sem_ir/pattern.cpp index c598d70dcf0d..00b01bed4ef6 100644 --- a/toolchain/sem_ir/pattern.cpp +++ b/toolchain/sem_ir/pattern.cpp @@ -55,6 +55,11 @@ auto GetFirstBindingNameFromPatternId(const File& sem_ir, InstId pattern_id) // TODO: Look through struct patterns. + if (auto form_pattern = inst.TryAs()) { + // TODO: This introduces a name, but we don't model it as a binding. + return EntityNameId::None; + } + auto [name_id, entity_name_id] = GetBoundEntityName(sem_ir, inst); CARBON_CHECK(entity_name_id.has_value(), "Unhandled pattern inst kind {0}", inst);