Просмотр исходного кода

Use computed constants in lowering rather than lowering instructions (#3905)

First steps towards using constant values in lowering.

For now, we reuse the regular instruction lowering to lower constants.
This mostly works, because we don't actually need an `llvm::Function` or
a current basic block when lowering a constant most of the time.
However, a special case is needed for lowering aggregate value constants
because they would otherwise create a stack alloca to store the
constant. Separate constant lowering code will be added in a future
change to clean this up.

When lowering a constant initializing expression, the result is a value
of the destination type, rather than code to initialize the destination,
so a separate copy step is required when finishing initialization from a
constant for a type that uses in-place initialization. Handling this
required extending `ReturnExpr` to track its destination location.

We currently often create non-constant `*_access` SemIR instructions
that are only used by constant `*_init` instructions. These cause
lowering to leave behind `getelementptr` instructions in the lowered IR
that are now unused. It should be possible to detect this case and avoid
producing these instructions, or to produce them lazily, but for now
we're just leaving them around for LLVM to clean up.
Richard Smith 2 лет назад
Родитель
Сommit
ccf87f0a38
60 измененных файлов с 357 добавлено и 162 удалено
  1. 7 2
      toolchain/check/return.cpp
  2. 1 1
      toolchain/check/testdata/alias/in_namespace.carbon
  3. 1 1
      toolchain/check/testdata/as/adapter_conversion.carbon
  4. 2 2
      toolchain/check/testdata/basics/raw_and_textual_ir.carbon
  5. 1 1
      toolchain/check/testdata/basics/raw_ir.carbon
  6. 1 1
      toolchain/check/testdata/basics/textual_ir.carbon
  7. 2 2
      toolchain/check/testdata/class/base.carbon
  8. 2 2
      toolchain/check/testdata/class/fail_abstract.carbon
  9. 1 1
      toolchain/check/testdata/class/fail_convert_to_invalid.carbon
  10. 1 1
      toolchain/check/testdata/class/fail_self.carbon
  11. 2 2
      toolchain/check/testdata/class/init.carbon
  12. 1 1
      toolchain/check/testdata/class/init_nested.carbon
  13. 1 1
      toolchain/check/testdata/class/raw_self.carbon
  14. 1 1
      toolchain/check/testdata/class/self_type.carbon
  15. 1 1
      toolchain/check/testdata/expr_category/in_place_tuple_init.carbon
  16. 1 1
      toolchain/check/testdata/global/class_with_fun.carbon
  17. 2 2
      toolchain/check/testdata/impl/self_in_signature.carbon
  18. 2 2
      toolchain/check/testdata/operators/overloaded/add.carbon
  19. 2 2
      toolchain/check/testdata/operators/overloaded/bit_and.carbon
  20. 2 2
      toolchain/check/testdata/operators/overloaded/bit_complement.carbon
  21. 2 2
      toolchain/check/testdata/operators/overloaded/bit_or.carbon
  22. 2 2
      toolchain/check/testdata/operators/overloaded/bit_xor.carbon
  23. 2 2
      toolchain/check/testdata/operators/overloaded/div.carbon
  24. 2 2
      toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon
  25. 1 1
      toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon
  26. 2 2
      toolchain/check/testdata/operators/overloaded/left_shift.carbon
  27. 2 2
      toolchain/check/testdata/operators/overloaded/mod.carbon
  28. 2 2
      toolchain/check/testdata/operators/overloaded/mul.carbon
  29. 2 2
      toolchain/check/testdata/operators/overloaded/negate.carbon
  30. 2 2
      toolchain/check/testdata/operators/overloaded/right_shift.carbon
  31. 2 2
      toolchain/check/testdata/operators/overloaded/sub.carbon
  32. 1 1
      toolchain/check/testdata/return/returned_var.carbon
  33. 1 1
      toolchain/check/testdata/return/tuple.carbon
  34. 1 1
      toolchain/check/testdata/struct/reorder_fields.carbon
  35. 18 0
      toolchain/lower/file_context.cpp
  36. 36 18
      toolchain/lower/function_context.cpp
  37. 9 0
      toolchain/lower/function_context.h
  38. 6 5
      toolchain/lower/handle.cpp
  39. 40 11
      toolchain/lower/handle_aggregates.cpp
  40. 8 2
      toolchain/lower/testdata/array/assign_return_value.carbon
  41. 20 6
      toolchain/lower/testdata/array/base.carbon
  42. 8 3
      toolchain/lower/testdata/array/function_param.carbon
  43. 13 10
      toolchain/lower/testdata/basics/numeric_literals.carbon
  44. 11 2
      toolchain/lower/testdata/class/adapt.carbon
  45. 9 2
      toolchain/lower/testdata/class/base.carbon
  46. 3 9
      toolchain/lower/testdata/function/call/struct_param.carbon
  47. 3 9
      toolchain/lower/testdata/function/call/tuple_param.carbon
  48. 4 7
      toolchain/lower/testdata/function/call/tuple_param_with_return_slot.carbon
  49. 14 4
      toolchain/lower/testdata/index/array_element_access.carbon
  50. 13 3
      toolchain/lower/testdata/index/tuple_element_access.carbon
  51. 8 2
      toolchain/lower/testdata/index/tuple_return_value_access.carbon
  52. 15 5
      toolchain/lower/testdata/let/tuple.carbon
  53. 11 2
      toolchain/lower/testdata/operators/assignment.carbon
  54. 11 2
      toolchain/lower/testdata/pointer/address_of_field.carbon
  55. 0 1
      toolchain/lower/testdata/return/return_var.carbon
  56. 11 2
      toolchain/lower/testdata/struct/member_access.carbon
  57. 11 2
      toolchain/lower/testdata/struct/two_entries.carbon
  58. 8 2
      toolchain/lower/testdata/tuple/two_entries.carbon
  59. 7 0
      toolchain/sem_ir/formatter.cpp
  60. 2 0
      toolchain/sem_ir/typed_insts.h

+ 7 - 2
toolchain/check/return.cpp

@@ -118,6 +118,7 @@ auto BuildReturnWithExpr(Context& context, Parse::ReturnStatementId node_id,
                          SemIR::InstId expr_id) -> void {
   const auto& function = GetCurrentFunction(context);
   auto returned_var_id = GetCurrentReturnedVar(context);
+  auto return_slot_id = SemIR::InstId::Invalid;
 
   if (!function.return_type_id.is_valid()) {
     CARBON_DIAGNOSTIC(
@@ -137,6 +138,7 @@ auto BuildReturnWithExpr(Context& context, Parse::ReturnStatementId node_id,
     expr_id = SemIR::InstId::BuiltinError;
   } else if (function.has_return_slot()) {
     expr_id = Initialize(context, node_id, function.return_storage_id, expr_id);
+    return_slot_id = function.return_storage_id;
   } else if (function.return_slot == SemIR::Function::ReturnSlot::Error) {
     // Don't produce a second error complaining the return type is incomplete.
     expr_id = SemIR::InstId::BuiltinError;
@@ -145,7 +147,7 @@ auto BuildReturnWithExpr(Context& context, Parse::ReturnStatementId node_id,
                                    function.return_type_id);
   }
 
-  context.AddInst({node_id, SemIR::ReturnExpr{expr_id}});
+  context.AddInst({node_id, SemIR::ReturnExpr{expr_id, return_slot_id}});
 }
 
 auto BuildReturnVar(Context& context, Parse::ReturnStatementId node_id)
@@ -160,13 +162,16 @@ auto BuildReturnVar(Context& context, Parse::ReturnStatementId node_id)
     returned_var_id = SemIR::InstId::BuiltinError;
   }
 
+  auto return_slot_id = function.return_storage_id;
   if (!function.has_return_slot()) {
     // If we don't have a return slot, we're returning by value. Convert to a
     // value expression.
     returned_var_id = ConvertToValueExpr(context, returned_var_id);
+    return_slot_id = SemIR::InstId::Invalid;
   }
 
-  context.AddInst({node_id, SemIR::ReturnExpr{returned_var_id}});
+  context.AddInst(
+      {node_id, SemIR::ReturnExpr{returned_var_id, return_slot_id}});
 }
 
 }  // namespace Carbon::Check

+ 1 - 1
toolchain/check/testdata/alias/in_namespace.carbon

@@ -75,6 +75,6 @@ fn F() -> NS.a {
 // CHECK:STDOUT:   %.loc15_17.3: init i32 = initialize_from %.loc15_16 to %.loc15_17.2 [template = constants.%.4]
 // CHECK:STDOUT:   %.loc15_17.4: init C = class_init (%.loc15_17.3), %return [template = constants.%.5]
 // CHECK:STDOUT:   %.loc15_18: init C = converted %.loc15_17.1, %.loc15_17.4 [template = constants.%.5]
-// CHECK:STDOUT:   return %.loc15_18
+// CHECK:STDOUT:   return %.loc15_18 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/as/adapter_conversion.carbon

@@ -183,7 +183,7 @@ var b: B = {.x = 1} as B;
 // CHECK:STDOUT:   %.loc9_27.5: init i32 = initialize_from %.loc9_26 to %.loc9_27.4 [template = constants.%.5]
 // CHECK:STDOUT:   %.loc9_27.6: init A = class_init (%.loc9_27.3, %.loc9_27.5), @A.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc9_28: init A = converted %.loc9_27.1, %.loc9_27.6 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc9_28
+// CHECK:STDOUT:   return %.loc9_28 to @A.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {

+ 2 - 2
toolchain/check/testdata/basics/raw_and_textual_ir.carbon

@@ -77,7 +77,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     inst+26:         {kind: TupleInit, arg0: block10, arg1: inst+13, type: type2}
 // CHECK:STDOUT:     inst+27:         {kind: TupleValue, arg0: block12, type: type2}
 // CHECK:STDOUT:     inst+28:         {kind: Converted, arg0: inst+18, arg1: inst+26, type: type2}
-// CHECK:STDOUT:     inst+29:         {kind: ReturnExpr, arg0: inst+28}
+// CHECK:STDOUT:     inst+29:         {kind: ReturnExpr, arg0: inst+28, arg1: inst+13}
 // CHECK:STDOUT:   constant_values:
 // CHECK:STDOUT:     inst+0:          template inst+0
 // CHECK:STDOUT:     inst+1:          template inst+1
@@ -190,6 +190,6 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:   %.loc12_16.5: init () = converted %.loc12_15.1, %.loc12_15.2 [template = constants.%.4]
 // CHECK:STDOUT:   %.loc12_16.6: init ((), ()) = tuple_init (%.loc12_16.3, %.loc12_16.5) to %return [template = constants.%.5]
 // CHECK:STDOUT:   %.loc12_17: init ((), ()) = converted %.loc12_16.1, %.loc12_16.6 [template = constants.%.5]
-// CHECK:STDOUT:   return %.loc12_17
+// CHECK:STDOUT:   return %.loc12_17 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/basics/raw_ir.carbon

@@ -77,7 +77,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     inst+26:         {kind: TupleInit, arg0: block10, arg1: inst+13, type: type2}
 // CHECK:STDOUT:     inst+27:         {kind: TupleValue, arg0: block12, type: type2}
 // CHECK:STDOUT:     inst+28:         {kind: Converted, arg0: inst+18, arg1: inst+26, type: type2}
-// CHECK:STDOUT:     inst+29:         {kind: ReturnExpr, arg0: inst+28}
+// CHECK:STDOUT:     inst+29:         {kind: ReturnExpr, arg0: inst+28, arg1: inst+13}
 // CHECK:STDOUT:   constant_values:
 // CHECK:STDOUT:     inst+0:          template inst+0
 // CHECK:STDOUT:     inst+1:          template inst+1

+ 1 - 1
toolchain/check/testdata/basics/textual_ir.carbon

@@ -54,6 +54,6 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:   %.loc12_16.5: init () = converted %.loc12_15.1, %.loc12_15.2 [template = constants.%.4]
 // CHECK:STDOUT:   %.loc12_16.6: init ((), ()) = tuple_init (%.loc12_16.3, %.loc12_16.5) to %return [template = constants.%.5]
 // CHECK:STDOUT:   %.loc12_17: init ((), ()) = converted %.loc12_16.1, %.loc12_16.6 [template = constants.%.5]
-// CHECK:STDOUT:   return %.loc12_17
+// CHECK:STDOUT:   return %.loc12_17 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/class/base.carbon

@@ -106,7 +106,7 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT:   %.loc18_35.5: init i32 = initialize_from %.loc18_34 to %.loc18_35.4 [template = constants.%.11]
 // CHECK:STDOUT:   %.loc18_35.6: init Derived = class_init (%.loc18_35.3, %.loc18_35.5), %return [template = constants.%.14]
 // CHECK:STDOUT:   %.loc18_36: init Derived = converted %.loc18_35.1, %.loc18_35.6 [template = constants.%.14]
-// CHECK:STDOUT:   return %.loc18_36
+// CHECK:STDOUT:   return %.loc18_36 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Access(%d: Derived) -> %return: (i32, i32) {
@@ -129,6 +129,6 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT:   %.loc22_24.5: init i32 = initialize_from %.loc22_22.2 to %.loc22_24.4
 // CHECK:STDOUT:   %.loc22_24.6: init (i32, i32) = tuple_init (%.loc22_24.3, %.loc22_24.5) to %return
 // CHECK:STDOUT:   %.loc22_25: init (i32, i32) = converted %.loc22_24.1, %.loc22_24.6
-// CHECK:STDOUT:   return %.loc22_25
+// CHECK:STDOUT:   return %.loc22_25 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/class/fail_abstract.carbon

@@ -99,7 +99,7 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT:   %.loc22_26: {.a: i32} = struct_literal (%.loc22_25)
 // CHECK:STDOUT:   %.loc22_34: i32 = int_literal 7 [template = constants.%.11]
 // CHECK:STDOUT:   %.loc22_35: {.base: {.a: i32}, .d: i32} = struct_literal (%.loc22_26, %.loc22_34)
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Access(%d: Derived) -> %return: (i32, i32) {
@@ -122,6 +122,6 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT:   %.loc26_24.5: init i32 = initialize_from %.loc26_22.2 to %.loc26_24.4
 // CHECK:STDOUT:   %.loc26_24.6: init (i32, i32) = tuple_init (%.loc26_24.3, %.loc26_24.5) to %return
 // CHECK:STDOUT:   %.loc26_25: init (i32, i32) = converted %.loc26_24.1, %.loc26_24.6
-// CHECK:STDOUT:   return %.loc26_25
+// CHECK:STDOUT:   return %.loc26_25 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/class/fail_convert_to_invalid.carbon

@@ -50,6 +50,6 @@ fn Make() -> C {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc15_16: i32 = int_literal 123 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc15_19: {.a: i32} = struct_literal (%.loc15_16)
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/class/fail_self.carbon

@@ -125,7 +125,7 @@ fn CallWrongSelf(ws: WrongSelf) {
 // CHECK:STDOUT:   %self: ref Class = bind_name self, %self.var
 // CHECK:STDOUT:   %self.ref: ref Class = name_ref self, %self
 // CHECK:STDOUT:   %.loc34: Class = bind_value %self.ref
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2[@WrongSelf.%self.loc38_8.2: Class]();

+ 2 - 2
toolchain/check/testdata/class/init.carbon

@@ -83,7 +83,7 @@ fn MakeReorder(n: i32, next: Class*) -> Class {
 // CHECK:STDOUT:   %.loc13_31.5: init Class* = initialize_from %next.ref to %.loc13_31.4
 // CHECK:STDOUT:   %.loc13_31.6: init Class = class_init (%.loc13_31.3, %.loc13_31.5), %return
 // CHECK:STDOUT:   %.loc13_32: init Class = converted %.loc13_31.1, %.loc13_31.6
-// CHECK:STDOUT:   return %.loc13_32
+// CHECK:STDOUT:   return %.loc13_32 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @MakeReorder(%n: i32, %next: Class*) -> %return: Class {
@@ -97,6 +97,6 @@ fn MakeReorder(n: i32, next: Class*) -> Class {
 // CHECK:STDOUT:   %.loc17_31.5: init Class* = initialize_from %next.ref to %.loc17_31.4
 // CHECK:STDOUT:   %.loc17_31.6: init Class = class_init (%.loc17_31.3, %.loc17_31.5), %return
 // CHECK:STDOUT:   %.loc17_32: init Class = converted %.loc17_31.1, %.loc17_31.6
-// CHECK:STDOUT:   return %.loc17_32
+// CHECK:STDOUT:   return %.loc17_32 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/class/init_nested.carbon

@@ -91,6 +91,6 @@ fn MakeOuter() -> Outer {
 // CHECK:STDOUT:   %.loc20_45.3: {.c: Inner, .d: Inner} = struct_literal (%MakeInner.call.loc20_25, %MakeInner.call.loc20_43)
 // CHECK:STDOUT:   %.loc20_45.4: init Outer = class_init (%MakeInner.call.loc20_25, %MakeInner.call.loc20_43), %return
 // CHECK:STDOUT:   %.loc20_46: init Outer = converted %.loc20_45.3, %.loc20_45.4
-// CHECK:STDOUT:   return %.loc20_46
+// CHECK:STDOUT:   return %.loc20_46 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/class/raw_self.carbon

@@ -113,6 +113,6 @@ fn Class.G[self: Self](r#self: i32) -> (i32, i32) {
 // CHECK:STDOUT:   %.loc18_25.5: init i32 = initialize_from %self.ref.loc18_19 to %.loc18_25.4
 // CHECK:STDOUT:   %.loc18_25.6: init (i32, i32) = tuple_init (%.loc18_25.3, %.loc18_25.5) to %return
 // CHECK:STDOUT:   %.loc18_26: init (i32, i32) = converted %.loc18_25.1, %.loc18_25.6
-// CHECK:STDOUT:   return %.loc18_26
+// CHECK:STDOUT:   return %.loc18_26 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/class/self_type.carbon

@@ -94,6 +94,6 @@ fn Class.F[self: Self]() -> i32 {
 // CHECK:STDOUT:   %.loc11_17.4: init Class = class_init (%.loc11_17.3), %s.ref.loc11_5
 // CHECK:STDOUT:   %.loc11_7: init Class = converted %.loc11_17.1, %.loc11_17.4
 // CHECK:STDOUT:   assign %s.ref.loc11_5, %.loc11_7
-// CHECK:STDOUT:   return %s
+// CHECK:STDOUT:   return %s to @Class.%return.var.loc9
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/expr_category/in_place_tuple_init.carbon

@@ -68,7 +68,7 @@ fn H() -> i32 {
 // CHECK:STDOUT:   %F.ref.loc12: <function> = name_ref F, file.%F [template = file.%F]
 // CHECK:STDOUT:   %.loc9: ref (i32, i32) = splice_block %return {}
 // CHECK:STDOUT:   %F.call.loc12: init (i32, i32) = call %F.ref.loc12() to %.loc9
-// CHECK:STDOUT:   return %F.call.loc12
+// CHECK:STDOUT:   return %F.call.loc12 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @H() -> i32 {

+ 1 - 1
toolchain/check/testdata/global/class_with_fun.carbon

@@ -49,7 +49,7 @@ var a: A = {};
 // CHECK:STDOUT:   %.loc9_11.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc9_11.2: init A = class_init (), %return [template = constants.%.4]
 // CHECK:STDOUT:   %.loc9_12: init A = converted %.loc9_11.1, %.loc9_11.2 [template = constants.%.4]
-// CHECK:STDOUT:   return %.loc9_12
+// CHECK:STDOUT:   return %.loc9_12 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {

+ 2 - 2
toolchain/check/testdata/impl/self_in_signature.carbon

@@ -236,7 +236,7 @@ impl D as SelfNested {
 // CHECK:STDOUT:   %.loc16_38.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc16_38.2: init C = class_init (), @impl.1.%return.var [template = constants.%.8]
 // CHECK:STDOUT:   %.loc16_39: init C = converted %.loc16_38.1, %.loc16_38.2 [template = constants.%.8]
-// CHECK:STDOUT:   return %.loc16_39
+// CHECK:STDOUT:   return %.loc16_39 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.3[@impl.2.%self.loc20_8.2: D](@impl.2.%x.loc20_20.2: D) -> @impl.2.%return.var: D {
@@ -244,7 +244,7 @@ impl D as SelfNested {
 // CHECK:STDOUT:   %.loc20_47.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc20_47.2: init D = class_init (), @impl.2.%return.var [template = constants.%.10]
 // CHECK:STDOUT:   %.loc20_48: init D = converted %.loc20_47.1, %.loc20_47.2 [template = constants.%.10]
-// CHECK:STDOUT:   return %.loc20_48
+// CHECK:STDOUT:   return %.loc20_48 to @impl.2.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.4(@SelfNested.%x.loc24_8.2: (Self*, {.x: Self, .y: i32}));

+ 2 - 2
toolchain/check/testdata/operators/overloaded/add.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/bit_and.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/bit_complement.carbon

@@ -147,7 +147,7 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self]() -> Self;
@@ -159,6 +159,6 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   %.loc15_10.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc14: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc15_10.2: init C = call %.loc15_10.1(%a.ref) to %.loc14
-// CHECK:STDOUT:   return %.loc15_10.2
+// CHECK:STDOUT:   return %.loc15_10.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/operators/overloaded/bit_or.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/bit_xor.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/div.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon

@@ -305,7 +305,7 @@ fn TestRef(b: C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %a.ref: C = name_ref a, %a
 // CHECK:STDOUT:   %Negate.decl: type = interface_decl @Negate [template = constants.%.4] {}
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestBinary(%a: C, %b: C) -> %return: C {
@@ -313,7 +313,7 @@ fn TestRef(b: C) {
 // CHECK:STDOUT:   %a.ref: C = name_ref a, %a
 // CHECK:STDOUT:   %b.ref: C = name_ref b, %b
 // CHECK:STDOUT:   %Add.decl: type = interface_decl @Add [template = constants.%.7] {}
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestRef(%b: C) {

+ 1 - 1
toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon

@@ -281,7 +281,7 @@ fn TestAssign(b: D) {
 // CHECK:STDOUT:   %.loc24_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc24_12.2: ref C = temporary_storage
 // CHECK:STDOUT:   %.loc24_12.3: init C = call %.loc24_12.1(<invalid>) [template = <error>]
-// CHECK:STDOUT:   return %.loc24_12.3
+// CHECK:STDOUT:   return %.loc24_12.3 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%b: D) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/left_shift.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/mod.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/mul.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/negate.carbon

@@ -147,7 +147,7 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self]() -> Self;
@@ -159,6 +159,6 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   %.loc15_10.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc14: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc15_10.2: init C = call %.loc15_10.1(%a.ref) to %.loc14
-// CHECK:STDOUT:   return %.loc15_10.2
+// CHECK:STDOUT:   return %.loc15_10.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/operators/overloaded/right_shift.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 2 - 2
toolchain/check/testdata/operators/overloaded/sub.carbon

@@ -255,7 +255,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc10_13.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_13.2: init C = class_init (), @impl.1.%return.var [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14: init C = converted %.loc10_13.1, %.loc10_13.2 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc10_14
+// CHECK:STDOUT:   return %.loc10_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
@@ -275,7 +275,7 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   %.loc18_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc17: ref C = splice_block %return {}
 // CHECK:STDOUT:   %.loc18_12.2: init C = call %.loc18_12.1(%a.ref, %b.ref) to %.loc17
-// CHECK:STDOUT:   return %.loc18_12.2
+// CHECK:STDOUT:   return %.loc18_12.2 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestAssign(%a: C*, %b: C) {

+ 1 - 1
toolchain/check/testdata/return/returned_var.carbon

@@ -74,7 +74,7 @@ fn G() -> i32 {
 // CHECK:STDOUT:   %.loc13_43.6: init C = class_init (%.loc13_43.3, %.loc13_43.5), %return [template = constants.%.6]
 // CHECK:STDOUT:   %.loc13_44: init C = converted %.loc13_43.1, %.loc13_43.6 [template = constants.%.6]
 // CHECK:STDOUT:   assign %return, %.loc13_44
-// CHECK:STDOUT:   return %result
+// CHECK:STDOUT:   return %result to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @G() -> i32 {

+ 1 - 1
toolchain/check/testdata/return/tuple.carbon

@@ -44,6 +44,6 @@ fn Main() -> (i32, i32) {
 // CHECK:STDOUT:   %.loc9_17.5: init i32 = initialize_from %.loc9_15 to %.loc9_17.4 [template = constants.%.5]
 // CHECK:STDOUT:   %.loc9_17.6: init (i32, i32) = tuple_init (%.loc9_17.3, %.loc9_17.5) to %return [template = constants.%.6]
 // CHECK:STDOUT:   %.loc9_18: init (i32, i32) = converted %.loc9_17.1, %.loc9_17.6 [template = constants.%.6]
-// CHECK:STDOUT:   return %.loc9_18
+// CHECK:STDOUT:   return %.loc9_18 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/struct/reorder_fields.carbon

@@ -77,6 +77,6 @@ fn F() -> {.a: i32, .b: f64} {
 // CHECK:STDOUT:   %.loc13_10.6: init f64 = initialize_from %.loc13_10.4 to %.loc13_10.5
 // CHECK:STDOUT:   %.loc13_10.7: init {.a: i32, .b: f64} = struct_init (%.loc13_10.3, %.loc13_10.6) to %return
 // CHECK:STDOUT:   %.loc13_11: init {.a: i32, .b: f64} = converted %y.ref, %.loc13_10.7
-// CHECK:STDOUT:   return %.loc13_11
+// CHECK:STDOUT:   return %.loc13_11 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 18 - 0
toolchain/lower/file_context.cpp

@@ -70,6 +70,7 @@ auto FileContext::GetGlobal(SemIR::InstId inst_id) -> llvm::Value* {
     return GetTypeAsValue();
   }
 
+  // TODO: Add a FunctionValue that FunctionDecl evaluates to, and remove this.
   auto target = sem_ir().insts().Get(inst_id);
   if (auto function_decl = target.TryAs<SemIR::FunctionDecl>()) {
     return GetFunction(function_decl->function_id);
@@ -84,6 +85,23 @@ auto FileContext::GetGlobal(SemIR::InstId inst_id) -> llvm::Value* {
     return GetTypeAsValue();
   }
 
+  auto constant_id = sem_ir().constant_values().Get(inst_id);
+  if (constant_id.is_constant()) {
+    if (auto function_decl = sem_ir().insts().TryGetAs<SemIR::FunctionDecl>(
+            constant_id.inst_id())) {
+      return GetFunction(function_decl->function_id);
+    }
+    auto* value = globals_.lookup(constant_id.inst_id());
+    if (!value) {
+      // TODO: Less-hacky constant lowering.
+      FunctionContext ctx(*this, nullptr, vlog_stream_);
+      ctx.LowerInst(constant_id.inst_id());
+      value = ctx.GetValue(constant_id.inst_id());
+      globals_.insert({constant_id.inst_id(), value});
+    }
+    return value;
+  }
+
   CARBON_FATAL() << "Missing value: " << inst_id << " " << target;
 }
 

+ 36 - 18
toolchain/lower/function_context.cpp

@@ -46,17 +46,25 @@ auto FunctionContext::TryToReuseBlock(SemIR::InstBlockId block_id,
 }
 
 auto FunctionContext::LowerBlock(SemIR::InstBlockId block_id) -> void {
-  for (const auto& inst_id : sem_ir().inst_blocks().Get(block_id)) {
-    auto inst = sem_ir().insts().Get(inst_id);
-    CARBON_VLOG() << "Lowering " << inst_id << ": " << inst << "\n";
-    builder_.getInserter().SetCurrentInstId(inst_id);
-    switch (inst.kind()) {
+  for (auto inst_id : sem_ir().inst_blocks().Get(block_id)) {
+    // Skip over constants. `FileContext::GetGlobal` lowers them as needed.
+    if (sem_ir().constant_values().Get(inst_id).is_constant()) {
+      continue;
+    }
+    LowerInst(inst_id);
+  }
+}
+
+auto FunctionContext::LowerInst(SemIR::InstId inst_id) -> void {
+  auto inst = sem_ir().insts().Get(inst_id);
+  CARBON_VLOG() << "Lowering " << inst_id << ": " << inst << "\n";
+  builder_.getInserter().SetCurrentInstId(inst_id);
+  switch (inst.kind()) {
 #define CARBON_SEM_IR_INST_KIND(Name)                     \
   case SemIR::Name::Kind:                                 \
     Handle##Name(*this, inst_id, inst.As<SemIR::Name>()); \
     break;
 #include "toolchain/sem_ir/inst_kind.def"
-    }
   }
   builder_.getInserter().SetCurrentInstId(SemIR::InstId::Invalid);
 }
@@ -90,7 +98,13 @@ auto FunctionContext::FinishInit(SemIR::TypeId type_id, SemIR::InstId dest_id,
                                  SemIR::InstId source_id) -> void {
   switch (SemIR::GetInitRepr(sem_ir(), type_id).kind) {
     case SemIR::InitRepr::None:
+      break;
     case SemIR::InitRepr::InPlace:
+      if (sem_ir().constant_values().Get(source_id).is_constant()) {
+        // When initializing from a constant, emission of the source doesn't
+        // initialize the destination. Copy the constant value instead.
+        CopyValue(type_id, source_id, dest_id);
+      }
       break;
     case SemIR::InitRepr::ByCopy:
       CopyValue(type_id, source_id, dest_id);
@@ -108,24 +122,28 @@ auto FunctionContext::CopyValue(SemIR::TypeId type_id, SemIR::InstId source_id,
     case SemIR::ValueRepr::Copy:
       builder().CreateStore(GetValue(source_id), GetValue(dest_id));
       break;
-    case SemIR::ValueRepr::Pointer: {
-      const auto& layout = llvm_module().getDataLayout();
-      auto* type = GetType(type_id);
-      // TODO: Compute known alignment of the source and destination, which may
-      // be greater than the alignment computed by LLVM.
-      auto align = layout.getABITypeAlign(type);
-
-      // TODO: Attach !tbaa.struct metadata indicating which portions of the
-      // type we actually need to copy and which are padding.
-      builder().CreateMemCpy(GetValue(dest_id), align, GetValue(source_id),
-                             align, layout.getTypeAllocSize(type));
+    case SemIR::ValueRepr::Pointer:
+      CopyObject(type_id, source_id, dest_id);
       break;
-    }
     case SemIR::ValueRepr::Custom:
       CARBON_FATAL() << "TODO: Add support for CopyValue with custom value rep";
   }
 }
 
+auto FunctionContext::CopyObject(SemIR::TypeId type_id, SemIR::InstId source_id,
+                                 SemIR::InstId dest_id) -> void {
+  const auto& layout = llvm_module().getDataLayout();
+  auto* type = GetType(type_id);
+  // TODO: Compute known alignment of the source and destination, which may
+  // be greater than the alignment computed by LLVM.
+  auto align = layout.getABITypeAlign(type);
+
+  // TODO: Attach !tbaa.struct metadata indicating which portions of the
+  // type we actually need to copy and which are padding.
+  builder().CreateMemCpy(GetValue(dest_id), align, GetValue(source_id), align,
+                         layout.getTypeAllocSize(type));
+}
+
 auto FunctionContext::Inserter::InsertHelper(
     llvm::Instruction* inst, const llvm::Twine& name, llvm::BasicBlock* block,
     llvm::BasicBlock::iterator insert_pt) const -> void {

+ 9 - 0
toolchain/lower/function_context.h

@@ -33,6 +33,9 @@ class FunctionContext {
   // Builds LLVM IR for the sequence of instructions in `block_id`.
   auto LowerBlock(SemIR::InstBlockId block_id) -> void;
 
+  // Builds LLVM IR for the specified instruction.
+  auto LowerInst(SemIR::InstId inst_id) -> void;
+
   // Returns a phi node corresponding to the block argument of the given basic
   // block.
   auto GetBlockArg(SemIR::InstBlockId block_id, SemIR::TypeId type_id)
@@ -129,6 +132,12 @@ class FunctionContext {
   auto CopyValue(SemIR::TypeId type_id, SemIR::InstId source_id,
                  SemIR::InstId dest_id) -> void;
 
+  // Emits an object representation copy for type `type_id` from `source_id` to
+  // `dest_id`. `source_id` and `dest_id` must produce pointers to `type_id`
+  // objects.
+  auto CopyObject(SemIR::TypeId type_id, SemIR::InstId source_id,
+                  SemIR::InstId dest_id) -> void;
+
   // Context for the overall lowering process.
   FileContext* file_context_;
 

+ 6 - 5
toolchain/lower/handle.cpp

@@ -595,15 +595,16 @@ auto HandleReturn(FunctionContext& context, SemIR::InstId /*inst_id*/,
 
 auto HandleReturnExpr(FunctionContext& context, SemIR::InstId /*inst_id*/,
                       SemIR::ReturnExpr inst) -> void {
-  switch (
-      SemIR::GetInitRepr(context.sem_ir(),
-                         context.sem_ir().insts().Get(inst.expr_id).type_id())
-          .kind) {
+  auto result_type_id = context.sem_ir().insts().Get(inst.expr_id).type_id();
+  switch (SemIR::GetInitRepr(context.sem_ir(), result_type_id).kind) {
     case SemIR::InitRepr::None:
-    case SemIR::InitRepr::InPlace:
       // Nothing to return.
       context.builder().CreateRetVoid();
       return;
+    case SemIR::InitRepr::InPlace:
+      context.FinishInit(result_type_id, inst.dest_id, inst.expr_id);
+      context.builder().CreateRetVoid();
+      return;
     case SemIR::InitRepr::ByCopy:
       // The expression produces the value representation for the type.
       context.builder().CreateRet(context.GetValue(inst.expr_id));

+ 40 - 11
toolchain/lower/handle_aggregates.cpp

@@ -208,18 +208,47 @@ auto EmitAggregateValueRepr(FunctionContext& context, SemIR::TypeId type_id,
       auto pointee_type_id = context.sem_ir().GetPointeeType(value_rep.type_id);
       auto* llvm_value_rep_type = context.GetType(pointee_type_id);
 
-      // Write the value representation to a local alloca so we can produce a
-      // pointer to it as the value representation of the struct or tuple.
-      auto* alloca =
-          context.builder().CreateAlloca(llvm_value_rep_type,
-                                         /*ArraySize=*/nullptr, name);
-      for (auto [i, ref] :
-           llvm::enumerate(context.sem_ir().inst_blocks().Get(refs_id))) {
-        context.builder().CreateStore(
-            context.GetValue(ref),
-            context.builder().CreateStructGEP(llvm_value_rep_type, alloca, i));
+      auto refs = context.sem_ir().inst_blocks().Get(refs_id);
+      if (context.builder().GetInsertBlock()) {
+        // Write the value representation to a local alloca so we can produce a
+        // pointer to it as the value representation of the struct or tuple.
+        auto* alloca =
+            context.builder().CreateAlloca(llvm_value_rep_type,
+                                           /*ArraySize=*/nullptr, name);
+        for (auto [i, ref] : llvm::enumerate(refs)) {
+          context.builder().CreateStore(context.GetValue(ref),
+                                        context.builder().CreateStructGEP(
+                                            llvm_value_rep_type, alloca, i));
+        }
+        return alloca;
+      } else {
+        // TODO: Move this out to a separate constant lowering file.
+        llvm::SmallVector<llvm::Constant*> elements;
+        elements.reserve(refs.size());
+        for (auto ref : refs) {
+          auto ref_value_rep = SemIR::GetValueRepr(
+              context.sem_ir(), context.sem_ir().insts().Get(ref).type_id());
+          auto* inner_value = llvm::cast<llvm::Constant>(context.GetValue(ref));
+          if (ref_value_rep.kind == SemIR::ValueRepr::Pointer) {
+            inner_value =
+                llvm::cast<llvm::GlobalVariable>(inner_value)->getInitializer();
+          }
+          elements.push_back(inner_value);
+        }
+        llvm::Constant* value;
+        if (auto* struct_type =
+                llvm::dyn_cast<llvm::StructType>(llvm_value_rep_type)) {
+          value = llvm::ConstantStruct::get(struct_type, elements);
+        } else if (auto* array_type =
+                       llvm::dyn_cast<llvm::ArrayType>(llvm_value_rep_type)) {
+          value = llvm::ConstantArray::get(array_type, elements);
+        } else {
+          CARBON_FATAL() << "Unknown aggregate value representation";
+        }
+        return new llvm::GlobalVariable(
+            context.llvm_module(), llvm_value_rep_type, /*isConstant=*/true,
+            llvm::GlobalVariable::InternalLinkage, value, name);
       }
-      return alloca;
     }
 
     case SemIR::ValueRepr::Custom:

+ 8 - 2
toolchain/lower/testdata/array/assign_return_value.carbon

@@ -13,12 +13,13 @@ fn Run() {
 // CHECK:STDOUT: ; ModuleID = 'assign_return_value.carbon'
 // CHECK:STDOUT: source_filename = "assign_return_value.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32 } { i32 12, i32 24 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @F(ptr sret({ i32, i32 }) %return) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc7_38.2.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 12, ptr %.loc7_38.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc7_38.4.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 24, ptr %.loc7_38.4.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @tuple, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -37,3 +38,8 @@ fn Run() {
 // CHECK:STDOUT:   store i32 %.loc10_22.9, ptr %.loc10_22.11.array.index, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 20 - 6
toolchain/lower/testdata/array/base.carbon

@@ -15,29 +15,32 @@ fn Run() {
 // CHECK:STDOUT: ; ModuleID = 'base.carbon'
 // CHECK:STDOUT: source_filename = "base.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant [1 x i32] [i32 1]
+// CHECK:STDOUT: @tuple.1 = internal constant [2 x double] [double 0x4026333333333334, double 2.200000e+00]
+// CHECK:STDOUT: @tuple.2 = internal constant [5 x {}] poison
+// CHECK:STDOUT: @tuple.3 = internal constant { i32, i32, i32 } { i32 1, i32 2, i32 3 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %a.var = alloca [1 x i32], align 4
 // CHECK:STDOUT:   %.loc8_24.3.array.index = getelementptr inbounds [1 x i32], ptr %a.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc8_24.3.array.index, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a.var, ptr align 4 @tuple, i64 4, i1 false)
 // CHECK:STDOUT:   %b.var = alloca [2 x double], align 8
 // CHECK:STDOUT:   %.loc9_32.3.array.index = getelementptr inbounds [2 x double], ptr %b.var, i32 0, i32 0
-// CHECK:STDOUT:   store double 0x4026333333333334, ptr %.loc9_32.3.array.index, align 8
 // CHECK:STDOUT:   %.loc9_32.6.array.index = getelementptr inbounds [2 x double], ptr %b.var, i32 0, i32 1
-// CHECK:STDOUT:   store double 2.200000e+00, ptr %.loc9_32.6.array.index, align 8
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %b.var, ptr align 8 @tuple.1, i64 16, i1 false)
 // CHECK:STDOUT:   %c.var = alloca [5 x {}], align 8
 // CHECK:STDOUT:   %.loc10_40.3.array.index = getelementptr inbounds [5 x {}], ptr %c.var, i32 0, i32 0
 // CHECK:STDOUT:   %.loc10_40.6.array.index = getelementptr inbounds [5 x {}], ptr %c.var, i32 0, i32 1
 // CHECK:STDOUT:   %.loc10_40.9.array.index = getelementptr inbounds [5 x {}], ptr %c.var, i32 0, i32 2
 // CHECK:STDOUT:   %.loc10_40.12.array.index = getelementptr inbounds [5 x {}], ptr %c.var, i32 0, i32 3
 // CHECK:STDOUT:   %.loc10_40.15.array.index = getelementptr inbounds [5 x {}], ptr %c.var, i32 0, i32 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %c.var, ptr align 1 @tuple.2, i64 0, i1 false)
 // CHECK:STDOUT:   %d.var = alloca { i32, i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc11_36.2.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %d.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc11_36.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc11_36.4.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %d.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc11_36.4.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc11_36.6.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %d.var, i32 0, i32 2
-// CHECK:STDOUT:   store i32 3, ptr %.loc11_36.6.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %d.var, ptr align 4 @tuple.3, i64 12, i1 false)
 // CHECK:STDOUT:   %e.var = alloca [3 x i32], align 4
 // CHECK:STDOUT:   %.loc12_21.1.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %d.var, i32 0, i32 0
 // CHECK:STDOUT:   %.loc12_21.2 = load i32, ptr %.loc12_21.1.tuple.elem, align 4
@@ -53,3 +56,14 @@ fn Run() {
 // CHECK:STDOUT:   store i32 %.loc12_21.12, ptr %.loc12_21.14.array.index, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 3 }
+// CHECK:STDOUT: uselistorder i32 2, { 0, 1, 3, 4, 2 }
+// CHECK:STDOUT: uselistorder i32 3, { 1, 0 }
+// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 3, 2, 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 8 - 3
toolchain/lower/testdata/array/function_param.carbon

@@ -15,6 +15,8 @@ fn G() -> i32 {
 // CHECK:STDOUT: ; ModuleID = 'function_param.carbon'
 // CHECK:STDOUT: source_filename = "function_param.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant [3 x i32] [i32 1, i32 2, i32 3]
+// CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @F(ptr %arr, i32 %i) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc8_15.2.array.index = getelementptr inbounds [3 x i32], ptr %arr, i32 0, i32 %i
@@ -26,11 +28,14 @@ fn G() -> i32 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc12_20.2.temp = alloca [3 x i32], align 4
 // CHECK:STDOUT:   %.loc12_20.4.array.index = getelementptr inbounds [3 x i32], ptr %.loc12_20.2.temp, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc12_20.4.array.index, align 4
 // CHECK:STDOUT:   %.loc12_20.7.array.index = getelementptr inbounds [3 x i32], ptr %.loc12_20.2.temp, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc12_20.7.array.index, align 4
 // CHECK:STDOUT:   %.loc12_20.10.array.index = getelementptr inbounds [3 x i32], ptr %.loc12_20.2.temp, i32 0, i32 2
-// CHECK:STDOUT:   store i32 3, ptr %.loc12_20.10.array.index, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %.loc12_20.2.temp, ptr align 4 @tuple, i64 12, i1 false)
 // CHECK:STDOUT:   %F.call = call i32 @F(ptr %.loc12_20.2.temp, i32 1)
 // CHECK:STDOUT:   ret i32 %F.call
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 13 - 10
toolchain/lower/testdata/basics/numeric_literals.carbon

@@ -26,29 +26,32 @@ fn F() {
 // CHECK:STDOUT: ; ModuleID = 'numeric_literals.carbon'
 // CHECK:STDOUT: source_filename = "numeric_literals.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant [4 x i32] [i32 8, i32 9, i32 8, i32 8]
+// CHECK:STDOUT: @tuple.1 = internal constant [6 x double] [double 9.000000e-01, double 8.000000e+00, double 8.000000e+01, double 1.000000e+07, double 1.000000e+08, double 1.000000e-08]
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @F() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %ints.var = alloca [4 x i32], align 4
 // CHECK:STDOUT:   %.loc15_3.3.array.index = getelementptr inbounds [4 x i32], ptr %ints.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 8, ptr %.loc15_3.3.array.index, align 4
 // CHECK:STDOUT:   %.loc15_3.6.array.index = getelementptr inbounds [4 x i32], ptr %ints.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 9, ptr %.loc15_3.6.array.index, align 4
 // CHECK:STDOUT:   %.loc15_3.9.array.index = getelementptr inbounds [4 x i32], ptr %ints.var, i32 0, i32 2
-// CHECK:STDOUT:   store i32 8, ptr %.loc15_3.9.array.index, align 4
 // CHECK:STDOUT:   %.loc15_3.12.array.index = getelementptr inbounds [4 x i32], ptr %ints.var, i32 0, i32 3
-// CHECK:STDOUT:   store i32 8, ptr %.loc15_3.12.array.index, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %ints.var, ptr align 4 @tuple, i64 16, i1 false)
 // CHECK:STDOUT:   %floats.var = alloca [6 x double], align 8
 // CHECK:STDOUT:   %.loc23_3.3.array.index = getelementptr inbounds [6 x double], ptr %floats.var, i32 0, i32 0
-// CHECK:STDOUT:   store double 9.000000e-01, ptr %.loc23_3.3.array.index, align 8
 // CHECK:STDOUT:   %.loc23_3.6.array.index = getelementptr inbounds [6 x double], ptr %floats.var, i32 0, i32 1
-// CHECK:STDOUT:   store double 8.000000e+00, ptr %.loc23_3.6.array.index, align 8
 // CHECK:STDOUT:   %.loc23_3.9.array.index = getelementptr inbounds [6 x double], ptr %floats.var, i32 0, i32 2
-// CHECK:STDOUT:   store double 8.000000e+01, ptr %.loc23_3.9.array.index, align 8
 // CHECK:STDOUT:   %.loc23_3.12.array.index = getelementptr inbounds [6 x double], ptr %floats.var, i32 0, i32 3
-// CHECK:STDOUT:   store double 1.000000e+07, ptr %.loc23_3.12.array.index, align 8
 // CHECK:STDOUT:   %.loc23_3.15.array.index = getelementptr inbounds [6 x double], ptr %floats.var, i32 0, i32 4
-// CHECK:STDOUT:   store double 1.000000e+08, ptr %.loc23_3.15.array.index, align 8
 // CHECK:STDOUT:   %.loc23_3.18.array.index = getelementptr inbounds [6 x double], ptr %floats.var, i32 0, i32 5
-// CHECK:STDOUT:   store double 1.000000e-08, ptr %.loc23_3.18.array.index, align 8
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %floats.var, ptr align 8 @tuple.1, i64 48, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 11 - 2
toolchain/lower/testdata/class/adapt.carbon

@@ -50,12 +50,13 @@ fn DoStuff(a: Int) -> Int {
 // CHECK:STDOUT: ; ModuleID = 'adapt_class.carbon'
 // CHECK:STDOUT: source_filename = "adapt_class.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @struct = internal constant { i32, i32 } { i32 1, i32 2 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @Make(ptr sret({ i32, i32 }) %return) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc9_27.2.a = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc9_27.2.a, align 4
 // CHECK:STDOUT:   %.loc9_27.4.b = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc9_27.4.b, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @struct, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -79,6 +80,14 @@ fn DoStuff(a: Int) -> Int {
 // CHECK:STDOUT:   %.loc28_17 = call i32 @GetB(ptr %pa.var)
 // CHECK:STDOUT:   ret i32 %.loc28_17
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 0, 1, 3, 2 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
 // CHECK:STDOUT: ; ModuleID = 'adapt_int.carbon'
 // CHECK:STDOUT: source_filename = "adapt_int.carbon"
 // CHECK:STDOUT:

+ 9 - 2
toolchain/lower/testdata/class/base.carbon

@@ -29,13 +29,15 @@ fn Convert(p: Derived*) -> Base* {
 // CHECK:STDOUT: ; ModuleID = 'base.carbon'
 // CHECK:STDOUT: source_filename = "base.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @struct = internal constant { i32 } { i32 4 }
+// CHECK:STDOUT: @struct.1 = internal constant { { i32 }, i32 } { { i32 } { i32 4 }, i32 7 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @Make(ptr sret({ { i32 }, i32 }) %return) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc18_35.2.base = getelementptr inbounds { { i32 }, i32 }, ptr %return, i32 0, i32 0
 // CHECK:STDOUT:   %.loc18_26.2.b = getelementptr inbounds { i32 }, ptr %.loc18_35.2.base, i32 0, i32 0
-// CHECK:STDOUT:   store i32 4, ptr %.loc18_26.2.b, align 4
 // CHECK:STDOUT:   %.loc18_35.4.d = getelementptr inbounds { { i32 }, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 7, ptr %.loc18_35.4.d, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @struct.1, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -58,3 +60,8 @@ fn Convert(p: Derived*) -> Base* {
 // CHECK:STDOUT:   %.loc26_11.2.base = getelementptr inbounds { { i32 }, i32 }, ptr %p, i32 0, i32 0
 // CHECK:STDOUT:   ret ptr %.loc26_11.2.base
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 3 - 9
toolchain/lower/testdata/function/call/struct_param.carbon

@@ -13,6 +13,8 @@ fn Main() {
 // CHECK:STDOUT: ; ModuleID = 'struct_param.carbon'
 // CHECK:STDOUT: source_filename = "struct_param.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @struct = internal constant { i32, i32 } { i32 2, i32 3 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @F({ i32 } %b, ptr %c) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret void
@@ -20,14 +22,6 @@ fn Main() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @Main() {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc10_34.2.struct = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   %.loc10_34.2 = getelementptr inbounds { i32, i32 }, ptr %.loc10_34.2.struct, i32 0, i32 0
-// CHECK:STDOUT:   store i32 2, ptr %.loc10_34.2, align 4
-// CHECK:STDOUT:   %.loc10_34.21 = getelementptr inbounds { i32, i32 }, ptr %.loc10_34.2.struct, i32 0, i32 1
-// CHECK:STDOUT:   store i32 3, ptr %.loc10_34.21, align 4
-// CHECK:STDOUT:   call void @F({ i32 } { i32 1 }, ptr %.loc10_34.2.struct)
+// CHECK:STDOUT:   call void @F({ i32 } { i32 1 }, ptr @struct)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: ; uselistorder directives
-// CHECK:STDOUT: uselistorder i32 1, { 2, 0, 1 }

+ 3 - 9
toolchain/lower/testdata/function/call/tuple_param.carbon

@@ -13,6 +13,8 @@ fn Main() {
 // CHECK:STDOUT: ; ModuleID = 'tuple_param.carbon'
 // CHECK:STDOUT: source_filename = "tuple_param.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32 } { i32 2, i32 3 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @F({ i32 } %b, ptr %c) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret void
@@ -20,14 +22,6 @@ fn Main() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @Main() {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %.loc10_20.2.tuple = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   %.loc10_20.2 = getelementptr inbounds { i32, i32 }, ptr %.loc10_20.2.tuple, i32 0, i32 0
-// CHECK:STDOUT:   store i32 2, ptr %.loc10_20.2, align 4
-// CHECK:STDOUT:   %.loc10_20.21 = getelementptr inbounds { i32, i32 }, ptr %.loc10_20.2.tuple, i32 0, i32 1
-// CHECK:STDOUT:   store i32 3, ptr %.loc10_20.21, align 4
-// CHECK:STDOUT:   call void @F({ i32 } { i32 1 }, ptr %.loc10_20.2.tuple)
+// CHECK:STDOUT:   call void @F({ i32 } { i32 1 }, ptr @tuple)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: ; uselistorder directives
-// CHECK:STDOUT: uselistorder i32 1, { 2, 0, 1 }

+ 4 - 7
toolchain/lower/testdata/function/call/tuple_param_with_return_slot.carbon

@@ -15,6 +15,8 @@ fn Main() {
 // CHECK:STDOUT: ; ModuleID = 'tuple_param_with_return_slot.carbon'
 // CHECK:STDOUT: source_filename = "tuple_param_with_return_slot.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32 } { i32 2, i32 3 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @F(ptr sret({ i32, i32, i32 }) %return, { i32 } %b, ptr %c) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc8_14.tuple.index = extractvalue { i32 } %b, 0
@@ -34,14 +36,9 @@ fn Main() {
 // CHECK:STDOUT: define void @Main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc12_4.1.temp = alloca { i32, i32, i32 }, align 8
-// CHECK:STDOUT:   %.loc12_20.2.tuple = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   %.loc12_20.2 = getelementptr inbounds { i32, i32 }, ptr %.loc12_20.2.tuple, i32 0, i32 0
-// CHECK:STDOUT:   store i32 2, ptr %.loc12_20.2, align 4
-// CHECK:STDOUT:   %.loc12_20.21 = getelementptr inbounds { i32, i32 }, ptr %.loc12_20.2.tuple, i32 0, i32 1
-// CHECK:STDOUT:   store i32 3, ptr %.loc12_20.21, align 4
-// CHECK:STDOUT:   call void @F(ptr %.loc12_4.1.temp, { i32 } { i32 1 }, ptr %.loc12_20.2.tuple)
+// CHECK:STDOUT:   call void @F(ptr %.loc12_4.1.temp, { i32 } { i32 1 }, ptr @tuple)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; uselistorder directives
-// CHECK:STDOUT: uselistorder i32 1, { 2, 0, 1, 3, 4, 5 }
+// CHECK:STDOUT: uselistorder i32 2, { 1, 0 }

+ 14 - 4
toolchain/lower/testdata/index/array_element_access.carbon

@@ -17,21 +17,22 @@ fn Run() {
 // CHECK:STDOUT: ; ModuleID = 'array_element_access.carbon'
 // CHECK:STDOUT: source_filename = "array_element_access.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32 } { i32 1, i32 2 }
+// CHECK:STDOUT: @tuple.1 = internal constant [2 x i32] [i32 1, i32 2]
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @A(ptr sret({ i32, i32 }) %return) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc6_36.2.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc6_36.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc6_36.4.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc6_36.4.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @tuple, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @B(ptr sret([2 x i32]) %return) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc8_34.3.array.index = getelementptr inbounds [2 x i32], ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc8_34.3.array.index, align 4
 // CHECK:STDOUT:   %.loc8_34.6.array.index = getelementptr inbounds [2 x i32], ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc8_34.6.array.index, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @tuple.1, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -67,3 +68,12 @@ fn Run() {
 // CHECK:STDOUT:   store i32 %.loc14_21.2, ptr %d.var, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 11 }
+// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 13 - 3
toolchain/lower/testdata/index/tuple_element_access.carbon

@@ -14,15 +14,15 @@ fn Run() -> i32 {
 // CHECK:STDOUT: ; ModuleID = 'tuple_element_access.carbon'
 // CHECK:STDOUT: source_filename = "tuple_element_access.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32, i32 } { i32 0, i32 1, i32 2 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %a.var = alloca { i32, i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc8_36.2.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 0, ptr %.loc8_36.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc8_36.4.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 1, ptr %.loc8_36.4.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc8_36.6.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 2
-// CHECK:STDOUT:   store i32 2, ptr %.loc8_36.6.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a.var, ptr align 4 @tuple, i64 12, i1 false)
 // CHECK:STDOUT:   %b.var = alloca i32, align 4
 // CHECK:STDOUT:   %.loc9_19.1.tuple.index = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 0
 // CHECK:STDOUT:   %.loc9_19.2 = load i32, ptr %.loc9_19.1.tuple.index, align 4
@@ -33,3 +33,13 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 %.loc10_19.2, ptr %c.var, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 0, { 0, 1, 2, 3, 5, 6, 7, 8, 4 }
+// CHECK:STDOUT: uselistorder i32 1, { 0, 1, 3, 4, 2 }
+// CHECK:STDOUT: uselistorder i32 2, { 0, 2, 1 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 8 - 2
toolchain/lower/testdata/index/tuple_return_value_access.carbon

@@ -13,12 +13,13 @@ fn Run() {
 // CHECK:STDOUT: ; ModuleID = 'tuple_return_value_access.carbon'
 // CHECK:STDOUT: source_filename = "tuple_return_value_access.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32 } { i32 12, i32 24 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @F(ptr sret({ i32, i32 }) %return) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc7_38.2.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 12, ptr %.loc7_38.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc7_38.4.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
-// CHECK:STDOUT:   store i32 24, ptr %.loc7_38.4.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 @tuple, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -32,3 +33,8 @@ fn Run() {
 // CHECK:STDOUT:   store i32 %.loc10_21.2, ptr %t.var, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 15 - 5
toolchain/lower/testdata/let/tuple.carbon

@@ -14,20 +14,20 @@ fn F() -> i32 {
 // CHECK:STDOUT: ; ModuleID = 'tuple.carbon'
 // CHECK:STDOUT: source_filename = "tuple.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32, i32 } { i32 1, i32 2, i32 3 }
+// CHECK:STDOUT: @tuple.1 = internal constant { i32, i32 } { i32 4, i32 5 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @F() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %a.var = alloca { i32, i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc8_36.2.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc8_36.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc8_36.4.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc8_36.4.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc8_36.6.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 2
-// CHECK:STDOUT:   store i32 3, ptr %.loc8_36.6.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a.var, ptr align 4 @tuple, i64 12, i1 false)
 // CHECK:STDOUT:   %b.var = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc9_28.2.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %b.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 4, ptr %.loc9_28.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc9_28.4.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %b.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 5, ptr %.loc9_28.4.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %b.var, ptr align 4 @tuple.1, i64 8, i1 false)
 // CHECK:STDOUT:   %.loc10_43.1.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 0
 // CHECK:STDOUT:   %.loc10_43.2 = load i32, ptr %.loc10_43.1.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc10_43.3.tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a.var, i32 0, i32 1
@@ -61,3 +61,13 @@ fn F() -> i32 {
 // CHECK:STDOUT:   %.loc11_16.tuple.index.load = load i32, ptr %.loc11_16.tuple.index, align 4
 // CHECK:STDOUT:   ret i32 %.loc11_16.tuple.index.load
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 12 }
+// CHECK:STDOUT: uselistorder i32 2, { 0, 1, 3, 2 }
+// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 11 - 2
toolchain/lower/testdata/operators/assignment.carbon

@@ -14,6 +14,8 @@ fn Main() {
 // CHECK:STDOUT: ; ModuleID = 'assignment.carbon'
 // CHECK:STDOUT: source_filename = "assignment.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32 } { i32 1, i32 2 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @Main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %a.var = alloca i32, align 4
@@ -21,8 +23,15 @@ fn Main() {
 // CHECK:STDOUT:   store i32 9, ptr %a.var, align 4
 // CHECK:STDOUT:   %b.var = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc11_12.2.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %b.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc11_12.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc11_12.4.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %b.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc11_12.4.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %b.var, ptr align 4 @tuple, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 1, 2, 3, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 11 - 2
toolchain/lower/testdata/pointer/address_of_field.carbon

@@ -14,16 +14,25 @@ fn F() {
 // CHECK:STDOUT: ; ModuleID = 'address_of_field.carbon'
 // CHECK:STDOUT: source_filename = "address_of_field.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @struct = internal constant { i32, i32 } { i32 1, i32 2 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: declare void @G(ptr)
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @F() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %s.var = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc10_46.2.a = getelementptr inbounds { i32, i32 }, ptr %s.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc10_46.2.a, align 4
 // CHECK:STDOUT:   %.loc10_46.4.b = getelementptr inbounds { i32, i32 }, ptr %s.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc10_46.4.b, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %s.var, ptr align 4 @struct, i64 8, i1 false)
 // CHECK:STDOUT:   %.loc11_7.b = getelementptr inbounds { i32, i32 }, ptr %s.var, i32 0, i32 1
 // CHECK:STDOUT:   call void @G(ptr %.loc11_7.b)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 0, 2, 3, 1 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 0 - 1
toolchain/lower/testdata/return/return_var.carbon

@@ -21,7 +21,6 @@ fn Make() -> C {
 // CHECK:STDOUT: define void @Make(ptr sret({ i32, ptr }) %return) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc14_30.2.data = getelementptr inbounds { i32, ptr }, ptr %return, i32 0, i32 0
-// CHECK:STDOUT:   store i32 42, ptr %.loc14_30.2.data, align 4
 // CHECK:STDOUT:   %.loc14_30.4.next = getelementptr inbounds { i32, ptr }, ptr %return, i32 0, i32 1
 // CHECK:STDOUT:   store ptr %return, ptr %.loc14_30.4.next, align 8
 // CHECK:STDOUT:   ret void

+ 11 - 2
toolchain/lower/testdata/struct/member_access.carbon

@@ -14,13 +14,14 @@ fn Run() -> i32 {
 // CHECK:STDOUT: ; ModuleID = 'member_access.carbon'
 // CHECK:STDOUT: source_filename = "member_access.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @struct = internal constant { double, i32 } { double 0.000000e+00, i32 1 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %x.var = alloca { double, i32 }, align 8
 // CHECK:STDOUT:   %.loc8_48.2.a = getelementptr inbounds { double, i32 }, ptr %x.var, i32 0, i32 0
-// CHECK:STDOUT:   store double 0.000000e+00, ptr %.loc8_48.2.a, align 8
 // CHECK:STDOUT:   %.loc8_48.4.b = getelementptr inbounds { double, i32 }, ptr %x.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 1, ptr %.loc8_48.4.b, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %x.var, ptr align 8 @struct, i64 16, i1 false)
 // CHECK:STDOUT:   %y.var = alloca i32, align 4
 // CHECK:STDOUT:   %.loc9_17.1.b = getelementptr inbounds { double, i32 }, ptr %x.var, i32 0, i32 1
 // CHECK:STDOUT:   %.loc9_17.2 = load i32, ptr %.loc9_17.1.b, align 4
@@ -30,3 +31,11 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 %.loc10, ptr %z.var, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 0, 1, 2, 4, 5, 3 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 11 - 2
toolchain/lower/testdata/struct/two_entries.carbon

@@ -13,13 +13,14 @@ fn Run() -> i32 {
 // CHECK:STDOUT: ; ModuleID = 'two_entries.carbon'
 // CHECK:STDOUT: source_filename = "two_entries.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @struct = internal constant { i32, i32 } { i32 1, i32 2 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %x.var = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc8_46.2.a = getelementptr inbounds { i32, i32 }, ptr %x.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 1, ptr %.loc8_46.2.a, align 4
 // CHECK:STDOUT:   %.loc8_46.4.b = getelementptr inbounds { i32, i32 }, ptr %x.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 2, ptr %.loc8_46.4.b, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x.var, ptr align 4 @struct, i64 8, i1 false)
 // CHECK:STDOUT:   %y.var = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc9_31.1.a = getelementptr inbounds { i32, i32 }, ptr %x.var, i32 0, i32 0
 // CHECK:STDOUT:   %.loc9_31.2 = load i32, ptr %.loc9_31.1.a, align 4
@@ -31,3 +32,11 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 %.loc9_31.6, ptr %.loc9_31.7.b, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 1, { 0, 1, 2, 4, 5, 3 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 8 - 2
toolchain/lower/testdata/tuple/two_entries.carbon

@@ -13,13 +13,14 @@ fn Run() -> i32 {
 // CHECK:STDOUT: ; ModuleID = 'two_entries.carbon'
 // CHECK:STDOUT: source_filename = "two_entries.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @tuple = internal constant { i32, i32 } { i32 12, i32 7 }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define i32 @main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %x.var = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc8_29.2.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %x.var, i32 0, i32 0
-// CHECK:STDOUT:   store i32 12, ptr %.loc8_29.2.tuple.elem, align 4
 // CHECK:STDOUT:   %.loc8_29.4.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %x.var, i32 0, i32 1
-// CHECK:STDOUT:   store i32 7, ptr %.loc8_29.4.tuple.elem, align 4
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x.var, ptr align 4 @tuple, i64 8, i1 false)
 // CHECK:STDOUT:   %y.var = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   %.loc9_23.1.tuple.elem = getelementptr inbounds { i32, i32 }, ptr %x.var, i32 0, i32 0
 // CHECK:STDOUT:   %.loc9_23.2 = load i32, ptr %.loc9_23.1.tuple.elem, align 4
@@ -31,3 +32,8 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 %.loc9_23.6, ptr %.loc9_23.7.tuple.elem, align 4
 // CHECK:STDOUT:   ret i32 0
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 7 - 0
toolchain/sem_ir/formatter.cpp

@@ -577,6 +577,13 @@ class Formatter {
     FormatReturnSlot(inst.dest_id);
   }
 
+  auto FormatInstructionRHS(ReturnExpr ret) -> void {
+    FormatArgs(ret.expr_id);
+    if (ret.dest_id.is_valid()) {
+      FormatReturnSlot(ret.dest_id);
+    }
+  }
+
   auto FormatInstructionRHS(StructInit init) -> void {
     FormatArgs(init.elements_id);
     FormatReturnSlot(init.dest_id);

+ 2 - 0
toolchain/sem_ir/typed_insts.h

@@ -690,6 +690,8 @@ struct ReturnExpr {
 
   // This is a statement, so has no type.
   InstId expr_id;
+  // The return slot, if any. Invalid if we're not returning through memory.
+  InstId dest_id;
 };
 
 struct SpliceBlock {