Procházet zdrojové kódy

Make continuations consistently "shallow" (#874)

Prior to this change, `__await` would make a deep copy of the continuation stack, but shallow-copy the individual stack frames within it. As a result, continuations appeared to have shallow semantics so long as the continuation stack had only a single frame.

This change also removes an obsolete test from the brief period when we intended continuations to have deep-copy semantics, which has been passing basically by accident.
Geoff Romer před 4 roky
rodič
revize
d177a08e01

+ 12 - 9
executable_semantics/interpreter/interpreter.cpp

@@ -1002,11 +1002,12 @@ auto Interpreter::StepStmt() -> Transition {
       todo.Push(arena->New<StatementAction>(
           arena->New<Return>(arena, stmt->source_loc())));
       todo.Push(arena->New<StatementAction>(cast<Continuation>(*stmt).Body()));
+      auto continuation_stack = arena->New<std::vector<Nonnull<Frame*>>>();
       auto continuation_frame =
           arena->New<Frame>("__continuation", scopes, todo);
+      continuation_stack->push_back(continuation_frame);
       Address continuation_address =
-          heap.AllocateValue(arena->New<ContinuationValue>(
-              std::vector<Nonnull<Frame*>>({continuation_frame})));
+          heap.AllocateValue(arena->New<ContinuationValue>(continuation_stack));
       // Store the continuation's address in the frame.
       continuation_frame->continuation = continuation_address;
       // Bind the continuation object to the continuation variable
@@ -1031,11 +1032,11 @@ auto Interpreter::StepStmt() -> Transition {
                 arena->New<TupleLiteral>(stmt->source_loc())));
         frame->todo.Push(ignore_result);
         // Push the continuation onto the current stack.
-        const std::vector<Nonnull<Frame*>>& continuation_vector =
-            cast<ContinuationValue>(*act->results()[0]).Stack();
-        for (auto frame_iter = continuation_vector.rbegin();
-             frame_iter != continuation_vector.rend(); ++frame_iter) {
-          stack.Push(*frame_iter);
+        std::vector<Nonnull<Frame*>>& continuation_vector =
+            *cast<ContinuationValue>(*act->results()[0]).Stack();
+        while (!continuation_vector.empty()) {
+          stack.Push(continuation_vector.back());
+          continuation_vector.pop_back();
         }
         return ManualTransition{};
       }
@@ -1048,8 +1049,10 @@ auto Interpreter::StepStmt() -> Transition {
         paused.push_back(stack.Pop());
       } while (paused.back()->continuation == std::nullopt);
       // Update the continuation with the paused stack.
-      heap.Write(*paused.back()->continuation,
-                 arena->New<ContinuationValue>(paused), stmt->source_loc());
+      const auto& continuation = cast<ContinuationValue>(
+          *heap.Read(*paused.back()->continuation, stmt->source_loc()));
+      CHECK(continuation.Stack()->empty());
+      *continuation.Stack() = std::move(paused);
       return ManualTransition{};
   }
 }

+ 3 - 3
executable_semantics/interpreter/value.cpp

@@ -292,7 +292,7 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::ContinuationValue: {
       out << "{";
       llvm::ListSeparator sep(" :: ");
-      for (Nonnull<Frame*> frame : cast<ContinuationValue>(*this).Stack()) {
+      for (Nonnull<Frame*> frame : *cast<ContinuationValue>(*this).Stack()) {
         out << sep << *frame;
       }
       out << "}";
@@ -352,8 +352,8 @@ auto CopyVal(Nonnull<Arena*> arena, Nonnull<const Value*> val,
     case Value::Kind::PointerValue:
       return arena->New<PointerValue>(cast<PointerValue>(*val).Val());
     case Value::Kind::ContinuationValue:
-      // Copying a continuation is "shallow".
-      return val;
+      return arena->New<ContinuationValue>(
+          cast<ContinuationValue>(*val).Stack());
     case Value::Kind::FunctionType: {
       const auto& fn_type = cast<FunctionType>(*val);
       return arena->New<FunctionType>(

+ 10 - 4
executable_semantics/interpreter/value.h

@@ -493,19 +493,25 @@ class VariableType : public Value {
 };
 
 // A first-class continuation representation of a fragment of the stack.
+// A continuation value behaves like a pointer to the underlying stack
+// fragment, which is exposed by `Stack()`.
 class ContinuationValue : public Value {
  public:
-  explicit ContinuationValue(std::vector<Nonnull<Frame*>> stack)
-      : Value(Kind::ContinuationValue), stack(std::move(stack)) {}
+  explicit ContinuationValue(Nonnull<std::vector<Nonnull<Frame*>>*> stack)
+      : Value(Kind::ContinuationValue), stack(stack) {}
 
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::ContinuationValue;
   }
 
-  auto Stack() const -> const std::vector<Nonnull<Frame*>>& { return stack; }
+  // The call stack of the suspended continuation, starting with the top
+  // frame (the reverse of the usual order). Note that this provides mutable
+  // access, even when *this is const, because of the reference-like semantics
+  // of ContinuationValue.
+  auto Stack() const -> Nonnull<std::vector<Nonnull<Frame*>>*> { return stack; }
 
  private:
-  std::vector<Nonnull<Frame*>> stack;
+  Nonnull<std::vector<Nonnull<Frame*>>*> stack;
 };
 
 // The String type.

+ 0 - 32
executable_semantics/testdata/experimental_continuation/copy_in_continuation.carbon

@@ -1,32 +0,0 @@
-// 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
-//
-// RUN: executable_semantics %s 2>&1 | \
-// RUN:   FileCheck --match-full-lines --allow-unused-prefixes=false %s
-// RUN: executable_semantics --trace %s 2>&1 | \
-// RUN:   FileCheck --match-full-lines --allow-unused-prefixes %s
-// AUTOUPDATE: executable_semantics %s
-// CHECK: result: 3
-
-package ExecutableSemanticsTest api;
-
-// Test the way in which copying of continuations interacts with data
-// on the stack such as the variable `x`. In this example the copy
-// happens after the variable `x` is created.
-
-var y: i32 = 0;
-
-fn main() -> i32 {
-  __continuation k1 {
-    var x: i32 = 0;
-    x = x + 1;
-    __await;
-    x = x + 2;
-    y = x;
-  }
-  __run k1;
-  var k2: __Continuation = k1;
-  __run k2;
-  return y;
-}

+ 9 - 4
executable_semantics/testdata/experimental_continuation/shallow_copy.carbon

@@ -14,12 +14,17 @@ package ExecutableSemanticsTest api;
 // Assignment for continuations is shallow, so `k2` refers to the same
 // continuation as `k1`.
 
+var x: i32 = 0;
+
+fn Foo() {
+  x = x + 1;
+  __await;
+  x = x + 2;
+}
+
 fn main() -> i32 {
-  var x: i32 = 0;
   __continuation k1 {
-    x = x + 1;
-    __await;
-    x = x + 2;
+    Foo();
   }
   var k2: __Continuation = k1;
   __run k1;