Преглед изворни кода

Generalize non-const ClassInit lowering beyond only InitializeFrom insts (#5199)

Fixes #5186

With @zygoloid's kind assistance, this generalizes the existing
non-const lowering of ClassInit, that had previously only handled
InitializeFrom, to find other cases - such as a nested ClassInit used to
initialize a class member.

This refactors the `FindReturnSlotArgForInitializer` from
`check/convert.cpp` into `sem_ir/file.{h,cpp}` for use from lowering
(since lower doesn't depend on check, which I assume is an intentional
layering constraint - so figured it made sense to move it to sem_ir, and
found one or two similar-ish utility functions in `sem_ir/file.{h,cpp}`,
so figured that was a good spot)
David Blaikie пре 1 година
родитељ
комит
4739828cca

+ 2 - 0
toolchain/check/BUILD

@@ -112,6 +112,7 @@ cc_library(
         "//toolchain/lex:tokenized_buffer",
         "//toolchain/parse:node_kind",
         "//toolchain/parse:tree",
+        "//toolchain/sem_ir:expr_info",
         "//toolchain/sem_ir:file",
         "//toolchain/sem_ir:formatter",
         "//toolchain/sem_ir:inst",
@@ -183,6 +184,7 @@ cc_library(
         "//toolchain/parse:node_kind",
         "//toolchain/parse:tree",
         "//toolchain/sem_ir:entry_point",
+        "//toolchain/sem_ir:expr_info",
         "//toolchain/sem_ir:file",
         "//toolchain/sem_ir:inst",
         "//toolchain/sem_ir:typed_insts",

+ 1 - 49
toolchain/check/convert.cpp

@@ -24,6 +24,7 @@
 #include "toolchain/check/type_completion.h"
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/sem_ir/copy_on_write_block.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
@@ -36,55 +37,6 @@
 
 namespace Carbon::Check {
 
-// Given an initializing expression, find its return slot argument. Returns
-// `None` if there is no return slot, because the initialization is not
-// performed in place.
-static auto FindReturnSlotArgForInitializer(SemIR::File& sem_ir,
-                                            SemIR::InstId init_id)
-    -> SemIR::InstId {
-  while (true) {
-    SemIR::Inst init_untyped = sem_ir.insts().Get(init_id);
-    CARBON_KIND_SWITCH(init_untyped) {
-      case CARBON_KIND(SemIR::AsCompatible init): {
-        init_id = init.source_id;
-        continue;
-      }
-      case CARBON_KIND(SemIR::Converted init): {
-        init_id = init.result_id;
-        continue;
-      }
-      case CARBON_KIND(SemIR::ArrayInit init): {
-        return init.dest_id;
-      }
-      case CARBON_KIND(SemIR::ClassInit init): {
-        return init.dest_id;
-      }
-      case CARBON_KIND(SemIR::StructInit init): {
-        return init.dest_id;
-      }
-      case CARBON_KIND(SemIR::TupleInit init): {
-        return init.dest_id;
-      }
-      case CARBON_KIND(SemIR::InitializeFrom init): {
-        return init.dest_id;
-      }
-      case CARBON_KIND(SemIR::Call call): {
-        if (!SemIR::ReturnTypeInfo::ForType(sem_ir, call.type_id)
-                 .has_return_slot()) {
-          return SemIR::InstId::None;
-        }
-        if (!call.args_id.has_value()) {
-          // Argument initialization failed, so we have no return slot.
-          return SemIR::InstId::None;
-        }
-        return sem_ir.inst_blocks().Get(call.args_id).back();
-      }
-      default:
-        CARBON_FATAL("Initialization from unexpected inst {0}", init_untyped);
-    }
-  }
-}
-
 // Marks the initializer `init_id` as initializing `target_id`.
 static auto MarkInitializerFor(SemIR::File& sem_ir, SemIR::InstId init_id,
                                SemIR::InstId target_id,

+ 1 - 0
toolchain/check/handle_index.cpp

@@ -14,6 +14,7 @@
 #include "toolchain/check/operator.h"
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/diagnostic.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 

+ 1 - 0
toolchain/check/handle_operator.cpp

@@ -11,6 +11,7 @@
 #include "toolchain/check/pointer_dereference.h"
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
+#include "toolchain/sem_ir/expr_info.h"
 
 namespace Carbon::Check {
 

+ 1 - 0
toolchain/check/member_access.cpp

@@ -19,6 +19,7 @@
 #include "toolchain/check/type.h"
 #include "toolchain/check/type_completion.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"

+ 1 - 0
toolchain/check/pattern_match.cpp

@@ -16,6 +16,7 @@
 #include "toolchain/check/subpattern.h"
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/format_providers.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/pattern.h"
 
 namespace Carbon::Check {

+ 1 - 0
toolchain/lower/BUILD

@@ -52,6 +52,7 @@ cc_library(
         "//toolchain/parse:tree",
         "//toolchain/sem_ir:absolute_node_id",
         "//toolchain/sem_ir:entry_point",
+        "//toolchain/sem_ir:expr_info",
         "//toolchain/sem_ir:file",
         "//toolchain/sem_ir:inst",
         "//toolchain/sem_ir:inst_namer",

+ 8 - 3
toolchain/lower/handle_aggregates.cpp

@@ -8,6 +8,8 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/Value.h"
 #include "toolchain/lower/function_context.h"
+#include "toolchain/sem_ir/expr_info.h"
+#include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -141,9 +143,12 @@ static auto EmitAggregateInitializer(FunctionContext& context,
       // non-constant initialization.
       for (auto [i, ref_id] : llvm::enumerate(refs)) {
         if (context.sem_ir().constant_values().Get(ref_id).is_constant()) {
-          auto init =
-              context.sem_ir().insts().GetAs<SemIR::InitializeFrom>(ref_id);
-          HandleInst(context, ref_id, init);
+          auto dest_id =
+              SemIR::FindReturnSlotArgForInitializer(context.sem_ir(), ref_id);
+          auto src_id = ref_id;
+          auto storage_type_id =
+              context.sem_ir().insts().Get(dest_id).type_id();
+          context.FinishInit(storage_type_id, dest_id, src_id);
         }
       }
       // TODO: Add a helper to poison a value slot.

+ 1 - 0
toolchain/lower/handle_expr_category.cpp

@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 #include "toolchain/lower/function_context.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/file.h"
 
 namespace Carbon::Lower {

+ 152 - 22
toolchain/lower/testdata/class/field.carbon

@@ -8,6 +8,10 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/class/field.carbon
 
+// --- basic.carbon
+
+library "[[@TEST_NAME]]";
+
 class C {
   var a: i32;
   var b: C*;
@@ -24,26 +28,64 @@ fn Run() -> i32 {
   return F(c);
 }
 
-// CHECK:STDOUT: ; ModuleID = 'field.carbon'
-// CHECK:STDOUT: source_filename = "field.carbon"
+// --- inplace_init_with_nonconst.carbon
+
+library "[[@TEST_NAME]]";
+
+class Other { }
+
+class Use {
+  var a: Other*;
+  var b: Other;
+}
+
+fn Run() {
+  var o: Other;
+  // .a is initialized with a non-constant expression
+  // .b is initialized with in-place class_init
+  var u: Use = {.a = &o, .b = {}};
+}
+
+// --- implicit_init_with_nonempty_nonconst.carbon
+
+library "[[@TEST_NAME]]";
+
+class Other {
+  var v: i32;
+}
+
+class Use {
+  var a: Other*;
+  var b: Other;
+}
+
+fn Run() {
+  var o: Other;
+  // .a is initialized with a non-constant expression
+  // .b is initialized with in-place class_init
+  var u: Use = {.a = &o, .b = {.v = 42}};
+}
+
+// CHECK:STDOUT: ; ModuleID = 'basic.carbon'
+// CHECK:STDOUT: source_filename = "basic.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @_CF.Main(ptr %c) !dbg !4 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc17_13.1.b = getelementptr inbounds nuw { i32, ptr }, ptr %c, i32 0, i32 1, !dbg !7
-// CHECK:STDOUT:   %.loc17_13.2 = load ptr, ptr %.loc17_13.1.b, align 8, !dbg !7
-// CHECK:STDOUT:   %.loc17_16.1.a = getelementptr inbounds nuw { i32, ptr }, ptr %.loc17_13.2, i32 0, i32 0, !dbg !8
-// CHECK:STDOUT:   %.loc17_16.2 = load i32, ptr %.loc17_16.1.a, align 4, !dbg !8
-// CHECK:STDOUT:   ret i32 %.loc17_16.2, !dbg !9
+// CHECK:STDOUT:   %.loc10_13.1.b = getelementptr inbounds nuw { i32, ptr }, ptr %c, i32 0, i32 1, !dbg !7
+// CHECK:STDOUT:   %.loc10_13.2 = load ptr, ptr %.loc10_13.1.b, align 8, !dbg !7
+// CHECK:STDOUT:   %.loc10_16.1.a = getelementptr inbounds nuw { i32, ptr }, ptr %.loc10_13.2, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   %.loc10_16.2 = load i32, ptr %.loc10_16.1.a, align 4, !dbg !8
+// CHECK:STDOUT:   ret i32 %.loc10_16.2, !dbg !9
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @main() !dbg !10 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %c.var = alloca { i32, ptr }, align 8, !dbg !11
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 16, ptr %c.var), !dbg !11
-// CHECK:STDOUT:   %.loc22_4.a = getelementptr inbounds nuw { i32, ptr }, ptr %c.var, i32 0, i32 0, !dbg !12
-// CHECK:STDOUT:   store i32 1, ptr %.loc22_4.a, align 4, !dbg !12
-// CHECK:STDOUT:   %.loc23.b = getelementptr inbounds nuw { i32, ptr }, ptr %c.var, i32 0, i32 1, !dbg !13
-// CHECK:STDOUT:   store ptr %c.var, ptr %.loc23.b, align 8, !dbg !13
+// CHECK:STDOUT:   %.loc15_4.a = getelementptr inbounds nuw { i32, ptr }, ptr %c.var, i32 0, i32 0, !dbg !12
+// CHECK:STDOUT:   store i32 1, ptr %.loc15_4.a, align 4, !dbg !12
+// CHECK:STDOUT:   %.loc16.b = getelementptr inbounds nuw { i32, ptr }, ptr %c.var, i32 0, i32 1, !dbg !13
+// CHECK:STDOUT:   store ptr %c.var, ptr %.loc16.b, align 8, !dbg !13
 // CHECK:STDOUT:   %F.call = call i32 @_CF.Main(ptr %c.var), !dbg !14
 // CHECK:STDOUT:   ret i32 %F.call, !dbg !15
 // CHECK:STDOUT: }
@@ -59,16 +101,104 @@ fn Run() -> i32 {
 // CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
-// CHECK:STDOUT: !3 = !DIFile(filename: "field.carbon", directory: "")
-// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !3 = !DIFile(filename: "basic.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 10, column: 12, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 10, column: 10, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 10, column: 3, scope: !4)
+// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "Run", linkageName: "main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !11 = !DILocation(line: 14, column: 3, scope: !10)
+// CHECK:STDOUT: !12 = !DILocation(line: 15, column: 3, scope: !10)
+// CHECK:STDOUT: !13 = !DILocation(line: 16, column: 3, scope: !10)
+// CHECK:STDOUT: !14 = !DILocation(line: 17, column: 10, scope: !10)
+// CHECK:STDOUT: !15 = !DILocation(line: 17, column: 3, scope: !10)
+// CHECK:STDOUT: ; ModuleID = 'inplace_init_with_nonconst.carbon'
+// CHECK:STDOUT: source_filename = "inplace_init_with_nonconst.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @Other.val.loc15_33.5 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %o.var = alloca {}, align 8, !dbg !7
+// CHECK:STDOUT:   %u.var = alloca { ptr, {} }, align 8, !dbg !7
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %o.var), !dbg !7
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %u.var), !dbg !7
+// CHECK:STDOUT:   %.loc15_33.2.a = getelementptr inbounds nuw { ptr, {} }, ptr %u.var, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   store ptr %o.var, ptr %.loc15_33.2.a, align 8, !dbg !8
+// CHECK:STDOUT:   %.loc15_33.4.b = getelementptr inbounds nuw { ptr, {} }, ptr %u.var, i32 0, i32 1, !dbg !8
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc15_33.4.b, ptr align 1 @Other.val.loc15_33.5, i64 0, i1 false), !dbg !8
+// CHECK:STDOUT:   ret void, !dbg !9
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// 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) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "inplace_init_with_nonconst.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Run", linkageName: "main", scope: null, file: !3, line: 11, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 12, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 15, column: 16, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 11, column: 1, scope: !4)
+// CHECK:STDOUT: ; ModuleID = 'implicit_init_with_nonempty_nonconst.carbon'
+// CHECK:STDOUT: source_filename = "implicit_init_with_nonempty_nonconst.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @Other.val.loc17_40.5 = internal constant { i32 } { i32 42 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %o.var = alloca { i32 }, align 8, !dbg !7
+// CHECK:STDOUT:   %u.var = alloca { ptr, { i32 } }, align 8, !dbg !7
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 4, ptr %o.var), !dbg !7
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 16, ptr %u.var), !dbg !7
+// CHECK:STDOUT:   %.loc17_40.2.a = getelementptr inbounds nuw { ptr, { i32 } }, ptr %u.var, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   store ptr %o.var, ptr %.loc17_40.2.a, align 8, !dbg !8
+// CHECK:STDOUT:   %.loc17_40.4.b = getelementptr inbounds nuw { ptr, { i32 } }, ptr %u.var, i32 0, i32 1, !dbg !8
+// CHECK:STDOUT:   %.loc17_39.3.v = getelementptr inbounds nuw { i32 }, ptr %.loc17_40.4.b, i32 0, i32 0, !dbg !9
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %.loc17_40.4.b, ptr align 4 @Other.val.loc17_40.5, i64 4, i1 false), !dbg !8
+// CHECK:STDOUT:   ret void, !dbg !10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// 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) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "implicit_init_with_nonempty_nonconst.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Run", linkageName: "main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{}
-// CHECK:STDOUT: !7 = !DILocation(line: 17, column: 12, scope: !4)
-// CHECK:STDOUT: !8 = !DILocation(line: 17, column: 10, scope: !4)
-// CHECK:STDOUT: !9 = !DILocation(line: 17, column: 3, scope: !4)
-// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "Run", linkageName: "main", scope: null, file: !3, line: 20, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !11 = !DILocation(line: 21, column: 3, scope: !10)
-// CHECK:STDOUT: !12 = !DILocation(line: 22, column: 3, scope: !10)
-// CHECK:STDOUT: !13 = !DILocation(line: 23, column: 3, scope: !10)
-// CHECK:STDOUT: !14 = !DILocation(line: 24, column: 10, scope: !10)
-// CHECK:STDOUT: !15 = !DILocation(line: 24, column: 3, scope: !10)
+// CHECK:STDOUT: !7 = !DILocation(line: 14, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 17, column: 16, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 17, column: 31, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 13, column: 1, scope: !4)

+ 13 - 0
toolchain/sem_ir/BUILD

@@ -178,6 +178,7 @@ cc_library(
     srcs = ["formatter.cpp"],
     hdrs = ["formatter.h"],
     deps = [
+        ":expr_info",
         ":file",
         ":inst_namer",
         ":typed_insts",
@@ -212,6 +213,18 @@ cc_library(
     ],
 )
 
+cc_library(
+    name = "expr_info",
+    srcs = ["expr_info.cpp"],
+    hdrs = ["expr_info.h"],
+    deps = [
+        ":file",
+        ":typed_insts",
+        "//common:check",
+        "//toolchain/base:kind_switch",
+    ],
+)
+
 cc_library(
     name = "dump",
     srcs = ["dump.cpp"],

+ 279 - 0
toolchain/sem_ir/expr_info.cpp

@@ -0,0 +1,279 @@
+// 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/sem_ir/expr_info.h"
+
+#include "common/check.h"
+#include "toolchain/base/kind_switch.h"
+#include "toolchain/sem_ir/typed_insts.h"
+
+namespace Carbon::SemIR {
+
+auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
+  const File* ir = &file;
+
+  // The overall expression category if the current instruction is a value
+  // expression.
+  ExprCategory value_category = ExprCategory::Value;
+
+  while (true) {
+    auto untyped_inst = ir->insts().Get(inst_id);
+    CARBON_KIND_SWITCH(untyped_inst) {
+      case AdaptDecl::Kind:
+      case AddrPattern::Kind:
+      case Assign::Kind:
+      case BaseDecl::Kind:
+      case BindingPattern::Kind:
+      case Branch::Kind:
+      case BranchIf::Kind:
+      case BranchWithArg::Kind:
+      case FieldDecl::Kind:
+      case FunctionDecl::Kind:
+      case ImplDecl::Kind:
+      case NameBindingDecl::Kind:
+      case Namespace::Kind:
+      case OutParamPattern::Kind:
+      case RefParamPattern::Kind:
+      case RequirementEquivalent::Kind:
+      case RequirementImpls::Kind:
+      case RequirementRewrite::Kind:
+      case Return::Kind:
+      case ReturnSlotPattern::Kind:
+      case ReturnExpr::Kind:
+      case TuplePattern::Kind:
+      case VarPattern::Kind:
+      case Vtable::Kind:
+        return ExprCategory::NotExpr;
+
+      case ImportRefUnloaded::Kind:
+      case ImportRefLoaded::Kind: {
+        auto import_ir_inst = ir->import_ir_insts().Get(
+            untyped_inst.As<SemIR::AnyImportRef>().import_ir_inst_id);
+        ir = ir->import_irs().Get(import_ir_inst.ir_id).sem_ir;
+        inst_id = import_ir_inst.inst_id;
+        continue;
+      }
+
+      case CARBON_KIND(AsCompatible inst): {
+        inst_id = inst.source_id;
+        continue;
+      }
+
+      case CARBON_KIND(BindAlias inst): {
+        inst_id = inst.value_id;
+        continue;
+      }
+      case CARBON_KIND(ExportDecl inst): {
+        inst_id = inst.value_id;
+        continue;
+      }
+      case CARBON_KIND(NameRef inst): {
+        inst_id = inst.value_id;
+        continue;
+      }
+
+      case CARBON_KIND(Converted inst): {
+        inst_id = inst.result_id;
+        continue;
+      }
+
+      case CARBON_KIND(SpecificConstant inst): {
+        inst_id = inst.inst_id;
+        continue;
+      }
+
+      case AccessMemberAction::Kind:
+      case AccessOptionalMemberAction::Kind:
+      case AddrOf::Kind:
+      case ArrayType::Kind:
+      case AssociatedConstantDecl::Kind:
+      case AssociatedEntity::Kind:
+      case AssociatedEntityType::Kind:
+      case AutoType::Kind:
+      case BindSymbolicName::Kind:
+      case BindValue::Kind:
+      case BlockArg::Kind:
+      case BoolLiteral::Kind:
+      case BoolType::Kind:
+      case BoundMethod::Kind:
+      case BoundMethodType::Kind:
+      case ClassDecl::Kind:
+      case ClassType::Kind:
+      case CompleteTypeWitness::Kind:
+      case ConstType::Kind:
+      case ConvertToValueAction::Kind:
+      case FacetAccessType::Kind:
+      case FacetAccessWitness::Kind:
+      case FacetType::Kind:
+      case FacetValue::Kind:
+      case FloatLiteral::Kind:
+      case FloatType::Kind:
+      case FunctionType::Kind:
+      case FunctionTypeWithSelfType::Kind:
+      case GenericClassType::Kind:
+      case GenericInterfaceType::Kind:
+      case LookupImplWitness::Kind:
+      case ImplWitness::Kind:
+      case ImplWitnessAccess::Kind:
+      case ImportCppDecl::Kind:
+      case ImportDecl::Kind:
+      case InstType::Kind:
+      case InstValue::Kind:
+      case IntLiteralType::Kind:
+      case IntType::Kind:
+      case IntValue::Kind:
+      case InterfaceDecl::Kind:
+      case LegacyFloatType::Kind:
+      case NamespaceType::Kind:
+      case PointerType::Kind:
+      case RefineTypeAction::Kind:
+      case RequireCompleteType::Kind:
+      case SpecificFunction::Kind:
+      case SpecificFunctionType::Kind:
+      case SpecificImplFunction::Kind:
+      case StringLiteral::Kind:
+      case StringType::Kind:
+      case StructType::Kind:
+      case StructValue::Kind:
+      case SymbolicBindingPattern::Kind:
+      case TupleType::Kind:
+      case TupleValue::Kind:
+      case TypeOfInst::Kind:
+      case TypeType::Kind:
+      case UnaryOperatorNot::Kind:
+      case UnboundElementType::Kind:
+      case ValueOfInitializer::Kind:
+      case ValueParam::Kind:
+      case ValueParamPattern::Kind:
+      case VtableType::Kind:
+      case WhereExpr::Kind:
+      case WitnessType::Kind:
+        return value_category;
+
+      case ErrorInst::Kind:
+        return ExprCategory::Error;
+
+      case CARBON_KIND(BindName inst): {
+        // TODO: Don't rely on value_id for expression category, since it may
+        // not be valid yet. This workaround only works because we don't support
+        // `var` in function signatures yet.
+        if (!inst.value_id.has_value()) {
+          return value_category;
+        }
+        inst_id = inst.value_id;
+        continue;
+      }
+
+      case CARBON_KIND(ArrayIndex inst): {
+        inst_id = inst.array_id;
+        continue;
+      }
+
+      case VtablePtr::Kind:
+        return ExprCategory::EphemeralRef;
+
+      case CARBON_KIND(ClassElementAccess inst): {
+        inst_id = inst.base_id;
+        // A value of class type is a pointer to an object representation.
+        // Therefore, if the base is a value, the result is an ephemeral
+        // reference.
+        value_category = ExprCategory::EphemeralRef;
+        continue;
+      }
+
+      case CARBON_KIND(StructAccess inst): {
+        inst_id = inst.struct_id;
+        continue;
+      }
+
+      case CARBON_KIND(TupleAccess inst): {
+        inst_id = inst.tuple_id;
+        continue;
+      }
+
+      case CARBON_KIND(SpliceBlock inst): {
+        inst_id = inst.result_id;
+        continue;
+      }
+
+      case SpliceInst::Kind:
+        // TODO: Add ExprCategory::Dependent.
+        return value_category;
+
+      case StructLiteral::Kind:
+      case TupleLiteral::Kind:
+        return ExprCategory::Mixed;
+
+      case ArrayInit::Kind:
+      case Call::Kind:
+      case InitializeFrom::Kind:
+      case ClassInit::Kind:
+      case StructInit::Kind:
+      case TupleInit::Kind:
+        return ExprCategory::Initializing;
+
+      case Deref::Kind:
+      case VarStorage::Kind:
+      case ReturnSlot::Kind:
+        return ExprCategory::DurableRef;
+
+      case Temporary::Kind:
+      case TemporaryStorage::Kind:
+      case ValueAsRef::Kind:
+        return ExprCategory::EphemeralRef;
+
+      case OutParam::Kind:
+      case RefParam::Kind:
+        // TODO: Consider introducing a separate category for OutParam:
+        // unlike other DurableRefs, it permits initialization.
+        return ExprCategory::DurableRef;
+    }
+  }
+}
+
+auto FindReturnSlotArgForInitializer(const File& sem_ir, InstId init_id)
+    -> InstId {
+  while (true) {
+    Inst init_untyped = sem_ir.insts().Get(init_id);
+    CARBON_KIND_SWITCH(init_untyped) {
+      case CARBON_KIND(AsCompatible init): {
+        init_id = init.source_id;
+        continue;
+      }
+      case CARBON_KIND(Converted init): {
+        init_id = init.result_id;
+        continue;
+      }
+      case CARBON_KIND(ArrayInit init): {
+        return init.dest_id;
+      }
+      case CARBON_KIND(ClassInit init): {
+        return init.dest_id;
+      }
+      case CARBON_KIND(StructInit init): {
+        return init.dest_id;
+      }
+      case CARBON_KIND(TupleInit init): {
+        return init.dest_id;
+      }
+      case CARBON_KIND(InitializeFrom init): {
+        return init.dest_id;
+      }
+      case CARBON_KIND(Call call): {
+        if (!ReturnTypeInfo::ForType(sem_ir, call.type_id).has_return_slot()) {
+          return InstId::None;
+        }
+        if (!call.args_id.has_value()) {
+          // Argument initialization failed, so we have no return slot.
+          return InstId::None;
+        }
+        return sem_ir.inst_blocks().Get(call.args_id).back();
+      }
+      default:
+        CARBON_FATAL("Initialization from unexpected inst {0}", init_untyped);
+    }
+  }
+}
+
+}  // namespace Carbon::SemIR

+ 53 - 0
toolchain/sem_ir/expr_info.h

@@ -0,0 +1,53 @@
+// 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_SEM_IR_EXPR_INFO_H_
+#define CARBON_TOOLCHAIN_SEM_IR_EXPR_INFO_H_
+
+#include <cstdint>
+
+#include "toolchain/sem_ir/file.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::SemIR {
+
+// The expression category of a sem_ir instruction. See /docs/design/values.md
+// for details.
+enum class ExprCategory : int8_t {
+  // This instruction does not correspond to an expression, and as such has no
+  // category.
+  NotExpr,
+  // The category of this instruction is not known due to an error.
+  Error,
+  // This instruction represents a value expression.
+  Value,
+  // This instruction represents a durable reference expression, that denotes an
+  // object that outlives the current full expression context.
+  DurableRef,
+  // This instruction represents an ephemeral reference expression, that denotes
+  // an object that does not outlive the current full expression context.
+  EphemeralRef,
+  // This instruction represents an initializing expression, that describes how
+  // to initialize an object.
+  Initializing,
+  // This instruction represents a syntactic combination of expressions that are
+  // permitted to have different expression categories. This is used for tuple
+  // and struct literals, where the subexpressions for different elements can
+  // have different categories.
+  Mixed,
+  Last = Mixed
+};
+
+// Returns the expression category for an instruction.
+auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory;
+
+// Given an initializing expression, find its return slot argument. Returns
+// `None` if there is no return slot, because the initialization is not
+// performed in place.
+auto FindReturnSlotArgForInitializer(const File& sem_ir, InstId init_id)
+    -> InstId;
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_EXPR_INFO_H_

+ 1 - 224
toolchain/sem_ir/file.cpp

@@ -47,8 +47,7 @@ File::File(const Parse::Tree* parse_tree, CheckIRId check_ir_id,
   for (auto kind : SingletonInstKinds) {
     auto inst_id =
         insts_.AddInNoBlock(LocIdAndInst::NoLoc(Inst::MakeSingleton(kind)));
-    constant_values_.Set(inst_id,
-                         SemIR::ConstantId::ForConcreteConstant(inst_id));
+    constant_values_.Set(inst_id, ConstantId::ForConcreteConstant(inst_id));
   }
 }
 
@@ -168,226 +167,4 @@ auto File::CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const
   mem_usage.Collect(MemUsage::ConcatLabel(label, "types_"), types_);
 }
 
-auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
-  const File* ir = &file;
-
-  // The overall expression category if the current instruction is a value
-  // expression.
-  ExprCategory value_category = ExprCategory::Value;
-
-  while (true) {
-    auto untyped_inst = ir->insts().Get(inst_id);
-    CARBON_KIND_SWITCH(untyped_inst) {
-      case AdaptDecl::Kind:
-      case AddrPattern::Kind:
-      case Assign::Kind:
-      case BaseDecl::Kind:
-      case BindingPattern::Kind:
-      case Branch::Kind:
-      case BranchIf::Kind:
-      case BranchWithArg::Kind:
-      case FieldDecl::Kind:
-      case FunctionDecl::Kind:
-      case ImplDecl::Kind:
-      case NameBindingDecl::Kind:
-      case Namespace::Kind:
-      case OutParamPattern::Kind:
-      case RefParamPattern::Kind:
-      case RequirementEquivalent::Kind:
-      case RequirementImpls::Kind:
-      case RequirementRewrite::Kind:
-      case Return::Kind:
-      case ReturnSlotPattern::Kind:
-      case ReturnExpr::Kind:
-      case TuplePattern::Kind:
-      case VarPattern::Kind:
-      case Vtable::Kind:
-        return ExprCategory::NotExpr;
-
-      case ImportRefUnloaded::Kind:
-      case ImportRefLoaded::Kind: {
-        auto import_ir_inst = ir->import_ir_insts().Get(
-            untyped_inst.As<SemIR::AnyImportRef>().import_ir_inst_id);
-        ir = ir->import_irs().Get(import_ir_inst.ir_id).sem_ir;
-        inst_id = import_ir_inst.inst_id;
-        continue;
-      }
-
-      case CARBON_KIND(AsCompatible inst): {
-        inst_id = inst.source_id;
-        continue;
-      }
-
-      case CARBON_KIND(BindAlias inst): {
-        inst_id = inst.value_id;
-        continue;
-      }
-      case CARBON_KIND(ExportDecl inst): {
-        inst_id = inst.value_id;
-        continue;
-      }
-      case CARBON_KIND(NameRef inst): {
-        inst_id = inst.value_id;
-        continue;
-      }
-
-      case CARBON_KIND(Converted inst): {
-        inst_id = inst.result_id;
-        continue;
-      }
-
-      case CARBON_KIND(SpecificConstant inst): {
-        inst_id = inst.inst_id;
-        continue;
-      }
-
-      case AccessMemberAction::Kind:
-      case AccessOptionalMemberAction::Kind:
-      case AddrOf::Kind:
-      case ArrayType::Kind:
-      case AssociatedConstantDecl::Kind:
-      case AssociatedEntity::Kind:
-      case AssociatedEntityType::Kind:
-      case AutoType::Kind:
-      case BindSymbolicName::Kind:
-      case BindValue::Kind:
-      case BlockArg::Kind:
-      case BoolLiteral::Kind:
-      case BoolType::Kind:
-      case BoundMethod::Kind:
-      case BoundMethodType::Kind:
-      case ClassDecl::Kind:
-      case ClassType::Kind:
-      case CompleteTypeWitness::Kind:
-      case ConstType::Kind:
-      case ConvertToValueAction::Kind:
-      case FacetAccessType::Kind:
-      case FacetAccessWitness::Kind:
-      case FacetType::Kind:
-      case FacetValue::Kind:
-      case FloatLiteral::Kind:
-      case FloatType::Kind:
-      case FunctionType::Kind:
-      case FunctionTypeWithSelfType::Kind:
-      case GenericClassType::Kind:
-      case GenericInterfaceType::Kind:
-      case LookupImplWitness::Kind:
-      case ImplWitness::Kind:
-      case ImplWitnessAccess::Kind:
-      case ImportCppDecl::Kind:
-      case ImportDecl::Kind:
-      case InstType::Kind:
-      case InstValue::Kind:
-      case IntLiteralType::Kind:
-      case IntType::Kind:
-      case IntValue::Kind:
-      case InterfaceDecl::Kind:
-      case LegacyFloatType::Kind:
-      case NamespaceType::Kind:
-      case PointerType::Kind:
-      case RefineTypeAction::Kind:
-      case RequireCompleteType::Kind:
-      case SpecificFunction::Kind:
-      case SpecificFunctionType::Kind:
-      case SpecificImplFunction::Kind:
-      case StringLiteral::Kind:
-      case StringType::Kind:
-      case StructType::Kind:
-      case StructValue::Kind:
-      case SymbolicBindingPattern::Kind:
-      case TupleType::Kind:
-      case TupleValue::Kind:
-      case TypeOfInst::Kind:
-      case TypeType::Kind:
-      case UnaryOperatorNot::Kind:
-      case UnboundElementType::Kind:
-      case ValueOfInitializer::Kind:
-      case ValueParam::Kind:
-      case ValueParamPattern::Kind:
-      case VtableType::Kind:
-      case WhereExpr::Kind:
-      case WitnessType::Kind:
-        return value_category;
-
-      case ErrorInst::Kind:
-        return ExprCategory::Error;
-
-      case CARBON_KIND(BindName inst): {
-        // TODO: Don't rely on value_id for expression category, since it may
-        // not be valid yet. This workaround only works because we don't support
-        // `var` in function signatures yet.
-        if (!inst.value_id.has_value()) {
-          return value_category;
-        }
-        inst_id = inst.value_id;
-        continue;
-      }
-
-      case CARBON_KIND(ArrayIndex inst): {
-        inst_id = inst.array_id;
-        continue;
-      }
-
-      case VtablePtr::Kind:
-        return ExprCategory::EphemeralRef;
-
-      case CARBON_KIND(ClassElementAccess inst): {
-        inst_id = inst.base_id;
-        // A value of class type is a pointer to an object representation.
-        // Therefore, if the base is a value, the result is an ephemeral
-        // reference.
-        value_category = ExprCategory::EphemeralRef;
-        continue;
-      }
-
-      case CARBON_KIND(StructAccess inst): {
-        inst_id = inst.struct_id;
-        continue;
-      }
-
-      case CARBON_KIND(TupleAccess inst): {
-        inst_id = inst.tuple_id;
-        continue;
-      }
-
-      case CARBON_KIND(SpliceBlock inst): {
-        inst_id = inst.result_id;
-        continue;
-      }
-
-      case SpliceInst::Kind:
-        // TODO: Add ExprCategory::Dependent.
-        return value_category;
-
-      case StructLiteral::Kind:
-      case TupleLiteral::Kind:
-        return ExprCategory::Mixed;
-
-      case ArrayInit::Kind:
-      case Call::Kind:
-      case InitializeFrom::Kind:
-      case ClassInit::Kind:
-      case StructInit::Kind:
-      case TupleInit::Kind:
-        return ExprCategory::Initializing;
-
-      case Deref::Kind:
-      case VarStorage::Kind:
-      case ReturnSlot::Kind:
-        return ExprCategory::DurableRef;
-
-      case Temporary::Kind:
-      case TemporaryStorage::Kind:
-      case ValueAsRef::Kind:
-        return ExprCategory::EphemeralRef;
-
-      case OutParam::Kind:
-      case RefParam::Kind:
-        // TODO: Consider introducing a separate category for OutParam:
-        // unlike other DurableRefs, it permits initialization.
-        return ExprCategory::DurableRef;
-    }
-  }
-}
-
 }  // namespace Carbon::SemIR

+ 0 - 30
toolchain/sem_ir/file.h

@@ -361,36 +361,6 @@ class File : public Printable<File> {
   ValueStore<ExprRegionId> expr_regions_;
 };
 
-// The expression category of a sem_ir instruction. See /docs/design/values.md
-// for details.
-enum class ExprCategory : int8_t {
-  // This instruction does not correspond to an expression, and as such has no
-  // category.
-  NotExpr,
-  // The category of this instruction is not known due to an error.
-  Error,
-  // This instruction represents a value expression.
-  Value,
-  // This instruction represents a durable reference expression, that denotes an
-  // object that outlives the current full expression context.
-  DurableRef,
-  // This instruction represents an ephemeral reference expression, that denotes
-  // an object that does not outlive the current full expression context.
-  EphemeralRef,
-  // This instruction represents an initializing expression, that describes how
-  // to initialize an object.
-  Initializing,
-  // This instruction represents a syntactic combination of expressions that are
-  // permitted to have different expression categories. This is used for tuple
-  // and struct literals, where the subexpressions for different elements can
-  // have different categories.
-  Mixed,
-  Last = Mixed
-};
-
-// Returns the expression category for an instruction.
-auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory;
-
 }  // namespace Carbon::SemIR
 
 #endif  // CARBON_TOOLCHAIN_SEM_IR_FILE_H_

+ 1 - 0
toolchain/sem_ir/formatter.cpp

@@ -15,6 +15,7 @@
 #include "toolchain/sem_ir/builtin_function_kind.h"
 #include "toolchain/sem_ir/constant.h"
 #include "toolchain/sem_ir/entity_with_params_base.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst_namer.h"