Parcourir la source

Explorer: Avoid unecessary copies when a value binding is created from a value expression (#3000)

#### Functional changes

* Avoid unnecessary copies when a value binding is created from a value
expression in call parameters
* Ensure the result of the value expression bound is destroyed
* Provide storage to initializing expressions used in call parameters

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Adrien Leravat il y a 2 ans
Parent
commit
9edfa9cad3

+ 39 - 34
explorer/interpreter/interpreter.cpp

@@ -316,9 +316,8 @@ static auto InitializePlaceholderValue(const ValueNodeView& value_node,
       break;
     case ExpressionCategory::Value:
       if (v.expression_category() == ExpressionCategory::Value) {
-        // TODO: Ensure value expressions of temporaries are registered as
-        // allocation to allow us to reference it without the need for a copy.
-        bindings->Initialize(value_node, v.value());
+        // We assume values are strictly nested for now.
+        bindings->BindValue(value_node, v.value());
       } else if (v.expression_category() == ExpressionCategory::Reference) {
         // Bind the reference expression value directly.
         CARBON_CHECK(v.address())
@@ -1115,7 +1114,9 @@ auto Interpreter::CallFunction(const CallExpression& call,
                << "` that has not been fully type-checked";
       }
 
-      RuntimeScope binding_scope(&heap_);
+      // Enter the binding scope to make any deduced arguments visible before
+      // we resolve the self type and parameter type.
+      auto& binding_scope = todo_.CurrentAction().scope().value();
 
       // Bring the deduced arguments and their witnesses into scope.
       for (const auto& [bind, val] : call.deduced_args()) {
@@ -1137,10 +1138,6 @@ auto Interpreter::CallFunction(const CallExpression& call,
         binding_scope.BindValue(impl_bind->original(), witness);
       }
 
-      // Enter the binding scope to make any deduced arguments visible before
-      // we resolve the self type and parameter type.
-      todo_.CurrentAction().StartScope(std::move(binding_scope));
-
       RuntimeScope function_scope(&heap_);
       BindingMap generic_args;
 
@@ -1706,46 +1703,58 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
     }
     case ExpressionKind::CallExpression: {
       const auto& call = cast<CallExpression>(exp);
-      unsigned int num_witnesses = call.witnesses().size();
+      CARBON_CHECK(call.argument().kind() == ExpressionKind::TupleLiteral);
+      const auto& args = cast<TupleLiteral>(call.argument());
+      const int num_args = args.fields().size();
+      const int num_witnesses = call.witnesses().size();
+      const int function_call_pos = 1 + num_args + num_witnesses;
       if (act.pos() == 0) {
         //    { {e1(e2) :: C, E, F} :: S, H}
         // -> { {e1 :: [](e2) :: C, E, F} :: S, H}
+        act.StartScope(RuntimeScope(&heap_));
         return todo_.Spawn(
             std::make_unique<ValueExpressionAction>(&call.function()));
-      } else if (act.pos() == 1) {
-        //    { { v :: [](e) :: C, E, F} :: S, H}
-        // -> { { e :: v([]) :: C, E, F} :: S, H}
-        bool preserve_nested_categories =
-            (act.results()[0]->kind() !=
-             Value::Kind::AlternativeConstructorValue);
-        return todo_.Spawn(std::make_unique<ExpressionAction>(
-            &call.argument(), preserve_nested_categories));
-      } else if (num_witnesses > 0 &&
-                 act.pos() < 2 + static_cast<int>(num_witnesses)) {
+      } else if (act.pos() < 1 + num_args) {
+        const auto* field = args.fields()[act.pos() - 1];
+        std::optional<AllocationId> alloc;
+        if (field->expression_category() == ExpressionCategory::Initializing) {
+          alloc = heap_.AllocateValue(
+              arena_->New<UninitializedValue>(&field->static_type()));
+          act.scope()->BindLifetimeToScope(Address(*alloc));
+        }
+        return todo_.Spawn(
+            std::make_unique<ExpressionAction>(field, false, alloc));
+      } else if (act.pos() < function_call_pos) {
         auto iter = call.witnesses().begin();
-        std::advance(iter, act.pos() - 2);
+        std::advance(iter, act.pos() - 1 - num_args);
         return todo_.Spawn(std::make_unique<WitnessAction>(
             cast<Witness>(iter->second), call.source_loc()));
-      } else if (act.pos() == 2 + static_cast<int>(num_witnesses)) {
+      } else if (act.pos() == function_call_pos) {
         //    { { v2 :: v1([]) :: C, E, F} :: S, H}
         // -> { {C',E',F'} :: {C, E, F} :: S, H}
+        // Prepare parameters tuple.
+        std::vector<Nonnull<const Value*>> param_values;
+        for (int i = 1; i <= num_args; ++i) {
+          param_values.push_back(act.results()[i]);
+        }
+        const auto* param_tuple = arena_->New<TupleValue>(param_values);
+        // Prepare witnesses.
         ImplWitnessMap witnesses;
         if (num_witnesses > 0) {
-          int i = 2;
+          int i = 1 + num_args;
           for (const auto& [impl_bind, impl_exp] : call.witnesses()) {
             witnesses[impl_bind] = act.results()[i];
             ++i;
           }
         }
-        return CallFunction(call, act.results()[0], act.results()[1],
+        return CallFunction(call, act.results()[0], param_tuple,
                             std::move(witnesses), act.location_received());
-      } else if (act.pos() == 3 + static_cast<int>(num_witnesses)) {
-        if (act.results().size() < 3 + num_witnesses) {
+      } else if (act.pos() == 1 + function_call_pos) {
+        if (static_cast<int>(act.results().size()) < 1 + function_call_pos) {
           // Control fell through without explicit return.
           return todo_.FinishAction(TupleValue::Empty());
         } else {
-          return todo_.FinishAction(
-              act.results()[2 + static_cast<int>(num_witnesses)]);
+          return todo_.FinishAction(act.results()[function_call_pos]);
         }
       } else {
         CARBON_FATAL() << "in StepValueExp with Call pos " << act.pos();
@@ -2517,16 +2526,12 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
         //    { {v :: return [] :: C, E, F} :: {C', E', F'} :: S, H}
         // -> { {v :: C', E', F'} :: S, H}
         const CallableDeclaration& function = cast<Return>(stmt).function();
-        CARBON_ASSIGN_OR_RETURN(
-            Nonnull<const Value*> return_value,
-            Convert(act.results()[0], &function.return_term().static_type(),
-                    stmt.source_loc()));
         // Write to initialized storage location, if any.
         if (const auto location = act.location_received()) {
-          CARBON_RETURN_IF_ERROR(
-              heap_.Write(Address(*location), return_value, stmt.source_loc()));
+          CARBON_RETURN_IF_ERROR(heap_.Write(
+              Address(*location), act.results()[0], stmt.source_loc()));
         }
-        return todo_.UnwindPast(*function.body(), return_value);
+        return todo_.UnwindPast(*function.body(), act.results()[0]);
       }
   }
 }

+ 39 - 0
explorer/testdata/let/value_expr_binding_from_initializing_expr.carbon

@@ -0,0 +1,39 @@
+// 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
+// CHECK:STDOUT: Bind from c initializing expression
+// CHECK:STDOUT: 0: Heap{}, 1: !Uninit<class C>
+// CHECK:STDOUT: 0: Heap{}, 1: C{}
+// CHECK:STDOUT: Binding scope end
+// CHECK:STDOUT: c destroyed
+// CHECK:STDOUT: 0: Heap{}, 1: !!C{}
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+class C {
+  fn Create() -> C {
+    heap.PrintAllocs();
+    return Create2();
+  }
+  fn Create2() -> C {
+    return {};
+  }
+  destructor[self: Self] {
+    Print("c destroyed");
+  }
+}
+
+fn CallWithValueExpressionBinding(c: C) {
+    heap.PrintAllocs();
+    Print("Binding scope end");
+}
+
+fn Main() -> i32 {
+  Print("Bind from c initializing expression");
+  CallWithValueExpressionBinding(C.Create());
+  heap.PrintAllocs();
+  return 0;
+}

+ 32 - 0
explorer/testdata/let/value_expr_binding_from_temp_value.carbon

@@ -0,0 +1,32 @@
+// 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
+// CHECK:STDOUT: 0: Heap{}
+// CHECK:STDOUT: Bind from 'temporary' c value expression
+// CHECK:STDOUT: Binding scope end
+// CHECK:STDOUT: c destroyed
+// CHECK:STDOUT: 0: Heap{}, 1: !!C{}
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+class C {
+  destructor[self: Self] {
+    Print("c destroyed");
+  }
+}
+
+fn CallWithValueExpressionBinding(c: C) {
+    Print("Binding scope end");
+}
+
+fn Main() -> i32 {
+  heap.PrintAllocs();
+
+  Print("Bind from 'temporary' c value expression");
+  CallWithValueExpressionBinding({});
+  heap.PrintAllocs();
+  return 0;
+}

+ 1 - 2
explorer/testdata/let/value_expr_binding_from_value.carbon

@@ -6,8 +6,7 @@
 // CHECK:STDOUT: 0: Heap{}, 1: C{}
 // CHECK:STDOUT: Bind from c value expression
 // CHECK:STDOUT: Binding scope end
-// CHECK:STDOUT: c destroyed
-// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{}
+// CHECK:STDOUT: 0: Heap{}, 1: C{}
 // CHECK:STDOUT: c destroyed
 // CHECK:STDOUT: result: 0
 

+ 1 - 2
explorer/testdata/let/value_expr_from_value.carbon

@@ -5,8 +5,7 @@
 // AUTOUPDATE
 // CHECK:STDOUT: 0: Heap{}, 1: C{}
 // CHECK:STDOUT: Initialize c from value expression
-// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: C{}
-// CHECK:STDOUT: c destroyed
+// CHECK:STDOUT: 0: Heap{}, 1: C{}
 // CHECK:STDOUT: c destroyed
 // CHECK:STDOUT: result: 0
 

+ 40 - 0
explorer/testdata/var/local/reference_expr_binding_from_initializing_expr.carbon

@@ -0,0 +1,40 @@
+// 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
+// CHECK:STDOUT: Bind from c initializing expression
+// CHECK:STDOUT: 0: Heap{}, 1: !Uninit<class C>
+// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: C{}
+// CHECK:STDOUT: Binding scope end
+// CHECK:STDOUT: c destroyed
+// CHECK:STDOUT: c destroyed
+// CHECK:STDOUT: 0: Heap{}, 1: !!C{}, 2: !!C{}
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+class C {
+  fn Create() -> C {
+    heap.PrintAllocs();
+    return Create2();
+  }
+  fn Create2() -> C {
+    return {};
+  }
+  destructor[self: Self] {
+    Print("c destroyed");
+  }
+}
+
+fn CallWithReferenceExpressionBinding(var c: C) {
+    heap.PrintAllocs();
+    Print("Binding scope end");
+}
+
+fn Main() -> i32 {
+  Print("Bind from c initializing expression");
+  CallWithReferenceExpressionBinding(C.Create());
+  heap.PrintAllocs();
+  return 0;
+}

+ 34 - 48
explorer/trace_testdata/full_trace.carbon

@@ -38,7 +38,7 @@ fn N.Foo(n: i32) -> i32 {
 fn Main() -> i32 {
   var x: i32 = N.Foo(0);
   return x;
-// CHECK:STDOUT: --- declared `Main` as `fn Main` in `package` (full_trace.carbon:[[@LINE+310]])
+// CHECK:STDOUT: --- declared `Main` as `fn Main` in `package` (full_trace.carbon:[[@LINE+296]])
 // CHECK:STDOUT: ** resolving decl `interface TestInterface` (full_trace.carbon:[[@LINE-15]])
 // CHECK:STDOUT: --- marked `TestInterface` declared but not usable in `package`
 // CHECK:STDOUT: --- marked `TestInterface` usable in `package`
@@ -58,10 +58,10 @@ fn Main() -> i32 {
 // CHECK:STDOUT: ** finished resolving stmt `return (n + 1);` (full_trace.carbon:[[@LINE-24]])
 // CHECK:STDOUT: ** finished resolving stmt `{return (n + 1);}` (full_trace.carbon:[[@LINE-23]])
 // CHECK:STDOUT: ** finished resolving decl `fn N.Foo` (full_trace.carbon:[[@LINE-24]])
-// CHECK:STDOUT: ** resolving decl `fn Main` (full_trace.carbon:[[@LINE+290]])
+// CHECK:STDOUT: ** resolving decl `fn Main` (full_trace.carbon:[[@LINE+276]])
 // CHECK:STDOUT: --- marked `Main` declared but not usable in `package`
 // CHECK:STDOUT: --- marked `Main` usable in `package`
-// CHECK:STDOUT: ** resolving stmt `{var x: i32 = N.Foo(0);return x;}` (full_trace.carbon:[[@LINE+287]])
+// CHECK:STDOUT: ** resolving stmt `{var x: i32 = N.Foo(0);return x;}` (full_trace.carbon:[[@LINE+273]])
 // CHECK:STDOUT: ** resolving stmt `var x: i32 = N.Foo(0);` (full_trace.carbon:[[@LINE-26]])
 // CHECK:STDOUT: --- resolved `N` as `namespace N` in `package` (full_trace.carbon:[[@LINE-27]])
 // CHECK:STDOUT: --- resolved `Foo` as `fn N.Foo` in `namespace N` (full_trace.carbon:[[@LINE-28]])
@@ -70,8 +70,8 @@ fn Main() -> i32 {
 // CHECK:STDOUT: ** resolving stmt `return x;` (full_trace.carbon:[[@LINE-30]])
 // CHECK:STDOUT: --- resolved `x` as `x` in `{var x: i32 = N.Foo(0);return x;}` (full_trace.carbon:[[@LINE-31]])
 // CHECK:STDOUT: ** finished resolving stmt `return x;` (full_trace.carbon:[[@LINE-32]])
-// CHECK:STDOUT: ** finished resolving stmt `{var x: i32 = N.Foo(0);return x;}` (full_trace.carbon:[[@LINE+278]])
-// CHECK:STDOUT: ** finished resolving decl `fn Main` (full_trace.carbon:[[@LINE+277]])
+// CHECK:STDOUT: ** finished resolving stmt `{var x: i32 = N.Foo(0);return x;}` (full_trace.carbon:[[@LINE+264]])
+// CHECK:STDOUT: ** finished resolving decl `fn Main` (full_trace.carbon:[[@LINE+263]])
 // CHECK:STDOUT: --- resolved `Main` as `fn Main` in `package` (<Main()>:0)
 // CHECK:STDOUT: ********** resolving control flow **********
 // CHECK:STDOUT: ********** type checking **********
@@ -213,7 +213,7 @@ fn Main() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:  .0.
-// CHECK:STDOUT: --- step decl fn Main .0. (full_trace.carbon:[[@LINE+135]]) --->
+// CHECK:STDOUT: --- step decl fn Main .0. (full_trace.carbon:[[@LINE+121]]) --->
 // CHECK:STDOUT: (-) stack-pop: fn Main ()-> i32 {
 // CHECK:STDOUT: {
 // CHECK:STDOUT: var x: i32 = N.Foo(0);
@@ -232,99 +232,85 @@ fn Main() -> i32 {
 // CHECK:STDOUT: (-) stack-pop: Main .0.
 // CHECK:STDOUT: (-) stack-pop: Main .1. {{[[][[]}}fun<Main>]]
 // CHECK:STDOUT: --- step exp Main() .1. (<Main()>:0) --->
-// CHECK:STDOUT: (+) stack-push: () .0.
-// CHECK:STDOUT: --- step exp () .0. (<Main()>:0) --->
-// CHECK:STDOUT: (-) stack-pop: () .0.
-// CHECK:STDOUT: --- step exp Main() .2. (<Main()>:0) --->
 // CHECK:STDOUT: calling function: fun<Main>
 // CHECK:STDOUT: match pattern ()
 // CHECK:STDOUT: from value expression with value ()
 // CHECK:STDOUT: (+) stack-push: .0. {}
 // CHECK:STDOUT: (+) stack-push: {var x: i32 = N.Foo(0);return x;} .0.
-// CHECK:STDOUT: --- step stmt {var x: i32 = N.Foo(0);return x;} .0. (full_trace.carbon:[[@LINE+107]]) --->
+// CHECK:STDOUT: --- step stmt {var x: i32 = N.Foo(0);return x;} .0. (full_trace.carbon:[[@LINE+97]]) --->
 // CHECK:STDOUT: (+) stack-push: var x: i32 = N.Foo(0); .0.
-// CHECK:STDOUT: --- step stmt var x: i32 = N.Foo(0); .0. (full_trace.carbon:[[@LINE-207]]) --->
+// CHECK:STDOUT: --- step stmt var x: i32 = N.Foo(0); .0. (full_trace.carbon:[[@LINE-203]]) --->
 // CHECK:STDOUT: (+) memory-alloc: #1 `Uninit<i32>` uninitialized
 // CHECK:STDOUT: (+) stack-push: N.Foo(0) .0.
-// CHECK:STDOUT: --- step exp N.Foo(0) .0. (full_trace.carbon:[[@LINE-210]]) --->
+// CHECK:STDOUT: --- step exp N.Foo(0) .0. (full_trace.carbon:[[@LINE-206]]) --->
 // CHECK:STDOUT: (+) stack-push: N.Foo .0.
 // CHECK:STDOUT: (+) stack-push: N.Foo .0.
-// CHECK:STDOUT: --- step exp N.Foo .0. (full_trace.carbon:[[@LINE-213]]) --->
+// CHECK:STDOUT: --- step exp N.Foo .0. (full_trace.carbon:[[@LINE-209]]) --->
 // CHECK:STDOUT: (-) stack-pop: N.Foo .0.
 // CHECK:STDOUT: (+) stack-push: Foo .0.
-// CHECK:STDOUT: --- step exp Foo .0. (full_trace.carbon:[[@LINE-216]]) --->
+// CHECK:STDOUT: --- step exp Foo .0. (full_trace.carbon:[[@LINE-212]]) --->
 // CHECK:STDOUT: (-) stack-pop: Foo .0.
 // CHECK:STDOUT: (-) stack-pop: N.Foo .1. {{[[][[]}}fun<N.Foo>]]
-// CHECK:STDOUT: --- step exp N.Foo(0) .1. (full_trace.carbon:[[@LINE-219]]) --->
-// CHECK:STDOUT: (+) stack-push: (0) .0.
-// CHECK:STDOUT: --- step exp (0) .0. (full_trace.carbon:[[@LINE-221]]) --->
+// CHECK:STDOUT: --- step exp N.Foo(0) .1. (full_trace.carbon:[[@LINE-215]]) --->
 // CHECK:STDOUT: (+) stack-push: 0 .0.
-// CHECK:STDOUT: --- step exp 0 .0. (full_trace.carbon:[[@LINE-223]]) --->
+// CHECK:STDOUT: --- step exp 0 .0. (full_trace.carbon:[[@LINE-217]]) --->
 // CHECK:STDOUT: (-) stack-pop: 0 .0.
-// CHECK:STDOUT: --- step exp (0) .1. (full_trace.carbon:[[@LINE-225]]) --->
-// CHECK:STDOUT: (-) stack-pop: (0) .1. {{[[][[]}}0]]
-// CHECK:STDOUT: --- step exp N.Foo(0) .2. (full_trace.carbon:[[@LINE-227]]) --->
+// CHECK:STDOUT: --- step exp N.Foo(0) .2. (full_trace.carbon:[[@LINE-219]]) --->
 // CHECK:STDOUT: calling function: fun<N.Foo>
 // CHECK:STDOUT: match pattern (Placeholder<n>,)
 // CHECK:STDOUT: from value expression with value (0,)
 // CHECK:STDOUT: match pattern Placeholder<n>
 // CHECK:STDOUT: from value expression with value 0
-// CHECK:STDOUT: (+) memory-alloc: #2 `0`
-// CHECK:STDOUT: (+) stack-push: .0. {n: i32: lval<Allocation(2)>}
+// CHECK:STDOUT: (+) stack-push: .0. {n: i32: 0}
 // CHECK:STDOUT: (+) stack-push: {return (n + 1);} .0.
-// CHECK:STDOUT: --- step stmt {return (n + 1);} .0. (full_trace.carbon:[[@LINE-239]]) --->
+// CHECK:STDOUT: --- step stmt {return (n + 1);} .0. (full_trace.carbon:[[@LINE-230]]) --->
 // CHECK:STDOUT: (+) stack-push: return (n + 1); .0.
-// CHECK:STDOUT: --- step stmt return (n + 1); .0. (full_trace.carbon:[[@LINE-243]]) --->
+// CHECK:STDOUT: --- step stmt return (n + 1); .0. (full_trace.carbon:[[@LINE-234]]) --->
 // CHECK:STDOUT: (+) stack-push: (n + 1) .0.
 // CHECK:STDOUT: (+) stack-push: (n + 1) .0.
-// CHECK:STDOUT: --- step exp (n + 1) .0. (full_trace.carbon:[[@LINE-246]]) --->
+// CHECK:STDOUT: --- step exp (n + 1) .0. (full_trace.carbon:[[@LINE-237]]) --->
 // CHECK:STDOUT: (+) stack-push: n .0.
 // CHECK:STDOUT: (+) stack-push: n .0.
-// CHECK:STDOUT: --- step exp n .0. (full_trace.carbon:[[@LINE-249]]) --->
-// CHECK:STDOUT: +++ memory-read: #2 `0`
+// CHECK:STDOUT: --- step exp n .0. (full_trace.carbon:[[@LINE-240]]) --->
 // CHECK:STDOUT: (-) stack-pop: n .0.
 // CHECK:STDOUT: (-) stack-pop: n .1. {{[[][[]}}0]]
-// CHECK:STDOUT: --- step exp (n + 1) .1. (full_trace.carbon:[[@LINE-253]]) --->
+// CHECK:STDOUT: --- step exp (n + 1) .1. (full_trace.carbon:[[@LINE-243]]) --->
 // CHECK:STDOUT: (+) stack-push: 1 .0.
 // CHECK:STDOUT: (+) stack-push: 1 .0.
-// CHECK:STDOUT: --- step exp 1 .0. (full_trace.carbon:[[@LINE-256]]) --->
+// CHECK:STDOUT: --- step exp 1 .0. (full_trace.carbon:[[@LINE-246]]) --->
 // CHECK:STDOUT: (-) stack-pop: 1 .0.
 // CHECK:STDOUT: (-) stack-pop: 1 .1. {{[[][[]}}1]]
-// CHECK:STDOUT: --- step exp (n + 1) .2. (full_trace.carbon:[[@LINE-259]]) --->
+// CHECK:STDOUT: --- step exp (n + 1) .2. (full_trace.carbon:[[@LINE-249]]) --->
 // CHECK:STDOUT: (-) stack-pop: (n + 1) .2. {{[[][[]}}0, 1]]
 // CHECK:STDOUT: (-) stack-pop: (n + 1) .1. {{[[][[]}}1]]
-// CHECK:STDOUT: --- step stmt return (n + 1); .1. (full_trace.carbon:[[@LINE-262]]) --->
+// CHECK:STDOUT: --- step stmt return (n + 1); .1. (full_trace.carbon:[[@LINE-252]]) --->
 // CHECK:STDOUT: +++ memory-write: #1 `1`
 // CHECK:STDOUT: (-) stack-pop: return (n + 1); .1. {{[[][[]}}1]]
 // CHECK:STDOUT: (-) stack-pop: {return (n + 1);} .1. {}
-// CHECK:STDOUT: (-) stack-pop: .0. {n: i32: lval<Allocation(2)>}
-// CHECK:STDOUT: (+) stack-push: clean up.0. {n: i32: lval<Allocation(2)>}
+// CHECK:STDOUT: (-) stack-pop: .0. {n: i32: 0}
+// CHECK:STDOUT: (+) stack-push: clean up.0. {n: i32: 0}
 // CHECK:STDOUT: (+) stack-push: clean up.0. {}
 // CHECK:STDOUT: (-) stack-pop: clean up.0. {}
-// CHECK:STDOUT: +++ memory-read: #2 `0`
-// CHECK:STDOUT: (+) stack-push: destroy.0.
-// CHECK:STDOUT: (-) stack-pop: destroy.0.
-// CHECK:STDOUT: (-) memory-dealloc: #2 `0`
-// CHECK:STDOUT: (-) stack-pop: clean up.2. {n: i32: lval<Allocation(2)>}
-// CHECK:STDOUT: --- step exp N.Foo(0) .3. (full_trace.carbon:[[@LINE-270]]) --->
-// CHECK:STDOUT: (-) stack-pop: N.Foo(0) .3. {{[[][[]}}fun<N.Foo>, (0,), 1]] {}
+// CHECK:STDOUT: (-) stack-pop: clean up.0. {n: i32: 0}
+// CHECK:STDOUT: --- step exp N.Foo(0) .3. (full_trace.carbon:[[@LINE-256]]) --->
+// CHECK:STDOUT: (-) stack-pop: N.Foo(0) .3. {{[[][[]}}fun<N.Foo>, 0, 1]] {}
 // CHECK:STDOUT: (+) stack-push: clean up.0. {}
 // CHECK:STDOUT: (-) stack-pop: clean up.0. {}
-// CHECK:STDOUT: --- step stmt var x: i32 = N.Foo(0); .1. (full_trace.carbon:[[@LINE-274]]) --->
+// CHECK:STDOUT: --- step stmt var x: i32 = N.Foo(0); .1. (full_trace.carbon:[[@LINE-260]]) --->
 // CHECK:STDOUT: +++ memory-read: #1 `1`
 // CHECK:STDOUT: match pattern Placeholder<x>
 // CHECK:STDOUT: from initializing expression with value 1
 // CHECK:STDOUT: (-) stack-pop: var x: i32 = N.Foo(0); .1. {{[[][[]}}1]]
 // CHECK:STDOUT: --- step stmt {var x: i32 = N.Foo(0);return x;} .1. (full_trace.carbon:[[@LINE+33]]) --->
 // CHECK:STDOUT: (+) stack-push: return x; .0.
-// CHECK:STDOUT: --- step stmt return x; .0. (full_trace.carbon:[[@LINE-280]]) --->
+// CHECK:STDOUT: --- step stmt return x; .0. (full_trace.carbon:[[@LINE-266]]) --->
 // CHECK:STDOUT: (+) stack-push: x .0.
 // CHECK:STDOUT: (+) stack-push: x .0.
-// CHECK:STDOUT: --- step exp x .0. (full_trace.carbon:[[@LINE-283]]) --->
+// CHECK:STDOUT: --- step exp x .0. (full_trace.carbon:[[@LINE-269]]) --->
 // CHECK:STDOUT: +++ memory-read: #1 `1`
 // CHECK:STDOUT: (-) stack-pop: x .0.
 // CHECK:STDOUT: (-) stack-pop: x .1. {{[[][[]}}ref_expr<Allocation(1)>]]
-// CHECK:STDOUT: --- step stmt return x; .1. (full_trace.carbon:[[@LINE-287]]) --->
+// CHECK:STDOUT: --- step stmt return x; .1. (full_trace.carbon:[[@LINE-273]]) --->
 // CHECK:STDOUT: (-) stack-pop: return x; .1. {{[[][[]}}1]]
 // CHECK:STDOUT: (-) stack-pop: {var x: i32 = N.Foo(0);return x;} .2. {x: i32: lval<Allocation(1)>}
 // CHECK:STDOUT: (-) stack-pop: .0. {}
@@ -336,8 +322,8 @@ fn Main() -> i32 {
 // CHECK:STDOUT: (-) memory-dealloc: #1 `1`
 // CHECK:STDOUT: (-) stack-pop: clean up.2. {x: i32: lval<Allocation(1)>}
 // CHECK:STDOUT: (-) stack-pop: clean up.0. {}
-// CHECK:STDOUT: --- step exp Main() .3. (<Main()>:0) --->
-// CHECK:STDOUT: (-) stack-pop: Main() .3. {{[[][[]}}fun<Main>, (), 1]] {}
+// CHECK:STDOUT: --- step exp Main() .2. (<Main()>:0) --->
+// CHECK:STDOUT: (-) stack-pop: Main() .2. {{[[][[]}}fun<Main>, 1]] {}
 // CHECK:STDOUT: (+) stack-push: clean up.0. {}
 // CHECK:STDOUT: (-) stack-pop: clean up.0. {}
 // CHECK:STDOUT: (-) stack-pop: Main() .1. {{[[][[]}}1]]