Przeglądaj źródła

Retain information about how an initializing expression is used in semantics IR (#3173)

Switch from a fully lowering-oriented approach to initializing
expressions in semantics IR to an approach that retains more information
about the high-level semantics of the program. Specifically:

- Always form an `Assign` node for a variable initialization or
assignment, even for empty types and when the actual initialization is
performed in-place by evaluating the right-hand side.
- Always form a `ReturnExpression` node for a return statement with an
expression, even when the actual initialization is performed in-place
into the return slot and the function doesn't return a value.

This is intended to permit us to more easily write semantic checks over
the IR, and to preserve a use edge from the use of an initializing
expression and its initializer in all cases.

To better support this, `MaterializeTemporary` is also split into two
nodes:

- A `TemporaryStorage` node allocates and provides the storage for a
temporary, and is emitted prior to emitting the initialization of the
temporary.
- A `Temporary` node connects the temporary storage to the initializer,
and is emitted after emitting the initialization.

Example testcase:

```carbon
fn F() -> (i32, i32);

fn G() -> (i32, i32) {
  var v: (i32, i32) = F();
  v = F();
  return F();
}

fn H() -> i32 {
  return G()[0];
}
```

Before:

```carbon-semir
fn @G() -> %return: (i32, i32) {
  // ...
  %v: (i32, i32) = var "v"
  %.loc4_24: (i32, i32) = call @F() to %v
  %.loc5: (i32, i32) = call @F() to %v
  %.loc6: (i32, i32) = call @F() to %return
  return
}

fn @H() -> i32 {
!entry:
  %.loc10_11.1: (i32, i32) = materialize_temporary
  %.loc10_11.2: (i32, i32) = call @G() to %.loc10_11.1
  %.loc10_14: i32 = int_literal 0
  %.loc10_15.1: i32 = tuple_index %.loc10_11.1, %.loc10_14
  %.loc10_15.2: i32 = bind_value %.loc10_15.1
  return %.loc10_15.2
}
```

After:

```carbon-semir
fn @G() -> %return: (i32, i32) {
  // ...
  %v: (i32, i32) = var "v"
  %.loc4_24: (i32, i32) = call @F() to %v
  assign %v, %.loc4_24
  %.loc5: (i32, i32) = call @F() to %v
  assign %v, %.loc5
  %.loc6: (i32, i32) = call @F() to %return
  return %.loc6
}

fn @H() -> i32 {
!entry:
  %.loc10_11.1: (i32, i32) = temporary_storage
  %.loc10_11.2: (i32, i32) = call @G() to %.loc10_11.1
  %.loc10_14: i32 = int_literal 0
  %.loc10_11.3: (i32, i32) = temporary %.loc10_11.1, %.loc10_11.2
  %.loc10_15.1: i32 = tuple_index %.loc10_11.3, %.loc10_14
  %.loc10_15.2: i32 = bind_value %.loc10_15.1
  return %.loc10_15.2
}
```

Note that in the "Before" code, there is no indication in `G()` that we
assigned to `v`, and no path through "use" edges from the tuple indexing
in `H()` to the call to `G()`.

This change results in our not performing `memcpy`s into the destination
for tuple and struct initializers. This is a consequence of those
initializers not yet performing in-place initialization, and will be
fixed in a subsequent change.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 2 lat temu
rodzic
commit
e41a165359
37 zmienionych plików z 432 dodań i 284 usunięć
  1. 40 0
      toolchain/lowering/lowering_function_context.cpp
  2. 13 0
      toolchain/lowering/lowering_function_context.h
  3. 15 32
      toolchain/lowering/lowering_handle.cpp
  4. 9 2
      toolchain/lowering/lowering_handle_expression_category.cpp
  5. 0 10
      toolchain/lowering/testdata/array/assign_return_value.carbon
  6. 0 13
      toolchain/lowering/testdata/array/base.carbon
  7. 0 6
      toolchain/lowering/testdata/function/call/tuple_param_with_return_slot.carbon
  8. 0 11
      toolchain/lowering/testdata/index/array_element_access.carbon
  9. 0 6
      toolchain/lowering/testdata/index/tuple_element_access.carbon
  10. 0 6
      toolchain/lowering/testdata/index/tuple_return_value_access.carbon
  11. 0 6
      toolchain/lowering/testdata/operators/assignment.carbon
  12. 0 6
      toolchain/lowering/testdata/pointer/address_of_field.carbon
  13. 0 6
      toolchain/lowering/testdata/struct/member_access.carbon
  14. 0 10
      toolchain/lowering/testdata/struct/two_entries.carbon
  15. 0 10
      toolchain/lowering/testdata/tuple/two_entries.carbon
  16. 16 26
      toolchain/semantics/semantics_context.cpp
  17. 4 2
      toolchain/semantics/semantics_context.h
  18. 3 4
      toolchain/semantics/semantics_handle_call_expression.cpp
  19. 4 1
      toolchain/semantics/semantics_handle_operator.cpp
  20. 3 13
      toolchain/semantics/semantics_handle_statement.cpp
  21. 6 3
      toolchain/semantics/semantics_handle_variable.cpp
  22. 8 4
      toolchain/semantics/semantics_ir.cpp
  23. 5 2
      toolchain/semantics/semantics_node.h
  24. 3 2
      toolchain/semantics/semantics_node_kind.def
  25. 8 8
      toolchain/semantics/testdata/array/assign_return_value.carbon
  26. 197 0
      toolchain/semantics/testdata/expression_category/in_place_tuple_initialization.carbon
  27. 2 2
      toolchain/semantics/testdata/function/call/empty_struct.carbon
  28. 2 2
      toolchain/semantics/testdata/function/call/empty_tuple.carbon
  29. 3 0
      toolchain/semantics/testdata/function/call/return_implicit.carbon
  30. 28 28
      toolchain/semantics/testdata/if_expression/constant_condition.carbon
  31. 14 14
      toolchain/semantics/testdata/if_expression/control_flow.carbon
  32. 7 4
      toolchain/semantics/testdata/index/fail_empty_tuple_access.carbon
  33. 6 6
      toolchain/semantics/testdata/index/tuple_return_value_access.carbon
  34. 14 14
      toolchain/semantics/testdata/operators/and.carbon
  35. 14 14
      toolchain/semantics/testdata/operators/or.carbon
  36. 6 6
      toolchain/semantics/testdata/pointer/fail_address_of_value.carbon
  37. 2 5
      toolchain/semantics/testdata/return/tuple.carbon

+ 40 - 0
toolchain/lowering/lowering_function_context.cpp

@@ -59,6 +59,46 @@ auto FunctionContext::CreateSyntheticBlock() -> llvm::BasicBlock* {
   return synthetic_block_;
 }
 
+auto FunctionContext::FinishInitialization(SemIR::TypeId type_id,
+                                           SemIR::NodeId dest_id,
+                                           SemIR::NodeId source_id) -> void {
+  switch (SemIR::GetInitializingRepresentation(semantics_ir(), type_id).kind) {
+    case SemIR::InitializingRepresentation::None:
+    case SemIR::InitializingRepresentation::InPlace:
+      break;
+    case SemIR::InitializingRepresentation::ByCopy:
+      CopyValue(type_id, source_id, dest_id);
+      break;
+  }
+}
+
+auto FunctionContext::CopyValue(SemIR::TypeId type_id, SemIR::NodeId source_id,
+                                SemIR::NodeId dest_id) -> void {
+  switch (auto rep = SemIR::GetValueRepresentation(semantics_ir(), type_id);
+          rep.kind) {
+    case SemIR::ValueRepresentation::None:
+      break;
+    case SemIR::ValueRepresentation::Copy:
+      builder().CreateStore(GetLocalLoaded(source_id), GetLocal(dest_id));
+      break;
+    case SemIR::ValueRepresentation::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(GetLocal(dest_id), align, GetLocal(source_id),
+                             align, layout.getTypeAllocSize(type));
+      break;
+    }
+    case SemIR::ValueRepresentation::Custom:
+      CARBON_FATAL() << "TODO: Add support for CopyValue with custom value rep";
+  }
+}
+
 auto FunctionContext::GetLocalLoaded(SemIR::NodeId node_id) -> llvm::Value* {
   auto* value = GetLocal(node_id);
   if (llvm::isa<llvm::AllocaInst, llvm::GetElementPtrInst>(value)) {

+ 13 - 0
toolchain/lowering/lowering_function_context.h

@@ -93,6 +93,13 @@ class FunctionContext {
     return synthetic_block_ == block;
   }
 
+  // After emitting an initializer `init_id`, finishes performing the
+  // initialization of `dest_id` from that initializer. This is a no-op if the
+  // initialization was performed in-place, and otherwise performs a store or a
+  // copy.
+  auto FinishInitialization(SemIR::TypeId type_id, SemIR::NodeId dest_id,
+                            SemIR::NodeId init_id) -> void;
+
   auto llvm_context() -> llvm::LLVMContext& {
     return file_context_->llvm_context();
   }
@@ -103,6 +110,12 @@ class FunctionContext {
   }
 
  private:
+  // Emits a value copy for type `type_id` from `source_id` to `dest_id`.
+  // `source_id` must produce a value representation for `type_id`, and
+  // `dest_id` must be a pointer to a `type_id` object.
+  auto CopyValue(SemIR::TypeId type_id, SemIR::NodeId source_id,
+                 SemIR::NodeId dest_id) -> void;
+
   // Context for the overall lowering process.
   FileContext* file_context_;
 

+ 15 - 32
toolchain/lowering/lowering_handle.cpp

@@ -78,37 +78,7 @@ auto HandleAssign(FunctionContext& context, SemIR::NodeId /*node_id*/,
                   SemIR::Node node) -> void {
   auto [storage_id, value_id] = node.GetAsAssign();
   auto storage_type_id = context.semantics_ir().GetNode(storage_id).type_id();
-
-  // We can assign from either a value expression or a by-copy initializing
-  // expression. In either case, the value of the source is the value
-  // representation of the target.
-  // TODO: Should we use different semantic nodes for those operations?
-  switch (auto rep = SemIR::GetValueRepresentation(context.semantics_ir(),
-                                                   storage_type_id);
-          rep.kind) {
-    case SemIR::ValueRepresentation::None:
-      break;
-    case SemIR::ValueRepresentation::Copy:
-      context.builder().CreateStore(context.GetLocalLoaded(value_id),
-                                    context.GetLocal(storage_id));
-      break;
-    case SemIR::ValueRepresentation::Pointer: {
-      const auto& layout = context.llvm_module().getDataLayout();
-      auto* type = context.GetType(storage_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.
-      context.builder().CreateMemCpy(context.GetLocal(storage_id), align,
-                                     context.GetLocal(value_id), align,
-                                     layout.getTypeAllocSize(type));
-      break;
-    }
-    case SemIR::ValueRepresentation::Custom:
-      CARBON_FATAL() << "TODO: Add support for Assign with custom value rep";
-  }
+  context.FinishInitialization(storage_type_id, storage_id, value_id);
 }
 
 auto HandleBinaryOperatorAdd(FunctionContext& /*context*/,
@@ -296,7 +266,20 @@ auto HandleReturn(FunctionContext& context, SemIR::NodeId /*node_id*/,
 auto HandleReturnExpression(FunctionContext& context, SemIR::NodeId /*node_id*/,
                             SemIR::Node node) -> void {
   SemIR::NodeId expr_id = node.GetAsReturnExpression();
-  context.builder().CreateRet(context.GetLocalLoaded(expr_id));
+  switch (SemIR::GetInitializingRepresentation(
+              context.semantics_ir(),
+              context.semantics_ir().GetNode(expr_id).type_id())
+              .kind) {
+    case SemIR::InitializingRepresentation::None:
+    case SemIR::InitializingRepresentation::InPlace:
+      // Nothing to return.
+      context.builder().CreateRetVoid();
+      return;
+    case SemIR::InitializingRepresentation::ByCopy:
+      // The expression produces the value representation for the type.
+      context.builder().CreateRet(context.GetLocalLoaded(expr_id));
+      return;
+  }
 }
 
 auto HandleStringLiteral(FunctionContext& /*context*/,

+ 9 - 2
toolchain/lowering/lowering_handle_expression_category.cpp

@@ -30,8 +30,15 @@ auto HandleBindValue(FunctionContext& context, SemIR::NodeId node_id,
   }
 }
 
-auto HandleMaterializeTemporary(FunctionContext& context, SemIR::NodeId node_id,
-                                SemIR::Node node) -> void {
+auto HandleTemporary(FunctionContext& context, SemIR::NodeId node_id,
+                     SemIR::Node node) -> void {
+  auto [temporary_id, init_id] = node.GetAsTemporary();
+  context.FinishInitialization(node.type_id(), temporary_id, init_id);
+  context.SetLocal(node_id, context.GetLocal(temporary_id));
+}
+
+auto HandleTemporaryStorage(FunctionContext& context, SemIR::NodeId node_id,
+                            SemIR::Node node) -> void {
   context.SetLocal(
       node_id, context.builder().CreateAlloca(context.GetType(node.type_id()),
                                               nullptr, "temp"));

+ 0 - 10
toolchain/lowering/testdata/array/assign_return_value.carbon

@@ -19,7 +19,6 @@ fn Run() {
 // CHECK:STDOUT:   store i32 12, ptr %1, align 4
 // CHECK:STDOUT:   %2 = getelementptr inbounds { i32, i32 }, ptr %tuple, i32 0, i32 1
 // CHECK:STDOUT:   store i32 24, ptr %2, 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:
@@ -36,14 +35,5 @@ fn Run() {
 // CHECK:STDOUT:   %3 = load i32, ptr %array.element1, align 4
 // CHECK:STDOUT:   %4 = getelementptr inbounds [2 x i32], ptr %array, i32 0, i32 1
 // CHECK:STDOUT:   store i32 %3, ptr %4, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %t, ptr align 4 %array, 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 ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
-// CHECK:STDOUT:
-// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 0 - 13
toolchain/lowering/testdata/array/base.carbon

@@ -27,7 +27,6 @@ fn Run() {
 // CHECK:STDOUT:   %2 = load i32, ptr %array.element, align 4
 // CHECK:STDOUT:   %3 = getelementptr inbounds [1 x i32], ptr %array, i32 0, i32 0
 // CHECK:STDOUT:   store i32 %2, ptr %3, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %array, i64 4, i1 false)
 // CHECK:STDOUT:   %b = alloca [2 x double], align 8
 // CHECK:STDOUT:   %tuple1 = alloca { double, double }, align 8
 // CHECK:STDOUT:   %4 = getelementptr inbounds { double, double }, ptr %tuple1, i32 0, i32 0
@@ -43,7 +42,6 @@ fn Run() {
 // CHECK:STDOUT:   %8 = load double, ptr %array.element4, align 8
 // CHECK:STDOUT:   %9 = getelementptr inbounds [2 x double], ptr %array2, i32 0, i32 1
 // CHECK:STDOUT:   store double %8, ptr %9, align 8
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %b, ptr align 8 %array2, i64 16, i1 false)
 // CHECK:STDOUT:   %tuple5 = alloca {}, align 8
 // CHECK:STDOUT:   %c = alloca [5 x {}], align 8
 // CHECK:STDOUT:   %tuple6 = alloca {}, align 8
@@ -83,7 +81,6 @@ fn Run() {
 // CHECK:STDOUT:   %23 = load {}, ptr %array.element17, align 1
 // CHECK:STDOUT:   %24 = getelementptr inbounds [5 x {}], ptr %array12, i32 0, i32 4
 // CHECK:STDOUT:   store {} %23, ptr %24, align 1
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %c, ptr align 1 %array12, i64 0, i1 false)
 // CHECK:STDOUT:   %tuple18 = alloca { %type, %type, %type }, align 8
 // CHECK:STDOUT:   %25 = getelementptr inbounds { %type, %type, %type }, ptr %tuple18, i32 0, i32 0
 // CHECK:STDOUT:   store %type zeroinitializer, ptr %25, align 1
@@ -99,7 +96,6 @@ fn Run() {
 // CHECK:STDOUT:   store i32 2, ptr %29, align 4
 // CHECK:STDOUT:   %30 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple19, i32 0, i32 2
 // CHECK:STDOUT:   store i32 3, ptr %30, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %d, ptr align 4 %tuple19, i64 12, i1 false)
 // CHECK:STDOUT:   %e = alloca [3 x i32], align 4
 // CHECK:STDOUT:   %array20 = alloca [3 x i32], align 4
 // CHECK:STDOUT:   %array.element21 = getelementptr inbounds { i32, i32, i32 }, ptr %d, i32 0, i32 0
@@ -114,14 +110,5 @@ fn Run() {
 // CHECK:STDOUT:   %35 = load i32, ptr %array.element23, align 4
 // CHECK:STDOUT:   %36 = getelementptr inbounds [3 x i32], ptr %array20, i32 0, i32 2
 // CHECK:STDOUT:   store i32 %35, ptr %36, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %e, ptr align 4 %array20, i64 12, 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, { 4, 3, 2, 1, 0 }
-// CHECK:STDOUT:
-// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 0 - 6
toolchain/lowering/testdata/function/call/tuple_param_with_return_slot.carbon

@@ -26,7 +26,6 @@ fn Main() {
 // CHECK:STDOUT:   store ptr %tuple.index1, ptr %2, align 8
 // CHECK:STDOUT:   %3 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple, i32 0, i32 2
 // CHECK:STDOUT:   store ptr %tuple.index2, ptr %3, align 8
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 %tuple, i64 12, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -45,8 +44,3 @@ fn Main() {
 // CHECK:STDOUT:   call void @F(ptr %temp, { i32 } %4, ptr %tuple2)
 // 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) }

+ 0 - 11
toolchain/lowering/testdata/index/array_element_access.carbon

@@ -23,7 +23,6 @@ fn Run() {
 // CHECK:STDOUT:   store i32 1, ptr %1, align 4
 // CHECK:STDOUT:   %2 = getelementptr inbounds { i32, i32 }, ptr %tuple, i32 0, i32 1
 // CHECK:STDOUT:   store i32 2, ptr %2, 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:
@@ -42,7 +41,6 @@ fn Run() {
 // CHECK:STDOUT:   %5 = load i32, ptr %array.element1, align 4
 // CHECK:STDOUT:   %6 = getelementptr inbounds [2 x i32], ptr %array, i32 0, i32 1
 // CHECK:STDOUT:   store i32 %5, ptr %6, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %return, ptr align 4 %array, i64 8, i1 false)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -59,7 +57,6 @@ fn Run() {
 // CHECK:STDOUT:   %3 = load i32, ptr %array.element1, align 4
 // CHECK:STDOUT:   %4 = getelementptr inbounds [2 x i32], ptr %array, i32 0, i32 1
 // CHECK:STDOUT:   store i32 %3, ptr %4, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %array, i64 8, i1 false)
 // CHECK:STDOUT:   %b = alloca i32, align 4
 // CHECK:STDOUT:   %temp2 = alloca { i32, i32 }, align 8
 // CHECK:STDOUT:   call void @A(ptr %temp2)
@@ -79,11 +76,3 @@ fn Run() {
 // CHECK:STDOUT:   store i32 %8, ptr %d, 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 ptr @llvm.memcpy.p0.p0.i64, { 2, 1, 0 }
-// CHECK:STDOUT:
-// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 0 - 6
toolchain/lowering/testdata/index/tuple_element_access.carbon

@@ -32,7 +32,6 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 1, ptr %5, align 4
 // CHECK:STDOUT:   %6 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple1, i32 0, i32 2
 // CHECK:STDOUT:   store i32 2, ptr %6, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %tuple1, i64 12, i1 false)
 // CHECK:STDOUT:   %b = alloca i32, align 4
 // CHECK:STDOUT:   %tuple.index = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0
 // CHECK:STDOUT:   %7 = load i32, ptr %tuple.index, align 4
@@ -43,8 +42,3 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 %8, ptr %c, 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) }

+ 0 - 6
toolchain/lowering/testdata/index/tuple_return_value_access.carbon

@@ -19,7 +19,6 @@ fn Run() {
 // CHECK:STDOUT:   store i32 12, ptr %1, align 4
 // CHECK:STDOUT:   %2 = getelementptr inbounds { i32, i32 }, ptr %tuple, i32 0, i32 1
 // CHECK:STDOUT:   store i32 24, ptr %2, 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,8 +31,3 @@ fn Run() {
 // CHECK:STDOUT:   store i32 %1, ptr %t, 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) }

+ 0 - 6
toolchain/lowering/testdata/operators/assignment.carbon

@@ -31,11 +31,5 @@ fn Main() {
 // CHECK:STDOUT:   store i32 1, ptr %3, align 4
 // CHECK:STDOUT:   %4 = getelementptr inbounds { i32, i32 }, ptr %tuple1, i32 0, i32 1
 // CHECK:STDOUT:   store i32 2, ptr %4, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %b, ptr align 4 %tuple1, 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: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 0 - 6
toolchain/lowering/testdata/pointer/address_of_field.carbon

@@ -24,14 +24,8 @@ fn F() {
 // CHECK:STDOUT:   store i32 1, ptr %a, align 4
 // CHECK:STDOUT:   %b = getelementptr inbounds { i32, i32 }, ptr %struct, i32 0, i32 1
 // CHECK:STDOUT:   store i32 2, ptr %b, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %s, ptr align 4 %struct, i64 8, i1 false)
 // CHECK:STDOUT:   %b1 = getelementptr inbounds { i32, i32 }, ptr %s, i32 0, i32 1
 // CHECK:STDOUT:   %1 = load ptr, ptr %b1, align 8
 // CHECK:STDOUT:   call void @G(ptr %1)
 // 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) }

+ 0 - 6
toolchain/lowering/testdata/struct/member_access.carbon

@@ -21,7 +21,6 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store double 0.000000e+00, ptr %a, align 8
 // CHECK:STDOUT:   %b = getelementptr inbounds { double, i32 }, ptr %struct, i32 0, i32 1
 // CHECK:STDOUT:   store i32 1, ptr %b, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %x, ptr align 8 %struct, i64 16, i1 false)
 // CHECK:STDOUT:   %y = alloca i32, align 4
 // CHECK:STDOUT:   %b1 = getelementptr inbounds { double, i32 }, ptr %x, i32 0, i32 1
 // CHECK:STDOUT:   %1 = load i32, ptr %b1, align 4
@@ -31,8 +30,3 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 %2, ptr %z, 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) }

+ 0 - 10
toolchain/lowering/testdata/struct/two_entries.carbon

@@ -20,16 +20,6 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 1, ptr %a, align 4
 // CHECK:STDOUT:   %b = getelementptr inbounds { i32, i32 }, ptr %struct, i32 0, i32 1
 // CHECK:STDOUT:   store i32 2, ptr %b, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x, ptr align 4 %struct, i64 8, i1 false)
 // CHECK:STDOUT:   %y = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %y, ptr align 4 %x, i64 8, i1 false)
 // 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 ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
-// CHECK:STDOUT:
-// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 0 - 10
toolchain/lowering/testdata/tuple/two_entries.carbon

@@ -27,21 +27,11 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   store i32 12, ptr %3, align 4
 // CHECK:STDOUT:   %4 = getelementptr inbounds { i32, i32 }, ptr %tuple1, i32 0, i32 1
 // CHECK:STDOUT:   store i32 7, ptr %4, align 4
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x, ptr align 4 %tuple1, i64 8, i1 false)
 // CHECK:STDOUT:   %tuple2 = alloca { %type, %type }, align 8
 // CHECK:STDOUT:   %5 = getelementptr inbounds { %type, %type }, ptr %tuple2, i32 0, i32 0
 // CHECK:STDOUT:   store %type zeroinitializer, ptr %5, align 1
 // CHECK:STDOUT:   %6 = getelementptr inbounds { %type, %type }, ptr %tuple2, i32 0, i32 1
 // CHECK:STDOUT:   store %type zeroinitializer, ptr %6, align 1
 // CHECK:STDOUT:   %y = alloca { i32, i32 }, align 8
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %y, ptr align 4 %x, i64 8, i1 false)
 // 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 ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
-// CHECK:STDOUT:
-// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 16 - 26
toolchain/semantics/semantics_context.cpp

@@ -262,7 +262,7 @@ auto Context::is_current_position_reachable() -> bool {
 }
 
 auto Context::Initialize(Parse::Node parse_node, SemIR::NodeId target_id,
-                         SemIR::NodeId value_id) -> void {
+                         SemIR::NodeId value_id) -> SemIR::NodeId {
   // Implicitly convert the value to the type of the target.
   auto type_id = semantics_ir().GetNode(target_id).type_id();
   auto expr_id = ImplicitAsRequired(parse_node, value_id, type_id);
@@ -281,19 +281,17 @@ auto Context::Initialize(Parse::Node parse_node, SemIR::NodeId target_id,
       //
       // TODO: Determine whether this is observably different from the design,
       // and change either the toolchain or the design so they match.
-      expr_id = AddNode(SemIR::Node::BindValue::Make(expr.parse_node(),
-                                                     expr.type_id(), expr_id));
-      [[fallthrough]];
+      return AddNode(SemIR::Node::BindValue::Make(expr.parse_node(),
+                                                  expr.type_id(), expr_id));
 
     case SemIR::ExpressionCategory::Value:
       // TODO: For class types, use an interface to determine how to perform
       // this operation.
-      AddNode(SemIR::Node::Assign::Make(expr.parse_node(), target_id, expr_id));
-      return;
+      return expr_id;
 
     case SemIR::ExpressionCategory::Initializing:
       MarkInitializerFor(expr_id, target_id);
-      return;
+      return expr_id;
   }
 }
 
@@ -328,6 +326,7 @@ auto Context::FinalizeTemporary(SemIR::NodeId init_id, bool discarded)
     -> SemIR::NodeId {
   // TODO: See if we can refactor this with MarkInitializerFor once recursion
   // through struct and tuple values is properly handled.
+  auto orig_init_id = init_id;
   while (true) {
     SemIR::Node init = semantics_ir().GetNode(init_id);
     CARBON_CHECK(SemIR::GetExpressionCategory(semantics_ir(), init_id) ==
@@ -352,11 +351,12 @@ auto Context::FinalizeTemporary(SemIR::NodeId init_id, bool discarded)
           // The return slot should have a materialized temporary in it.
           auto temporary_id = semantics_ir().GetNodeBlock(refs_id).back();
           CARBON_CHECK(semantics_ir().GetNode(temporary_id).kind() ==
-                       SemIR::NodeKind::MaterializeTemporary)
+                       SemIR::NodeKind::TemporaryStorage)
               << "Return slot for function call does not contain a temporary; "
               << "initialized multiple times? Have "
               << semantics_ir().GetNode(temporary_id);
-          return temporary_id;
+          return AddNode(SemIR::Node::Temporary::Make(
+              init.parse_node(), init.type_id(), temporary_id, orig_init_id));
         }
 
         if (discarded) {
@@ -366,17 +366,13 @@ auto Context::FinalizeTemporary(SemIR::NodeId init_id, bool discarded)
 
         // The function has no return slot, but we want to produce a temporary
         // object. Materialize one now.
-        auto temporary_id = AddNode(SemIR::Node::MaterializeTemporary::Make(
+        // TODO: Consider using an invalid ID to mean that we immediately
+        // materialize and initialize a temporary, rather than two separate
+        // nodes.
+        auto temporary_id = AddNode(SemIR::Node::TemporaryStorage::Make(
             init.parse_node(), init.type_id()));
-        if (SemIR::GetInitializingRepresentation(semantics_ir(), init.type_id())
-                .kind != SemIR::InitializingRepresentation::None) {
-          AddNode(SemIR::Node::Assign::Make(init.parse_node(), temporary_id,
-                                            init_id));
-        } else {
-          // TODO: Should we create an empty value and Assign it to the
-          // temporary?
-        }
-        return temporary_id;
+        return AddNode(SemIR::Node::Temporary::Make(
+            init.parse_node(), init.type_id(), temporary_id, init_id));
       }
     }
   }
@@ -410,17 +406,11 @@ auto Context::MarkInitializerFor(SemIR::NodeId init_id, SemIR::NodeId target_id)
           auto temporary_id = std::exchange(
               semantics_ir().GetNodeBlock(refs_id).back(), target_id);
           auto temporary = semantics_ir().GetNode(temporary_id);
-          CARBON_CHECK(temporary.kind() ==
-                       SemIR::NodeKind::MaterializeTemporary)
+          CARBON_CHECK(temporary.kind() == SemIR::NodeKind::TemporaryStorage)
               << "Return slot for function call does not contain a temporary; "
               << "initialized multiple times? Have " << temporary;
           semantics_ir().ReplaceNode(
               temporary_id, SemIR::Node::NoOp::Make(temporary.parse_node()));
-        } else if (SemIR::GetInitializingRepresentation(semantics_ir(),
-                                                        init.type_id())
-                       .kind != SemIR::InitializingRepresentation::None) {
-          AddNode(
-              SemIR::Node::Assign::Make(init.parse_node(), target_id, init_id));
         }
         return;
       }

+ 4 - 2
toolchain/semantics/semantics_context.h

@@ -122,9 +122,11 @@ class Context {
   // Convert the given expression to a value expression of the same type.
   auto ConvertToValueExpression(SemIR::NodeId expr_id) -> SemIR::NodeId;
 
-  // Performs initialization of `target_id` from `value_id`.
+  // Performs initialization of `target_id` from `value_id`. Returns the
+  // possibly-converted initialization expression, which should be assigned to
+  // the target using a suitable node for the kind of initialization.
   auto Initialize(Parse::Node parse_node, SemIR::NodeId target_id,
-                  SemIR::NodeId value_id) -> void;
+                  SemIR::NodeId value_id) -> SemIR::NodeId;
 
   // Converts `value_id` to a value expression of type `type_id`.
   auto ConvertToValueOfType(Parse::Node parse_node, SemIR::NodeId value_id,

+ 3 - 4
toolchain/semantics/semantics_handle_call_expression.cpp

@@ -52,11 +52,10 @@ auto HandleCallExpression(Context& context, Parse::Node parse_node) -> bool {
     if (refs_id == SemIR::NodeBlockId::Empty) {
       refs_id = context.semantics_ir().AddNodeBlock();
     }
-    // Tentatively put a materialized temporary in the function's return slot.
+    // Tentatively put storage for a temporary in the function's return slot.
     // This will be replaced if necessary when we perform initialization.
-    auto return_slot_id =
-        context.AddNode(SemIR::Node::MaterializeTemporary::Make(
-            call_expr_parse_node, callable.return_type_id));
+    auto return_slot_id = context.AddNode(SemIR::Node::TemporaryStorage::Make(
+        call_expr_parse_node, callable.return_type_id));
     context.semantics_ir().GetNodeBlock(refs_id).push_back(return_slot_id);
   }
   auto call_node_id = context.AddNode(SemIR::Node::Call::Make(

+ 4 - 1
toolchain/semantics/semantics_handle_operator.cpp

@@ -59,7 +59,10 @@ auto HandleInfixOperator(Context& context, Parse::Node parse_node) -> bool {
                           "Expression is not assignable.");
         context.emitter().Emit(lhs_node, AssignmentToNonAssignable);
       }
-      context.Initialize(parse_node, lhs_id, rhs_id);
+      // TODO: Destroy the old value before reinitializing. This will require
+      // building the destruction code before we build the RHS subexpression.
+      rhs_id = context.Initialize(parse_node, lhs_id, rhs_id);
+      context.AddNode(SemIR::Node::Assign::Make(parse_node, lhs_id, rhs_id));
       // We model assignment as an expression, so we need to push a value for
       // it, even though it doesn't produce a value.
       // TODO: Consider changing our parse tree to model assignment as a

+ 3 - 13
toolchain/semantics/semantics_handle_statement.cpp

@@ -51,24 +51,14 @@ auto HandleReturnStatement(Context& context, Parse::Node parse_node) -> bool {
           .Build(parse_node, ReturnStatementDisallowExpression)
           .Note(fn_node.parse_node(), ReturnStatementImplicitNote)
           .Emit();
-
-      context.AddNode(SemIR::Node::ReturnExpression::Make(parse_node, arg));
     } else if (callable.return_slot_id.is_valid()) {
-      context.Initialize(parse_node, callable.return_slot_id, arg);
-
-      context.AddNode(SemIR::Node::Return::Make(parse_node));
+      arg = context.Initialize(parse_node, callable.return_slot_id, arg);
     } else {
       arg = context.ConvertToValueOfType(parse_node, arg,
                                          callable.return_type_id);
-
-      if (SemIR::GetInitializingRepresentation(context.semantics_ir(),
-                                               callable.return_type_id)
-              .kind == SemIR::InitializingRepresentation::None) {
-        context.AddNode(SemIR::Node::Return::Make(parse_node));
-      } else {
-        context.AddNode(SemIR::Node::ReturnExpression::Make(parse_node, arg));
-      }
     }
+
+    context.AddNode(SemIR::Node::ReturnExpression::Make(parse_node, arg));
   }
 
   // Switch to a new, unreachable, empty node block. This typically won't

+ 6 - 3
toolchain/semantics/semantics_handle_variable.cpp

@@ -10,12 +10,12 @@ namespace Carbon::Check {
 auto HandleVariableDeclaration(Context& context, Parse::Node parse_node)
     -> bool {
   // Handle the optional initializer.
-  auto expr_node_id = SemIR::NodeId::Invalid;
+  auto init_id = SemIR::NodeId::Invalid;
   bool has_init =
       context.parse_tree().node_kind(context.node_stack().PeekParseNode()) !=
       Parse::NodeKind::PatternBinding;
   if (has_init) {
-    expr_node_id = context.node_stack().PopExpression();
+    init_id = context.node_stack().PopExpression();
     context.node_stack()
         .PopAndDiscardSoloParseNode<Parse::NodeKind::VariableInitializer>();
   }
@@ -28,7 +28,10 @@ auto HandleVariableDeclaration(Context& context, Parse::Node parse_node)
   context.AddNameToLookup(var.parse_node(), name_id, var_id);
   // If there was an initializer, assign it to storage.
   if (has_init) {
-    context.Initialize(parse_node, var_id, expr_node_id);
+    init_id = context.Initialize(parse_node, var_id, init_id);
+    // TODO: Consider using different node kinds for assignment versus
+    // initialization.
+    context.AddNode(SemIR::Node::Assign::Make(parse_node, var_id, init_id));
   }
 
   context.node_stack()

+ 8 - 4
toolchain/semantics/semantics_ir.cpp

@@ -235,7 +235,6 @@ static auto GetTypePrecedence(NodeKind kind) -> int {
     case NodeKind::FunctionDeclaration:
     case NodeKind::IntegerLiteral:
     case NodeKind::Invalid:
-    case NodeKind::MaterializeTemporary:
     case NodeKind::Namespace:
     case NodeKind::NoOp:
     case NodeKind::Parameter:
@@ -247,6 +246,8 @@ static auto GetTypePrecedence(NodeKind kind) -> int {
     case NodeKind::StructTypeField:
     case NodeKind::StructValue:
     case NodeKind::StubReference:
+    case NodeKind::Temporary:
+    case NodeKind::TemporaryStorage:
     case NodeKind::TupleIndex:
     case NodeKind::TupleValue:
     case NodeKind::UnaryOperatorNot:
@@ -396,7 +397,6 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const
       case NodeKind::Dereference:
       case NodeKind::FunctionDeclaration:
       case NodeKind::IntegerLiteral:
-      case NodeKind::MaterializeTemporary:
       case NodeKind::Namespace:
       case NodeKind::NoOp:
       case NodeKind::Parameter:
@@ -407,6 +407,8 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const
       case NodeKind::StructAccess:
       case NodeKind::StructValue:
       case NodeKind::StubReference:
+      case NodeKind::Temporary:
+      case NodeKind::TemporaryStorage:
       case NodeKind::TupleIndex:
       case NodeKind::TupleValue:
       case NodeKind::UnaryOperatorNot:
@@ -520,7 +522,8 @@ auto GetExpressionCategory(const File& file, NodeId node_id)
       case NodeKind::VarStorage:
         return ExpressionCategory::DurableReference;
 
-      case NodeKind::MaterializeTemporary:
+      case NodeKind::Temporary:
+      case NodeKind::TemporaryStorage:
         return ExpressionCategory::EphemeralReference;
     }
   }
@@ -551,7 +554,6 @@ auto GetValueRepresentation(const File& file, TypeId type_id)
       case NodeKind::FunctionDeclaration:
       case NodeKind::IntegerLiteral:
       case NodeKind::Invalid:
-      case NodeKind::MaterializeTemporary:
       case NodeKind::Namespace:
       case NodeKind::NoOp:
       case NodeKind::Parameter:
@@ -562,6 +564,8 @@ auto GetValueRepresentation(const File& file, TypeId type_id)
       case NodeKind::StructAccess:
       case NodeKind::StructTypeField:
       case NodeKind::StructValue:
+      case NodeKind::Temporary:
+      case NodeKind::TemporaryStorage:
       case NodeKind::TupleIndex:
       case NodeKind::TupleValue:
       case NodeKind::UnaryOperatorNot:

+ 5 - 2
toolchain/semantics/semantics_node.h

@@ -372,8 +372,6 @@ class Node : public Printable<Node> {
   using IntegerLiteral =
       Factory<NodeKind::IntegerLiteral, IntegerLiteralId /*integer_id*/>;
 
-  using MaterializeTemporary = Factory<NodeKind::MaterializeTemporary>;
-
   using Namespace =
       FactoryNoType<NodeKind::Namespace, NameScopeId /*name_scope_id*/>;
 
@@ -406,6 +404,11 @@ class Node : public Printable<Node> {
 
   using StubReference = Factory<NodeKind::StubReference, NodeId /*node_id*/>;
 
+  using Temporary =
+      Factory<NodeKind::Temporary, NodeId /*storage_id*/, NodeId /*init_id*/>;
+
+  using TemporaryStorage = Factory<NodeKind::TemporaryStorage>;
+
   using TupleIndex =
       Factory<NodeKind::TupleIndex, NodeId /*tuple_id*/, NodeId /*index*/>;
 

+ 3 - 2
toolchain/semantics/semantics_node_kind.def

@@ -66,8 +66,6 @@ CARBON_SEMANTICS_NODE_KIND_IMPL(FunctionDeclaration, "fn_decl", Untyped,
                                 NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(IntegerLiteral, "int_literal", Typed,
                                 NotTerminator)
-CARBON_SEMANTICS_NODE_KIND_IMPL(MaterializeTemporary, "materialize_temporary",
-                                Typed, NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(Namespace, "namespace", Untyped, NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(NoOp, "no_op", None, NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(Parameter, "parameter", Typed, NotTerminator)
@@ -87,6 +85,9 @@ CARBON_SEMANTICS_NODE_KIND_IMPL(StructValue, "struct_value", Typed,
                                 NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(StubReference, "stub_reference", Typed,
                                 NotTerminator)
+CARBON_SEMANTICS_NODE_KIND_IMPL(Temporary, "temporary", Typed, NotTerminator)
+CARBON_SEMANTICS_NODE_KIND_IMPL(TemporaryStorage, "temporary_storage", Typed,
+                                NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(TupleIndex, "tuple_index", Typed, NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(TupleType, "tuple_type", Typed, NotTerminator)
 CARBON_SEMANTICS_NODE_KIND_IMPL(TupleValue, "tuple_value", Typed, NotTerminator)

+ 8 - 8
toolchain/semantics/testdata/array/assign_return_value.carbon

@@ -57,9 +57,9 @@ fn Run() {
 // CHECK:STDOUT:   {kind: ArrayType, arg0: node+11, arg1: type1, type: typeTypeType},
 // CHECK:STDOUT:   {kind: VarStorage, arg0: str3, type: type3},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type2},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type2},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+15, arg1: node+14},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+15, type: type2},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type2},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+15, arg1: node+14, type: type2},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+16, type: type2},
 // CHECK:STDOUT:   {kind: ArrayValue, arg0: node+17, type: type3},
 // CHECK:STDOUT:   {kind: Assign, arg0: node+13, arg1: node+18},
 // CHECK:STDOUT:   {kind: Return},
@@ -123,10 +123,10 @@ fn Run() {
 // CHECK:STDOUT:   %.loc10_17: type = array_type %.loc10_16, i32
 // CHECK:STDOUT:   %t: [i32; 1] = var "t"
 // CHECK:STDOUT:   %.loc10_22.1: (i32,) = call @F()
-// CHECK:STDOUT:   %.loc10_22.2: (i32,) = materialize_temporary
-// CHECK:STDOUT:   assign %.loc10_22.2, %.loc10_22.1
-// CHECK:STDOUT:   %.loc10_22.3: (i32,) = bind_value %.loc10_22.2
-// CHECK:STDOUT:   %.loc10_22.4: [i32; 1] = array_value %.loc10_22.3
-// CHECK:STDOUT:   assign %t, %.loc10_22.4
+// CHECK:STDOUT:   %.loc10_22.2: (i32,) = temporary_storage
+// CHECK:STDOUT:   %.loc10_22.3: (i32,) = temporary %.loc10_22.2, %.loc10_22.1
+// CHECK:STDOUT:   %.loc10_22.4: (i32,) = bind_value %.loc10_22.3
+// CHECK:STDOUT:   %.loc10_22.5: [i32; 1] = array_value %.loc10_22.4
+// CHECK:STDOUT:   assign %t, %.loc10_22.5
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 197 - 0
toolchain/semantics/testdata/expression_category/in_place_tuple_initialization.carbon

@@ -0,0 +1,197 @@
+// 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
+//
+// AUTOUPDATE
+
+fn F() -> (i32, i32);
+
+fn G() -> (i32, i32) {
+  var v: (i32, i32) = F();
+  v = F();
+  return F();
+}
+
+fn H() -> i32 {
+  return G()[0];
+}
+
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block0, return_type: type2, return_slot: node+5},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, return_type: type2, return_slot: node+10, body: [block6]},
+// CHECK:STDOUT:   {name: str4, param_refs: block0, return_type: type1, body: [block12]},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT:   0,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT:   return,
+// CHECK:STDOUT:   G,
+// CHECK:STDOUT:   v,
+// CHECK:STDOUT:   H,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   node+2,
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT:   node+4,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     typeTypeType,
+// CHECK:STDOUT:     typeTypeType,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     type1,
+// CHECK:STDOUT:     type1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block2, type: type0},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, arg0: str1, type: type2},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block5, type: type0},
+// CHECK:STDOUT:   {kind: VarStorage, arg0: str1, type: type2},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block7, type: type0},
+// CHECK:STDOUT:   {kind: VarStorage, arg0: str3, type: type2},
+// CHECK:STDOUT:   {kind: NoOp},
+// CHECK:STDOUT:   {kind: Call, arg0: block8, arg1: function0, type: type2},
+// CHECK:STDOUT:   {kind: Assign, arg0: node+15, arg1: node+17},
+// CHECK:STDOUT:   {kind: NoOp},
+// CHECK:STDOUT:   {kind: Call, arg0: block9, arg1: function0, type: type2},
+// CHECK:STDOUT:   {kind: Assign, arg0: node+15, arg1: node+20},
+// CHECK:STDOUT:   {kind: NoOp},
+// CHECK:STDOUT:   {kind: Call, arg0: block10, arg1: function0, type: type2},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+23},
+// CHECK:STDOUT:   {kind: VarStorage, arg0: str1, type: type1},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type2},
+// CHECK:STDOUT:   {kind: Call, arg0: block13, arg1: function1, type: type2},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type1},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+27, arg1: node+28, type: type2},
+// CHECK:STDOUT:   {kind: TupleIndex, arg0: node+30, arg1: node+29, type: type1},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+31, type: type1},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+32},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:     node+26,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:     node+14,
+// CHECK:STDOUT:     node+15,
+// CHECK:STDOUT:     node+16,
+// CHECK:STDOUT:     node+17,
+// CHECK:STDOUT:     node+18,
+// CHECK:STDOUT:     node+19,
+// CHECK:STDOUT:     node+20,
+// CHECK:STDOUT:     node+21,
+// CHECK:STDOUT:     node+22,
+// CHECK:STDOUT:     node+23,
+// CHECK:STDOUT:     node+24,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+15,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+15,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+25,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+27,
+// CHECK:STDOUT:     node+28,
+// CHECK:STDOUT:     node+29,
+// CHECK:STDOUT:     node+30,
+// CHECK:STDOUT:     node+31,
+// CHECK:STDOUT:     node+32,
+// CHECK:STDOUT:     node+33,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+27,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+// CHECK:STDOUT:
+// CHECK:STDOUT: package {
+// CHECK:STDOUT:   %.loc7 = fn_decl @F
+// CHECK:STDOUT:   %.loc9 = fn_decl @G
+// CHECK:STDOUT:   %.loc15 = fn_decl @H
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() -> %return: (i32, i32);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() -> %return: (i32, i32) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc10_11: type = stub_reference i32
+// CHECK:STDOUT:   %.loc10_16: type = stub_reference i32
+// CHECK:STDOUT:   %.loc10_19: (type, type) = tuple_value (%.loc10_11, %.loc10_16)
+// CHECK:STDOUT:   %v: (i32, i32) = var "v"
+// CHECK:STDOUT:   no_op
+// CHECK:STDOUT:   %.loc10_24: (i32, i32) = call @F() to %v
+// CHECK:STDOUT:   assign %v, %.loc10_24
+// CHECK:STDOUT:   no_op
+// CHECK:STDOUT:   %.loc11: (i32, i32) = call @F() to %v
+// CHECK:STDOUT:   assign %v, %.loc11
+// CHECK:STDOUT:   no_op
+// CHECK:STDOUT:   %.loc12: (i32, i32) = call @F() to %return
+// CHECK:STDOUT:   return %.loc12
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @H() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc16_11.1: (i32, i32) = temporary_storage
+// CHECK:STDOUT:   %.loc16_11.2: (i32, i32) = call @G() to %.loc16_11.1
+// CHECK:STDOUT:   %.loc16_14: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc16_11.3: (i32, i32) = temporary %.loc16_11.1, %.loc16_11.2
+// CHECK:STDOUT:   %.loc16_15.1: i32 = tuple_index %.loc16_11.3, %.loc16_14
+// CHECK:STDOUT:   %.loc16_15.2: i32 = bind_value %.loc16_15.1
+// CHECK:STDOUT:   return %.loc16_15.2
+// CHECK:STDOUT: }

+ 2 - 2
toolchain/semantics/testdata/function/call/empty_struct.carbon

@@ -39,7 +39,7 @@ fn Main() {
 // CHECK:STDOUT:   {kind: StructValue, arg0: block0, type: type0},
 // CHECK:STDOUT:   {kind: VarStorage, arg0: str2, type: type0},
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
-// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+2},
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
 // CHECK:STDOUT:   {kind: StructValue, arg0: block0, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+8, type: type0},
@@ -84,7 +84,7 @@ fn Main() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Echo(%a: {}) -> {} {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   return
+// CHECK:STDOUT:   return %a
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Main() {

+ 2 - 2
toolchain/semantics/testdata/function/call/empty_tuple.carbon

@@ -41,7 +41,7 @@ fn Main() {
 // CHECK:STDOUT:   {kind: TupleValue, arg0: block0, type: type0},
 // CHECK:STDOUT:   {kind: VarStorage, arg0: str2, type: type0},
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
-// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+2},
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
 // CHECK:STDOUT:   {kind: TupleValue, arg0: block0, type: type0},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+8, type: type0},
@@ -86,7 +86,7 @@ fn Main() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Echo(%a: ()) -> () {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   return
+// CHECK:STDOUT:   return %a
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Main() {

+ 3 - 0
toolchain/semantics/testdata/function/call/return_implicit.carbon

@@ -40,6 +40,7 @@ fn Main() {
 // CHECK:STDOUT:   {kind: TupleValue, arg0: block0, type: type0},
 // CHECK:STDOUT:   {kind: VarStorage, arg0: str2, type: type0},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
+// CHECK:STDOUT:   {kind: Assign, arg0: node+5, arg1: node+6},
 // CHECK:STDOUT:   {kind: Return},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
@@ -58,6 +59,7 @@ fn Main() {
 // CHECK:STDOUT:     node+5,
 // CHECK:STDOUT:     node+6,
 // CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 // CHECK:STDOUT:
@@ -77,5 +79,6 @@ fn Main() {
 // CHECK:STDOUT:   %.loc11_11.2: () = tuple_value ()
 // CHECK:STDOUT:   %b: () = var "b"
 // CHECK:STDOUT:   %.loc11_37: () = call @MakeImplicitEmptyTuple()
+// CHECK:STDOUT:   assign %b, %.loc11_37
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 28 - 28
toolchain/semantics/testdata/if_expression/constant_condition.carbon

@@ -56,13 +56,13 @@ fn G() -> i32 {
 // CHECK:STDOUT:   {kind: BranchIf, arg0: block8, arg1: node+10},
 // CHECK:STDOUT:   {kind: Branch, arg0: block9},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+14, arg1: node+13},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+14, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+14, arg1: node+13, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+15, type: type0},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+18, arg1: node+17},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+18, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+18, arg1: node+17, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+19, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block10, arg1: node+16},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block10, arg1: node+20},
 // CHECK:STDOUT:   {kind: BlockArg, arg0: block10, type: type0},
@@ -73,13 +73,13 @@ fn G() -> i32 {
 // CHECK:STDOUT:   {kind: BranchIf, arg0: block13, arg1: node+27},
 // CHECK:STDOUT:   {kind: Branch, arg0: block14},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+31, arg1: node+30},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+31, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+31, arg1: node+30, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+32, type: type0},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+35, arg1: node+34},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+35, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+35, arg1: node+34, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+36, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block15, arg1: node+33},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block15, arg1: node+37},
 // CHECK:STDOUT:   {kind: BlockArg, arg0: block15, type: type0},
@@ -188,17 +188,17 @@ fn G() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then:
 // CHECK:STDOUT:   %.loc11_24.1: i32 = call @A()
-// CHECK:STDOUT:   %.loc11_24.2: i32 = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_24.2, %.loc11_24.1
-// CHECK:STDOUT:   %.loc11_24.3: i32 = bind_value %.loc11_24.2
-// CHECK:STDOUT:   br !if.expr.result(%.loc11_24.3)
+// CHECK:STDOUT:   %.loc11_24.2: i32 = temporary_storage
+// CHECK:STDOUT:   %.loc11_24.3: i32 = temporary %.loc11_24.2, %.loc11_24.1
+// CHECK:STDOUT:   %.loc11_24.4: i32 = bind_value %.loc11_24.3
+// CHECK:STDOUT:   br !if.expr.result(%.loc11_24.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.else:
 // CHECK:STDOUT:   %.loc11_33.1: i32 = call @B()
-// CHECK:STDOUT:   %.loc11_33.2: i32 = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_33.2, %.loc11_33.1
-// CHECK:STDOUT:   %.loc11_33.3: i32 = bind_value %.loc11_33.2
-// CHECK:STDOUT:   br !if.expr.result(%.loc11_33.3)
+// CHECK:STDOUT:   %.loc11_33.2: i32 = temporary_storage
+// CHECK:STDOUT:   %.loc11_33.3: i32 = temporary %.loc11_33.2, %.loc11_33.1
+// CHECK:STDOUT:   %.loc11_33.4: i32 = bind_value %.loc11_33.3
+// CHECK:STDOUT:   br !if.expr.result(%.loc11_33.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.result:
 // CHECK:STDOUT:   %.loc11_10: i32 = block_arg !if.expr.result
@@ -212,17 +212,17 @@ fn G() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then:
 // CHECK:STDOUT:   %.loc15_25.1: i32 = call @A()
-// CHECK:STDOUT:   %.loc15_25.2: i32 = materialize_temporary
-// CHECK:STDOUT:   assign %.loc15_25.2, %.loc15_25.1
-// CHECK:STDOUT:   %.loc15_25.3: i32 = bind_value %.loc15_25.2
-// CHECK:STDOUT:   br !if.expr.result(%.loc15_25.3)
+// CHECK:STDOUT:   %.loc15_25.2: i32 = temporary_storage
+// CHECK:STDOUT:   %.loc15_25.3: i32 = temporary %.loc15_25.2, %.loc15_25.1
+// CHECK:STDOUT:   %.loc15_25.4: i32 = bind_value %.loc15_25.3
+// CHECK:STDOUT:   br !if.expr.result(%.loc15_25.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.else:
 // CHECK:STDOUT:   %.loc15_34.1: i32 = call @B()
-// CHECK:STDOUT:   %.loc15_34.2: i32 = materialize_temporary
-// CHECK:STDOUT:   assign %.loc15_34.2, %.loc15_34.1
-// CHECK:STDOUT:   %.loc15_34.3: i32 = bind_value %.loc15_34.2
-// CHECK:STDOUT:   br !if.expr.result(%.loc15_34.3)
+// CHECK:STDOUT:   %.loc15_34.2: i32 = temporary_storage
+// CHECK:STDOUT:   %.loc15_34.3: i32 = temporary %.loc15_34.2, %.loc15_34.1
+// CHECK:STDOUT:   %.loc15_34.4: i32 = bind_value %.loc15_34.3
+// CHECK:STDOUT:   br !if.expr.result(%.loc15_34.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.result:
 // CHECK:STDOUT:   %.loc15_10: i32 = block_arg !if.expr.result

+ 14 - 14
toolchain/semantics/testdata/if_expression/control_flow.carbon

@@ -51,13 +51,13 @@ fn F(b: bool) -> i32 {
 // CHECK:STDOUT:   {kind: BranchIf, arg0: block9, arg1: node+8},
 // CHECK:STDOUT:   {kind: Branch, arg0: block10},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+14, arg1: node+13},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+14, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+14, arg1: node+13, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+15, type: type0},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+18, arg1: node+17},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+18, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+18, arg1: node+17, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+19, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block11, arg1: node+16},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block11, arg1: node+20},
 // CHECK:STDOUT:   {kind: BlockArg, arg0: block11, type: type0},
@@ -140,17 +140,17 @@ fn F(b: bool) -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then:
 // CHECK:STDOUT:   %.loc11_21.1: i32 = call @A()
-// CHECK:STDOUT:   %.loc11_21.2: i32 = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_21.2, %.loc11_21.1
-// CHECK:STDOUT:   %.loc11_21.3: i32 = bind_value %.loc11_21.2
-// CHECK:STDOUT:   br !if.expr.result(%.loc11_21.3)
+// CHECK:STDOUT:   %.loc11_21.2: i32 = temporary_storage
+// CHECK:STDOUT:   %.loc11_21.3: i32 = temporary %.loc11_21.2, %.loc11_21.1
+// CHECK:STDOUT:   %.loc11_21.4: i32 = bind_value %.loc11_21.3
+// CHECK:STDOUT:   br !if.expr.result(%.loc11_21.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.else:
 // CHECK:STDOUT:   %.loc11_30.1: i32 = call @B()
-// CHECK:STDOUT:   %.loc11_30.2: i32 = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_30.2, %.loc11_30.1
-// CHECK:STDOUT:   %.loc11_30.3: i32 = bind_value %.loc11_30.2
-// CHECK:STDOUT:   br !if.expr.result(%.loc11_30.3)
+// CHECK:STDOUT:   %.loc11_30.2: i32 = temporary_storage
+// CHECK:STDOUT:   %.loc11_30.3: i32 = temporary %.loc11_30.2, %.loc11_30.1
+// CHECK:STDOUT:   %.loc11_30.4: i32 = bind_value %.loc11_30.3
+// CHECK:STDOUT:   br !if.expr.result(%.loc11_30.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.result:
 // CHECK:STDOUT:   %.loc11_10: i32 = block_arg !if.expr.result

+ 7 - 4
toolchain/semantics/testdata/index/fail_empty_tuple_access.carbon

@@ -42,8 +42,9 @@ fn Run() {
 // CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock0, type: typeTypeType},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type1},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: TupleIndex, arg0: node+6, arg1: nodeError, type: typeError},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+6, arg1: node+4, type: type0},
+// CHECK:STDOUT:   {kind: TupleIndex, arg0: node+7, arg1: nodeError, type: typeError},
 // CHECK:STDOUT:   {kind: Return},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
@@ -63,6 +64,7 @@ fn Run() {
 // CHECK:STDOUT:     node+6,
 // CHECK:STDOUT:     node+7,
 // CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 // CHECK:STDOUT:
@@ -81,7 +83,8 @@ fn Run() {
 // CHECK:STDOUT:   %.loc13_4.1: type = tuple_type ()
 // CHECK:STDOUT:   %.loc13_4.2: () = call @F()
 // CHECK:STDOUT:   %.loc13_7: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc13_4.3: () = materialize_temporary
-// CHECK:STDOUT:   %.loc13_8: <error> = tuple_index %.loc13_4.3, <error>
+// CHECK:STDOUT:   %.loc13_4.3: () = temporary_storage
+// CHECK:STDOUT:   %.loc13_4.4: () = temporary %.loc13_4.3, %.loc13_4.2
+// CHECK:STDOUT:   %.loc13_8: <error> = tuple_index %.loc13_4.4, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 6 - 6
toolchain/semantics/testdata/index/tuple_return_value_access.carbon

@@ -54,9 +54,9 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type2},
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type1},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type2},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+14, arg1: node+12},
-// CHECK:STDOUT:   {kind: TupleIndex, arg0: node+14, arg1: node+13, type: type1},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type2},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+14, arg1: node+12, type: type2},
+// CHECK:STDOUT:   {kind: TupleIndex, arg0: node+15, arg1: node+13, type: type1},
 // CHECK:STDOUT:   {kind: BindValue, arg0: node+16, type: type1},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+17},
 // CHECK:STDOUT: ]
@@ -117,9 +117,9 @@ fn Run() -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc10_11.1: (i32,) = call @F()
 // CHECK:STDOUT:   %.loc10_14: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc10_11.2: (i32,) = materialize_temporary
-// CHECK:STDOUT:   assign %.loc10_11.2, %.loc10_11.1
-// CHECK:STDOUT:   %.loc10_15.1: i32 = tuple_index %.loc10_11.2, %.loc10_14
+// CHECK:STDOUT:   %.loc10_11.2: (i32,) = temporary_storage
+// CHECK:STDOUT:   %.loc10_11.3: (i32,) = temporary %.loc10_11.2, %.loc10_11.1
+// CHECK:STDOUT:   %.loc10_15.1: i32 = tuple_index %.loc10_11.3, %.loc10_14
 // CHECK:STDOUT:   %.loc10_15.2: i32 = bind_value %.loc10_15.1
 // CHECK:STDOUT:   return %.loc10_15.2
 // CHECK:STDOUT: }

+ 14 - 14
toolchain/semantics/testdata/operators/and.carbon

@@ -44,16 +44,16 @@ fn And() -> bool {
 // CHECK:STDOUT:   {kind: VarStorage, arg0: str1, type: type0},
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+11, arg1: node+10},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+11, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+11, arg1: node+10, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+12, type: type0},
 // CHECK:STDOUT:   {kind: BoolLiteral, arg0: false, type: type0},
 // CHECK:STDOUT:   {kind: BranchIf, arg0: block8, arg1: node+13},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+14},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+18, arg1: node+17},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+18, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+18, arg1: node+17, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+19, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+20},
 // CHECK:STDOUT:   {kind: BlockArg, arg0: block9, type: type0},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+22},
@@ -126,18 +126,18 @@ fn And() -> bool {
 // CHECK:STDOUT: fn @And() -> bool {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc11_11.1: bool = call @F()
-// CHECK:STDOUT:   %.loc11_11.2: bool = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_11.2, %.loc11_11.1
-// CHECK:STDOUT:   %.loc11_11.3: bool = bind_value %.loc11_11.2
+// CHECK:STDOUT:   %.loc11_11.2: bool = temporary_storage
+// CHECK:STDOUT:   %.loc11_11.3: bool = temporary %.loc11_11.2, %.loc11_11.1
+// CHECK:STDOUT:   %.loc11_11.4: bool = bind_value %.loc11_11.3
 // CHECK:STDOUT:   %.loc11_14.1: bool = bool_literal false
-// CHECK:STDOUT:   if %.loc11_11.3 br !and.rhs else br !and.result(%.loc11_14.1)
+// CHECK:STDOUT:   if %.loc11_11.4 br !and.rhs else br !and.result(%.loc11_14.1)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !and.rhs:
 // CHECK:STDOUT:   %.loc11_19.1: bool = call @G()
-// CHECK:STDOUT:   %.loc11_19.2: bool = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_19.2, %.loc11_19.1
-// CHECK:STDOUT:   %.loc11_19.3: bool = bind_value %.loc11_19.2
-// CHECK:STDOUT:   br !and.result(%.loc11_19.3)
+// CHECK:STDOUT:   %.loc11_19.2: bool = temporary_storage
+// CHECK:STDOUT:   %.loc11_19.3: bool = temporary %.loc11_19.2, %.loc11_19.1
+// CHECK:STDOUT:   %.loc11_19.4: bool = bind_value %.loc11_19.3
+// CHECK:STDOUT:   br !and.result(%.loc11_19.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !and.result:
 // CHECK:STDOUT:   %.loc11_14.2: bool = block_arg !and.result

+ 14 - 14
toolchain/semantics/testdata/operators/or.carbon

@@ -44,17 +44,17 @@ fn Or() -> bool {
 // CHECK:STDOUT:   {kind: VarStorage, arg0: str1, type: type0},
 // CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+11, arg1: node+10},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+11, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+11, arg1: node+10, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+12, type: type0},
 // CHECK:STDOUT:   {kind: UnaryOperatorNot, arg0: node+13, type: type0},
 // CHECK:STDOUT:   {kind: BoolLiteral, arg0: true, type: type0},
 // CHECK:STDOUT:   {kind: BranchIf, arg0: block8, arg1: node+14},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+15},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type0},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+19, arg1: node+18},
-// CHECK:STDOUT:   {kind: BindValue, arg0: node+19, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type0},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+19, arg1: node+18, type: type0},
+// CHECK:STDOUT:   {kind: BindValue, arg0: node+20, type: type0},
 // CHECK:STDOUT:   {kind: BranchWithArg, arg0: block9, arg1: node+21},
 // CHECK:STDOUT:   {kind: BlockArg, arg0: block9, type: type0},
 // CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+23},
@@ -128,19 +128,19 @@ fn Or() -> bool {
 // CHECK:STDOUT: fn @Or() -> bool {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc11_11.1: bool = call @F()
-// CHECK:STDOUT:   %.loc11_11.2: bool = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_11.2, %.loc11_11.1
-// CHECK:STDOUT:   %.loc11_11.3: bool = bind_value %.loc11_11.2
-// CHECK:STDOUT:   %.loc11_14.1: bool = not %.loc11_11.3
+// CHECK:STDOUT:   %.loc11_11.2: bool = temporary_storage
+// CHECK:STDOUT:   %.loc11_11.3: bool = temporary %.loc11_11.2, %.loc11_11.1
+// CHECK:STDOUT:   %.loc11_11.4: bool = bind_value %.loc11_11.3
+// CHECK:STDOUT:   %.loc11_14.1: bool = not %.loc11_11.4
 // CHECK:STDOUT:   %.loc11_14.2: bool = bool_literal true
 // CHECK:STDOUT:   if %.loc11_14.1 br !or.rhs else br !or.result(%.loc11_14.2)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !or.rhs:
 // CHECK:STDOUT:   %.loc11_18.1: bool = call @G()
-// CHECK:STDOUT:   %.loc11_18.2: bool = materialize_temporary
-// CHECK:STDOUT:   assign %.loc11_18.2, %.loc11_18.1
-// CHECK:STDOUT:   %.loc11_18.3: bool = bind_value %.loc11_18.2
-// CHECK:STDOUT:   br !or.result(%.loc11_18.3)
+// CHECK:STDOUT:   %.loc11_18.2: bool = temporary_storage
+// CHECK:STDOUT:   %.loc11_18.3: bool = temporary %.loc11_18.2, %.loc11_18.1
+// CHECK:STDOUT:   %.loc11_18.4: bool = bind_value %.loc11_18.3
+// CHECK:STDOUT:   br !or.result(%.loc11_18.4)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !or.result:
 // CHECK:STDOUT:   %.loc11_14.3: bool = block_arg !or.result

+ 6 - 6
toolchain/semantics/testdata/pointer/fail_address_of_value.carbon

@@ -185,9 +185,9 @@ fn AddressOfParameter(param: i32) {
 // CHECK:STDOUT:   {kind: BinaryOperatorAdd, arg0: node+35, arg1: node+36, type: type0},
 // CHECK:STDOUT:   {kind: AddressOf, arg0: node+37, type: type2},
 // CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type1},
-// CHECK:STDOUT:   {kind: MaterializeTemporary, type: type1},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+40, arg1: node+39},
-// CHECK:STDOUT:   {kind: StructAccess, arg0: node+40, arg1: member0, type: type0},
+// CHECK:STDOUT:   {kind: TemporaryStorage, type: type1},
+// CHECK:STDOUT:   {kind: Temporary, arg0: node+40, arg1: node+39, type: type1},
+// CHECK:STDOUT:   {kind: StructAccess, arg0: node+41, arg1: member0, type: type0},
 // CHECK:STDOUT:   {kind: AddressOf, arg0: node+42, type: type2},
 // CHECK:STDOUT:   {kind: BoolLiteral, arg0: true, type: type3},
 // CHECK:STDOUT:   {kind: UnaryOperatorNot, arg0: node+44, type: type3},
@@ -394,9 +394,9 @@ fn AddressOfParameter(param: i32) {
 // CHECK:STDOUT:   %.loc42_7: i32 = add %.loc42_5, %.loc42_9
 // CHECK:STDOUT:   %.loc42_3: i32* = address_of %.loc42_7
 // CHECK:STDOUT:   %.loc46_5.1: {.a: i32} = call @H()
-// CHECK:STDOUT:   %.loc46_5.2: {.a: i32} = materialize_temporary
-// CHECK:STDOUT:   assign %.loc46_5.2, %.loc46_5.1
-// CHECK:STDOUT:   %.loc46_7: i32 = struct_access %.loc46_5.2, member0
+// CHECK:STDOUT:   %.loc46_5.2: {.a: i32} = temporary_storage
+// CHECK:STDOUT:   %.loc46_5.3: {.a: i32} = temporary %.loc46_5.2, %.loc46_5.1
+// CHECK:STDOUT:   %.loc46_7: i32 = struct_access %.loc46_5.3, member0
 // CHECK:STDOUT:   %.loc46_3: i32* = address_of %.loc46_7
 // CHECK:STDOUT:   %.loc50_9: bool = bool_literal true
 // CHECK:STDOUT:   %.loc50_5: bool = not %.loc50_9

+ 2 - 5
toolchain/semantics/testdata/return/tuple.carbon

@@ -51,8 +51,7 @@ fn Main() -> (i32, i32) {
 // CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type1},
 // CHECK:STDOUT:   {kind: StubReference, arg0: node+9, type: type1},
 // CHECK:STDOUT:   {kind: TupleValue, arg0: block5, type: type2},
-// CHECK:STDOUT:   {kind: Assign, arg0: node+5, arg1: node+11},
-// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+11},
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: node_blocks: [
 // CHECK:STDOUT:   [
@@ -79,7 +78,6 @@ fn Main() -> (i32, i32) {
 // CHECK:STDOUT:     node+10,
 // CHECK:STDOUT:     node+11,
 // CHECK:STDOUT:     node+12,
-// CHECK:STDOUT:     node+13,
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT:   [
 // CHECK:STDOUT:     node+8,
@@ -98,6 +96,5 @@ fn Main() -> (i32, i32) {
 // CHECK:STDOUT:   %.loc9_15.1: i32 = int_literal 35
 // CHECK:STDOUT:   %.loc9_15.2: i32 = stub_reference %.loc9_15.1
 // CHECK:STDOUT:   %.loc9_17: (i32, i32) = tuple_value (%.loc9_11.2, %.loc9_15.2)
-// CHECK:STDOUT:   assign %return, %.loc9_17
-// CHECK:STDOUT:   return
+// CHECK:STDOUT:   return %.loc9_17
 // CHECK:STDOUT: }