Procházet zdrojové kódy

Unify Action and Scope stacks, and eliminate Frame (#880)

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
Geoff Romer před 4 roky
rodič
revize
3bec7f8dc0

+ 51 - 0
executable_semantics/ast/statement.h

@@ -17,6 +17,8 @@
 
 namespace Carbon {
 
+class FunctionDeclaration;
+
 class Statement {
  public:
   enum class Kind {
@@ -167,9 +169,24 @@ class Return : public Statement {
   auto expression() -> Expression& { return *expression_; }
   auto is_omitted_expression() const -> bool { return is_omitted_expression_; }
 
+  // The AST node representing the function body this statement returns from.
+  // Can only be called after ResolveControlFlow has visited this node.
+  //
+  // Note that this function does not represent an edge in the tree
+  // structure of the AST: the return value is not a child of this node,
+  // but an ancestor.
+  auto function() const -> const FunctionDeclaration& { return **function_; }
+
+  // Can only be called once, by ResolveControlFlow.
+  void set_function(Nonnull<const FunctionDeclaration*> function) {
+    CHECK(!function_.has_value());
+    function_ = function;
+  }
+
  private:
   Nonnull<Expression*> expression_;
   bool is_omitted_expression_;
+  std::optional<Nonnull<const FunctionDeclaration*>> function_;
 };
 
 class Sequence : public Statement {
@@ -244,6 +261,23 @@ class Break : public Statement {
   static auto classof(const Statement* stmt) -> bool {
     return stmt->kind() == Kind::Break;
   }
+
+  // The AST node representing the loop this statement breaks out of.
+  // Can only be called after ResolveControlFlow has visited this node.
+  //
+  // Note that this function does not represent an edge in the tree
+  // structure of the AST: the return value is not a child of this node,
+  // but an ancestor.
+  auto loop() const -> const Statement& { return **loop_; }
+
+  // Can only be called once, by ResolveControlFlow.
+  void set_loop(Nonnull<const Statement*> loop) {
+    CHECK(!loop_.has_value());
+    loop_ = loop;
+  }
+
+ private:
+  std::optional<Nonnull<const Statement*>> loop_;
 };
 
 class Continue : public Statement {
@@ -254,6 +288,23 @@ class Continue : public Statement {
   static auto classof(const Statement* stmt) -> bool {
     return stmt->kind() == Kind::Continue;
   }
+
+  // The AST node representing the loop this statement continues.
+  // Can only be called after ResolveControlFlow has visited this node.
+  //
+  // Note that this function does not represent an edge in the tree
+  // structure of the AST: the return value is not a child of this node,
+  // but an ancestor.
+  auto loop() const -> const Statement& { return **loop_; }
+
+  // Can only be called once, by ResolveControlFlow.
+  void set_loop(Nonnull<const Statement*> loop) {
+    CHECK(!loop_.has_value());
+    loop_ = loop;
+  }
+
+ private:
+  std::optional<Nonnull<const Statement*>> loop_;
 };
 
 class Match : public Statement {

+ 17 - 5
executable_semantics/interpreter/BUILD

@@ -7,15 +7,13 @@ package(default_visibility = ["//executable_semantics:__pkg__"])
 # These currently have to be a single build rule because of a dependency cycle
 # in printing.
 cc_library(
-    name = "action_frame_and_value",
+    name = "action_and_value",
     srcs = [
         "action.cpp",
-        "frame.cpp",
         "value.cpp",
     ],
     hdrs = [
         "action.h",
-        "frame.h",
         "value.h",
     ],
     deps = [
@@ -55,6 +53,7 @@ cc_library(
     hdrs = ["exec_program.h"],
     deps = [
         ":interpreter",
+        ":resolve_control_flow",
         ":type_checker",
         "//executable_semantics/ast",
     ],
@@ -74,7 +73,7 @@ cc_library(
     srcs = ["heap.cpp"],
     hdrs = ["heap.h"],
     deps = [
-        ":action_frame_and_value",
+        ":action_and_value",
         ":address",
         "//common:ostream",
         "@llvm-project//llvm:Support",
@@ -90,7 +89,7 @@ cc_library(
         "interpreter.h",
     ],
     deps = [
-        ":action_frame_and_value",
+        ":action_and_value",
         ":address",
         ":heap",
         "//common:check",
@@ -102,6 +101,19 @@ cc_library(
     ],
 )
 
+cc_library(
+    name = "resolve_control_flow",
+    srcs = ["resolve_control_flow.cpp"],
+    hdrs = ["resolve_control_flow.h"],
+    deps = [
+        "//common:check",
+        "//executable_semantics/ast",
+        "//executable_semantics/ast:declaration",
+        "//executable_semantics/ast:statement",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
 cc_library(
     name = "stack",
     hdrs = ["stack.h"],

+ 2 - 0
executable_semantics/interpreter/action.cpp

@@ -35,6 +35,8 @@ void Action::Print(llvm::raw_ostream& out) const {
     case Action::Kind::StatementAction:
       cast<StatementAction>(*this).statement().PrintDepth(1, out);
       break;
+    case Action::Kind::ScopeAction:
+      out << "ScopeAction";
   }
   out << "<" << pos_ << ">";
   if (results_.size() > 0) {

+ 56 - 0
executable_semantics/interpreter/action.h

@@ -11,12 +11,25 @@
 #include "executable_semantics/ast/expression.h"
 #include "executable_semantics/ast/pattern.h"
 #include "executable_semantics/ast/statement.h"
+#include "executable_semantics/interpreter/dictionary.h"
 #include "executable_semantics/interpreter/stack.h"
 #include "executable_semantics/interpreter/value.h"
 #include "llvm/Support/Compiler.h"
 
 namespace Carbon {
 
+using Env = Dictionary<std::string, Address>;
+
+struct Scope {
+  explicit Scope(Env values) : Scope(values, std::vector<std::string>()) {}
+  Scope(Env values, std::vector<std::string> l)
+      : values(values), locals(std::move(l)) {}
+
+  Env values;
+  std::vector<std::string> locals;
+  bool deallocated = false;
+};
+
 class Action {
  public:
   enum class Kind {
@@ -24,6 +37,7 @@ class Action {
     ExpressionAction,
     PatternAction,
     StatementAction,
+    ScopeAction,
   };
 
   Action(const Value&) = delete;
@@ -32,10 +46,23 @@ class Action {
   void AddResult(Nonnull<const Value*> result) { results_.push_back(result); }
 
   void Clear() {
+    CHECK(!scope_.has_value());
     pos_ = 0;
     results_.clear();
   }
 
+  // Associates this action with a new scope, with initial state `scope`.
+  // Values that are local to this scope will be deallocated when this
+  // Action is completed or unwound. Can only be called once on a given
+  // Action.
+  void StartScope(Scope scope) {
+    CHECK(!scope_.has_value());
+    scope_ = std::move(scope);
+  }
+
+  // Returns the scope associated with this Action, if any.
+  auto scope() -> std::optional<Scope>& { return scope_; }
+
   static void PrintList(const Stack<Nonnull<Action*>>& ls,
                         llvm::raw_ostream& out);
 
@@ -69,10 +96,14 @@ class Action {
  private:
   int pos_ = 0;
   std::vector<Nonnull<const Value*>> results_;
+  std::optional<Scope> scope_;
 
   const Kind kind_;
 };
 
+// An Action which implements evaluation of an Expression to produce an
+// lvalue. The result be expressed as a PointerValue which points to the
+// Expression's value.
 class LValAction : public Action {
  public:
   explicit LValAction(Nonnull<const Expression*> expression)
@@ -82,12 +113,15 @@ class LValAction : public Action {
     return action->kind() == Kind::LValAction;
   }
 
+  // The Expression this Action evaluates.
   auto expression() const -> const Expression& { return *expression_; }
 
  private:
   Nonnull<const Expression*> expression_;
 };
 
+// An Action which implements evaluation of an Expression to produce an
+// rvalue. The result is expressed as a Value.
 class ExpressionAction : public Action {
  public:
   explicit ExpressionAction(Nonnull<const Expression*> expression)
@@ -97,12 +131,15 @@ class ExpressionAction : public Action {
     return action->kind() == Kind::ExpressionAction;
   }
 
+  // The Expression this Action evaluates.
   auto expression() const -> const Expression& { return *expression_; }
 
  private:
   Nonnull<const Expression*> expression_;
 };
 
+// An Action which implements evaluation of a Pattern. The result is expressed
+// as a Value.
 class PatternAction : public Action {
  public:
   explicit PatternAction(Nonnull<const Pattern*> pattern)
@@ -112,12 +149,15 @@ class PatternAction : public Action {
     return action->kind() == Kind::PatternAction;
   }
 
+  // The Pattern this Action evaluates.
   auto pattern() const -> const Pattern& { return *pattern_; }
 
  private:
   Nonnull<const Pattern*> pattern_;
 };
 
+// An Action which implements execution of a Statement. Does not produce a
+// result.
 class StatementAction : public Action {
  public:
   explicit StatementAction(Nonnull<const Statement*> statement)
@@ -127,12 +167,28 @@ class StatementAction : public Action {
     return action->kind() == Kind::StatementAction;
   }
 
+  // The Statement this Action executes.
   auto statement() const -> const Statement& { return *statement_; }
 
  private:
   Nonnull<const Statement*> statement_;
 };
 
+// Action which does nothing except introduce a new scope into the action
+// stack. This is useful when a distinct scope doesn't otherwise have an
+// Action it can naturally be associated with. ScopeActions are not associated
+// with AST nodes.
+class ScopeAction : public Action {
+ public:
+  ScopeAction(Scope scope) : Action(Kind::ScopeAction) {
+    StartScope(std::move(scope));
+  }
+
+  static auto classof(const Action* action) -> bool {
+    return action->kind() == Kind::ScopeAction;
+  }
+};
+
 }  // namespace Carbon
 
 #endif  // EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_

+ 2 - 0
executable_semantics/interpreter/exec_program.cpp

@@ -8,6 +8,7 @@
 #include "common/ostream.h"
 #include "executable_semantics/common/arena.h"
 #include "executable_semantics/interpreter/interpreter.h"
+#include "executable_semantics/interpreter/resolve_control_flow.h"
 #include "executable_semantics/interpreter/type_checker.h"
 
 namespace Carbon {
@@ -42,6 +43,7 @@ void ExecProgram(Nonnull<Arena*> arena, AST ast, bool trace) {
     }
     llvm::outs() << "********** type checking **********\n";
   }
+  ResolveControlFlow(ast);
   TypeChecker type_checker(arena, trace);
   TypeChecker::TypeCheckContext p = type_checker.TopLevel(&ast.declarations);
   TypeEnv top = p.types;

+ 0 - 18
executable_semantics/interpreter/frame.cpp

@@ -1,18 +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
-
-#include "executable_semantics/interpreter/frame.h"
-
-#include "common/ostream.h"
-#include "executable_semantics/interpreter/action.h"
-
-namespace Carbon {
-
-void Frame::Print(llvm::raw_ostream& out) const {
-  out << name << "{";
-  Action::PrintList(todo, out);
-  out << "}";
-}
-
-}  // namespace Carbon

+ 0 - 66
executable_semantics/interpreter/frame.h

@@ -1,66 +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
-
-#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_
-#define EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "common/ostream.h"
-#include "executable_semantics/interpreter/action.h"
-#include "executable_semantics/interpreter/address.h"
-#include "executable_semantics/interpreter/dictionary.h"
-#include "executable_semantics/interpreter/stack.h"
-#include "llvm/Support/Compiler.h"
-
-namespace Carbon {
-
-using Env = Dictionary<std::string, Address>;
-
-struct Scope {
-  explicit Scope(Env values) : Scope(values, std::vector<std::string>()) {}
-  Scope(Env values, std::vector<std::string> l)
-      : values(values), locals(std::move(l)) {}
-
-  Env values;
-  std::vector<std::string> locals;
-};
-
-// A frame represents either a function call or a delimited continuation.
-struct Frame {
-  Frame(const Frame&) = delete;
-  auto operator=(const Frame&) -> Frame& = delete;
-
-  Frame(std::string n, Stack<Nonnull<Scope*>> s, Stack<Nonnull<Action*>> c)
-      : name(std::move(n)),
-        scopes(std::move(s)),
-        todo(std::move(c)),
-        continuation() {}
-
-  void Print(llvm::raw_ostream& out) const;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
-
-  // The name of the function.
-  std::string name;
-  // If the frame represents a function call, the bottom scope
-  // contains the parameter-argument bindings for this function
-  // call. The rest of the scopes contain local variables defined by
-  // blocks within the function. The scope at the top of the stack is
-  // the current scope and its environment is the one used for looking
-  // up the value associated with a variable.
-  Stack<Nonnull<Scope*>> scopes;
-  // The actions that need to be executed in the future of the
-  // current function call. The top of the stack is the action
-  // that is executed first.
-  Stack<Nonnull<Action*>> todo;
-  // If this frame is the bottom frame of a continuation, then it stores
-  // the address of the continuation.
-  std::optional<Address> continuation;
-};
-
-}  // namespace Carbon
-
-#endif  // EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_

+ 162 - 213
executable_semantics/interpreter/interpreter.cpp

@@ -17,13 +17,12 @@
 #include "executable_semantics/common/arena.h"
 #include "executable_semantics/common/error.h"
 #include "executable_semantics/interpreter/action.h"
-#include "executable_semantics/interpreter/frame.h"
 #include "executable_semantics/interpreter/stack.h"
-#include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Casting.h"
 
 using llvm::cast;
+using llvm::dyn_cast;
 
 namespace Carbon {
 
@@ -43,11 +42,17 @@ void Interpreter::PrintEnv(Env values, llvm::raw_ostream& out) {
 // State Operations
 //
 
-auto Interpreter::CurrentEnv() -> Env {
-  Nonnull<Frame*> frame = stack_.Top();
-  return frame->scopes.Top()->values;
+auto Interpreter::CurrentScope() -> Scope& {
+  for (Nonnull<Action*> action : todo_) {
+    if (action->scope().has_value()) {
+      return *action->scope();
+    }
+  }
+  FATAL() << "No current scope";
 }
 
+auto Interpreter::CurrentEnv() -> Env { return CurrentScope().values; }
+
 // Returns the given name from the environment, printing an error if not found.
 auto Interpreter::GetFromEnv(SourceLocation source_loc, const std::string& name)
     -> Address {
@@ -61,11 +66,11 @@ auto Interpreter::GetFromEnv(SourceLocation source_loc, const std::string& name)
 void Interpreter::PrintState(llvm::raw_ostream& out) {
   out << "{\nstack: ";
   llvm::ListSeparator sep(" :: ");
-  for (const auto& frame : stack_) {
-    out << sep << *frame;
+  for (Nonnull<const Action*> action : todo_) {
+    out << sep << *action;
   }
   out << "\nheap: " << heap_;
-  if (!stack_.IsEmpty() && !stack_.Top()->scopes.IsEmpty()) {
+  if (!todo_.IsEmpty()) {
     out << "\nvalues: ";
     PrintEnv(CurrentEnv(), out);
   }
@@ -176,19 +181,14 @@ void Interpreter::InitGlobals(llvm::ArrayRef<Nonnull<Declaration*>> fs) {
   }
 }
 
-void Interpreter::DeallocateScope(Nonnull<Scope*> scope) {
-  for (const auto& l : scope->locals) {
-    std::optional<Address> a = scope->values.Get(l);
+void Interpreter::DeallocateScope(Scope& scope) {
+  CHECK(!scope.deallocated);
+  for (const auto& l : scope.locals) {
+    std::optional<Address> a = scope.values.Get(l);
     CHECK(a);
     heap_.Deallocate(*a);
   }
-}
-
-void Interpreter::DeallocateLocals(Nonnull<Frame*> frame) {
-  while (!frame->scopes.IsEmpty()) {
-    DeallocateScope(frame->scopes.Top());
-    frame->scopes.Pop();
-  }
+  scope.deallocated = true;
 }
 
 auto Interpreter::CreateTuple(Nonnull<Action*> act,
@@ -374,7 +374,7 @@ void Interpreter::PatternAssignment(Nonnull<const Value*> pat,
 }
 
 auto Interpreter::StepLvalue() -> Transition {
-  Nonnull<Action*> act = stack_.Top()->todo.Top();
+  Nonnull<Action*> act = todo_.Top();
   const Expression& exp = cast<LValAction>(*act).expression();
   if (trace_) {
     llvm::outs() << "--- step lvalue " << exp << " (" << exp.source_loc()
@@ -524,7 +524,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
 }
 
 auto Interpreter::StepExp() -> Transition {
-  Nonnull<Action*> act = stack_.Top()->todo.Top();
+  Nonnull<Action*> act = todo_.Top();
   const Expression& exp = cast<ExpressionAction>(*act).expression();
   if (trace_) {
     llvm::outs() << "--- step exp " << exp << " (" << exp.source_loc()
@@ -659,6 +659,13 @@ auto Interpreter::StepExp() -> Transition {
             FATAL_RUNTIME_ERROR(exp.source_loc())
                 << "in call, expected a function, not " << *act->results()[0];
         }
+      } else if (act->pos() == 3) {
+        if (act->results().size() < 3) {
+          // Control fell through without explicit return.
+          return Done{TupleValue::Empty()};
+        } else {
+          return Done{act->results()[2]};
+        }
       } else {
         FATAL() << "in handle_value with Call pos " << act->pos();
       }
@@ -720,7 +727,7 @@ auto Interpreter::StepExp() -> Transition {
 }
 
 auto Interpreter::StepPattern() -> Transition {
-  Nonnull<Action*> act = stack_.Top()->todo.Top();
+  Nonnull<Action*> act = todo_.Top();
   const Pattern& pattern = cast<PatternAction>(*act).pattern();
   if (trace_) {
     llvm::outs() << "--- step pattern " << pattern << " ("
@@ -772,38 +779,13 @@ auto Interpreter::StepPattern() -> Transition {
   }
 }
 
-static auto IsWhileAct(Nonnull<Action*> act) -> bool {
-  switch (act->kind()) {
-    case Action::Kind::StatementAction:
-      switch (cast<StatementAction>(*act).statement().kind()) {
-        case Statement::Kind::While:
-          return true;
-        default:
-          return false;
-      }
-    default:
-      return false;
-  }
-}
-
-static auto HasLocalScope(Nonnull<Action*> act) -> bool {
-  switch (act->kind()) {
-    case Action::Kind::StatementAction:
-      switch (cast<StatementAction>(*act).statement().kind()) {
-        case Statement::Kind::Block:
-        case Statement::Kind::Match:
-          return true;
-        default:
-          return false;
-      }
-    default:
-      return false;
-  }
+static auto IsRunAction(Nonnull<Action*> action) -> bool {
+  const auto* statement = dyn_cast<StatementAction>(action);
+  return statement != nullptr && llvm::isa<Run>(statement->statement());
 }
 
 auto Interpreter::StepStmt() -> Transition {
-  Nonnull<Frame*> frame = stack_.Top();
-  Nonnull<Action*> act = frame->todo.Top();
+  Nonnull<Action*> act = todo_.Top();
   const Statement& stmt = cast<StatementAction>(*act).statement();
   if (trace_) {
     llvm::outs() << "--- step stmt ";
@@ -816,13 +798,11 @@ auto Interpreter::StepStmt() -> Transition {
       if (act->pos() == 0) {
         //    { { (match (e) ...) :: C, E, F} :: S, H}
         // -> { { e :: (match ([]) ...) :: C, E, F} :: S, H}
-        frame->scopes.Push(arena_->New<Scope>(CurrentEnv()));
+        act->StartScope(Scope(CurrentEnv()));
         return Spawn{arena_->New<ExpressionAction>(&match_stmt.expression())};
       } else {
         int clause_num = act->pos() - 1;
         if (clause_num >= static_cast<int>(match_stmt.clauses().size())) {
-          DeallocateScope(frame->scopes.Top());
-          frame->scopes.Pop();
           return Done{};
         }
         auto c = match_stmt.clauses()[clause_num];
@@ -835,8 +815,8 @@ auto Interpreter::StepStmt() -> Transition {
           act->set_pos(match_stmt.clauses().size() + 1);
 
           for (const auto& [name, value] : *matches) {
-            frame->scopes.Top()->values.Set(name, value);
-            frame->scopes.Top()->locals.push_back(name);
+            act->scope()->values.Set(name, value);
+            act->scope()->locals.push_back(name);
           }
           return Spawn{arena_->New<StatementAction>(&c.statement())};
         } else {
@@ -868,40 +848,24 @@ auto Interpreter::StepStmt() -> Transition {
       CHECK(act->pos() == 0);
       //    { { break; :: ... :: (while (e) s) :: C, E, F} :: S, H}
       // -> { { C, E', F} :: S, H}
-      auto it =
-          std::find_if(frame->todo.begin(), frame->todo.end(), &IsWhileAct);
-      if (it == frame->todo.end()) {
-        FATAL_RUNTIME_ERROR(stmt.source_loc())
-            << "`break` not inside `while` statement";
-      }
-      ++it;
-      return UnwindTo{*it};
+      return UnwindPast{&cast<Break>(stmt).loop()};
     }
     case Statement::Kind::Continue: {
       CHECK(act->pos() == 0);
       //    { { continue; :: ... :: (while (e) s) :: C, E, F} :: S, H}
       // -> { { (while (e) s) :: C, E', F} :: S, H}
-      auto it =
-          std::find_if(frame->todo.begin(), frame->todo.end(), &IsWhileAct);
-      if (it == frame->todo.end()) {
-        FATAL_RUNTIME_ERROR(stmt.source_loc())
-            << "`continue` not inside `while` statement";
-      }
-      return UnwindTo{*it};
+      return UnwindTo{&cast<Continue>(stmt).loop()};
     }
     case Statement::Kind::Block: {
       if (act->pos() == 0) {
-        const auto& block = cast<Block>(stmt);
+        const Block& block = cast<Block>(stmt);
         if (block.statement()) {
-          frame->scopes.Push(arena_->New<Scope>(CurrentEnv()));
+          act->StartScope(Scope(CurrentEnv()));
           return Spawn{arena_->New<StatementAction>(*block.statement())};
         } else {
           return Done{};
         }
       } else {
-        Nonnull<Scope*> scope = frame->scopes.Top();
-        DeallocateScope(scope);
-        frame->scopes.Pop(1);
         return Done{};
       }
     }
@@ -924,8 +888,9 @@ auto Interpreter::StepStmt() -> Transition {
             << stmt.source_loc()
             << ": internal error in variable definition, match failed";
         for (const auto& [name, value] : *matches) {
-          frame->scopes.Top()->values.Set(name, value);
-          frame->scopes.Top()->locals.push_back(name);
+          Scope& current_scope = CurrentScope();
+          current_scope.values.Set(name, value);
+          current_scope.locals.push_back(name);
         }
         return Done{};
       }
@@ -994,7 +959,8 @@ auto Interpreter::StepStmt() -> Transition {
         // -> { {v :: C', E', F'} :: S, H}
         // TODO(geoffromer): convert the result to the function's return type,
         // once #880 gives us a way to find that type.
-        return UnwindFunctionCall{act->results()[0]};
+        const FunctionDeclaration& function = cast<Return>(stmt).function();
+        return UnwindPast{*function.body(), act->results()[0]};
       }
     case Statement::Kind::Sequence: {
       //    { { (s1,s2) :: C, E, F} :: S, H}
@@ -1015,64 +981,50 @@ auto Interpreter::StepStmt() -> Transition {
       CHECK(act->pos() == 0);
       // Create a continuation object by creating a frame similar the
       // way one is created in a function call.
-      auto scopes = Stack<Nonnull<Scope*>>(arena_->New<Scope>(CurrentEnv()));
-      Stack<Nonnull<Action*>> todo;
-      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);
+      auto continuation_stack = arena_->New<std::vector<Nonnull<Action*>>>();
+      continuation_stack->push_back(
+          arena_->New<StatementAction>(&cast<Continuation>(stmt).body()));
+      continuation_stack->push_back(
+          arena_->New<ScopeAction>(Scope(CurrentEnv())));
       Address continuation_address = 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
-      frame->scopes.Top()->values.Set(
+      CurrentScope().values.Set(
           cast<Continuation>(stmt).continuation_variable(),
           continuation_address);
-      // Pop the continuation statement.
-      frame->todo.Pop();
-      return ManualTransition{};
+      return Done{};
     }
-    case Statement::Kind::Run:
+    case Statement::Kind::Run: {
+      auto& run = cast<Run>(stmt);
       if (act->pos() == 0) {
         // Evaluate the argument of the run statement.
-        return Spawn{
-            arena_->New<ExpressionAction>(&cast<Run>(stmt).argument())};
-      } else {
-        frame->todo.Pop(1);
-        // Push an expression statement action to ignore the result
-        // value from the continuation.
-        auto ignore_result =
-            arena_->New<StatementAction>(arena_->New<ExpressionStatement>(
-                stmt.source_loc(),
-                arena_->New<TupleLiteral>(stmt.source_loc())));
-        frame->todo.Push(ignore_result);
-        // Push the continuation onto the current stack_.
-        Nonnull<const Value*> arg =
-            Convert(act->results()[0], arena_->New<ContinuationType>());
-        std::vector<Nonnull<Frame*>>& continuation_vector =
-            cast<ContinuationValue>(*arg).stack();
+        return Spawn{arena_->New<ExpressionAction>(&run.argument())};
+      } else if (act->pos() == 1) {
+        // Push the continuation onto the current stack.
+        std::vector<Nonnull<Action*>>& continuation_vector =
+            cast<const ContinuationValue>(*act->results()[0]).stack();
         while (!continuation_vector.empty()) {
-          stack_.Push(continuation_vector.back());
+          todo_.Push(continuation_vector.back());
           continuation_vector.pop_back();
         }
+        act->set_pos(2);
         return ManualTransition{};
+      } else {
+        return Done{};
       }
+    }
     case Statement::Kind::Await:
       CHECK(act->pos() == 0);
       // Pause the current continuation
-      frame->todo.Pop();
-      std::vector<Nonnull<Frame*>> paused;
-      do {
-        paused.push_back(stack_.Pop());
-      } while (paused.back()->continuation == std::nullopt);
-      // Update the continuation with the paused stack_.
-      const auto& continuation = cast<ContinuationValue>(
-          *heap_.Read(*paused.back()->continuation, stmt.source_loc()));
+      todo_.Pop();
+      std::vector<Nonnull<Action*>> paused;
+      while (!IsRunAction(todo_.Top())) {
+        paused.push_back(todo_.Pop());
+      }
+      const auto& continuation =
+          cast<const ContinuationValue>(*todo_.Top()->results()[0]);
       CHECK(continuation.stack().empty());
+      // Update the continuation with the paused stack.
       continuation.stack() = std::move(paused);
       return ManualTransition{};
   }
@@ -1084,62 +1036,82 @@ class Interpreter::DoTransition {
   explicit DoTransition(Interpreter* interpreter) : interpreter(interpreter) {}
 
   void operator()(const Done& done) {
-    Nonnull<Frame*> frame = interpreter->stack_.Top();
-    if (frame->todo.Top()->kind() != Action::Kind::StatementAction) {
-      CHECK(done.result);
-      frame->todo.Pop();
-      if (frame->todo.IsEmpty()) {
-        interpreter->program_value_ = *done.result;
-      } else {
-        frame->todo.Top()->AddResult(*done.result);
-      }
-    } else {
-      CHECK(!done.result);
-      frame->todo.Pop();
+    Nonnull<Action*> act = interpreter->todo_.Pop();
+    if (act->scope().has_value()) {
+      interpreter->DeallocateScope(*act->scope());
+    }
+    switch (act->kind()) {
+      case Action::Kind::ExpressionAction:
+      case Action::Kind::LValAction:
+      case Action::Kind::PatternAction:
+        CHECK(done.result.has_value());
+        interpreter->todo_.Top()->AddResult(*done.result);
+        break;
+      case Action::Kind::StatementAction:
+        CHECK(!done.result.has_value());
+        break;
+      case Action::Kind::ScopeAction:
+        if (done.result.has_value()) {
+          interpreter->todo_.Top()->AddResult(*done.result);
+        }
+        break;
     }
   }
 
   void operator()(const Spawn& spawn) {
-    Nonnull<Frame*> frame = interpreter->stack_.Top();
-    Nonnull<Action*> action = frame->todo.Top();
+    Nonnull<Action*> action = interpreter->todo_.Top();
     action->set_pos(action->pos() + 1);
-    frame->todo.Push(spawn.child);
+    interpreter->todo_.Push(spawn.child);
   }
 
   void operator()(const Delegate& delegate) {
-    Nonnull<Frame*> frame = interpreter->stack_.Top();
-    frame->todo.Pop();
-    frame->todo.Push(delegate.delegate);
+    Nonnull<Action*> act = interpreter->todo_.Pop();
+    if (act->scope().has_value()) {
+      delegate.delegate->StartScope(*act->scope());
+    }
+    interpreter->todo_.Push(delegate.delegate);
   }
 
   void operator()(const RunAgain&) {
-    Nonnull<Action*> action = interpreter->stack_.Top()->todo.Top();
+    Nonnull<Action*> action = interpreter->todo_.Top();
     action->set_pos(action->pos() + 1);
   }
 
   void operator()(const UnwindTo& unwind_to) {
-    Nonnull<Frame*> frame = interpreter->stack_.Top();
-    while (frame->todo.Top() != unwind_to.new_top) {
-      if (HasLocalScope(frame->todo.Top())) {
-        interpreter->DeallocateScope(frame->scopes.Top());
-        frame->scopes.Pop();
+    while (true) {
+      if (const auto* statement_action =
+              dyn_cast<StatementAction>(interpreter->todo_.Top());
+          statement_action != nullptr &&
+          &statement_action->statement() == unwind_to.ast_node) {
+        break;
+      }
+      Nonnull<Action*> action = interpreter->todo_.Pop();
+      if (action->scope().has_value()) {
+        interpreter->DeallocateScope(*action->scope());
       }
-      frame->todo.Pop();
     }
   }
 
-  void operator()(const UnwindFunctionCall& unwind) {
-    interpreter->DeallocateLocals(interpreter->stack_.Top());
-    interpreter->stack_.Pop();
-    if (interpreter->stack_.Top()->todo.IsEmpty()) {
-      interpreter->program_value_ = unwind.return_val;
-    } else {
-      interpreter->stack_.Top()->todo.Top()->AddResult(unwind.return_val);
+  void operator()(const UnwindPast& unwind_past) {
+    while (true) {
+      Nonnull<Action*> action = interpreter->todo_.Pop();
+      if (action->scope().has_value()) {
+        interpreter->DeallocateScope(*action->scope());
+      }
+      if (const auto* statement_action = dyn_cast<StatementAction>(action);
+          statement_action != nullptr &&
+          &statement_action->statement() == unwind_past.ast_node) {
+        break;
+      }
+    }
+    if (unwind_past.result.has_value()) {
+      interpreter->todo_.Top()->AddResult(*unwind_past.result);
     }
   }
 
   void operator()(const CallFunction& call) {
-    interpreter->stack_.Top()->todo.Pop();
+    Nonnull<Action*> action = interpreter->todo_.Top();
+    action->set_pos(action->pos() + 1);
     Nonnull<const Value*> converted_args = interpreter->Convert(
         call.args, &call.function->param_pattern().static_type());
     std::optional<Env> matches =
@@ -1148,20 +1120,16 @@ class Interpreter::DoTransition {
     CHECK(matches.has_value())
         << "internal error in call_function, pattern match failed";
     // Create the new frame and push it on the stack
-    Env values = interpreter->globals_;
-    std::vector<std::string> params;
+    Scope new_scope(interpreter->globals_);
     for (const auto& [name, value] : *matches) {
-      values.Set(name, value);
-      params.push_back(name);
+      new_scope.values.Set(name, value);
+      new_scope.locals.push_back(name);
     }
-    auto scopes =
-        Stack<Nonnull<Scope*>>(interpreter->arena_->New<Scope>(values, params));
+    interpreter->todo_.Push(
+        interpreter->arena_->New<ScopeAction>(std::move(new_scope)));
     CHECK(call.function->body()) << "Calling a function that's missing a body";
-    auto todo = Stack<Nonnull<Action*>>(
+    interpreter->todo_.Push(
         interpreter->arena_->New<StatementAction>(*call.function->body()));
-    auto frame =
-        interpreter->arena_->New<Frame>(call.function->name(), scopes, todo);
-    interpreter->stack_.Push(frame);
   }
 
   void operator()(const ManualTransition&) {}
@@ -1172,14 +1140,7 @@ class Interpreter::DoTransition {
 
 // State transition.
 void Interpreter::Step() {
-  Nonnull<Frame*> frame = stack_.Top();
-  if (frame->todo.IsEmpty()) {
-    std::visit(DoTransition(this),
-               Transition{UnwindFunctionCall{TupleValue::Empty()}});
-    return;
-  }
-
-  Nonnull<Action*> act = frame->todo.Top();
+  Nonnull<Action*> act = todo_.Top();
   switch (act->kind()) {
     case Action::Kind::LValAction:
       std::visit(DoTransition(this), StepLvalue());
@@ -1193,75 +1154,63 @@ void Interpreter::Step() {
     case Action::Kind::StatementAction:
       std::visit(DoTransition(this), StepStmt());
       break;
+    case Action::Kind::ScopeAction:
+      if (act->results().empty()) {
+        std::visit(DoTransition(this), Transition{Done{}});
+      } else {
+        CHECK(act->results().size() == 1);
+        std::visit(DoTransition(this), Transition{Done{act->results()[0]}});
+      }
   }  // switch
 }
 
+auto Interpreter::ExecuteAction(Nonnull<Action*> action, Env values,
+                                bool trace_steps) -> Nonnull<const Value*> {
+  todo_ = {};
+  todo_.Push(arena_->New<ScopeAction>(Scope(values)));
+  todo_.Push(action);
+
+  while (todo_.Count() > 1) {
+    Step();
+    if (trace_steps) {
+      PrintState(llvm::outs());
+    }
+  }
+  CHECK(todo_.Top()->results().size() == 1);
+  return todo_.Top()->results()[0];
+}
+
 auto Interpreter::InterpProgram(llvm::ArrayRef<Nonnull<Declaration*>> fs,
                                 Nonnull<const Expression*> call_main) -> int {
   // Check that the interpreter is in a clean state.
   CHECK(globals_.IsEmpty());
-  CHECK(stack_.IsEmpty());
-  CHECK(program_value_ == std::nullopt);
+  CHECK(todo_.IsEmpty());
 
   if (trace_) {
     llvm::outs() << "********** initializing globals **********\n";
   }
   InitGlobals(fs);
 
-  auto todo = Stack<Nonnull<Action*>>(arena_->New<ExpressionAction>(call_main));
-  auto scopes = Stack<Nonnull<Scope*>>(arena_->New<Scope>(globals_));
-  stack_ = Stack<Nonnull<Frame*>>(arena_->New<Frame>("top", scopes, todo));
-
   if (trace_) {
     llvm::outs() << "********** calling main function **********\n";
     PrintState(llvm::outs());
   }
 
-  while (stack_.Count() > 1 || !stack_.Top()->todo.IsEmpty()) {
-    if (!stack_.Top()->todo.IsEmpty()) {
-      CHECK(stack_.Top()->todo.Top()->kind() != Action::Kind::PatternAction)
-          << "Pattern evaluation must happen before run-time.";
-    }
-    Step();
-    if (trace_) {
-      PrintState(llvm::outs());
-    }
-  }
-  return cast<IntValue>(**program_value_).value();
+  return cast<IntValue>(*ExecuteAction(arena_->New<ExpressionAction>(call_main),
+                                       globals_, trace_))
+      .value();
 }
 
 auto Interpreter::InterpExp(Env values, Nonnull<const Expression*> e)
     -> Nonnull<const Value*> {
-  CHECK(program_value_ == std::nullopt);
-  auto program_value_guard =
-      llvm::make_scope_exit([&] { program_value_ = std::nullopt; });
-  auto todo = Stack<Nonnull<Action*>>(arena_->New<ExpressionAction>(e));
-  auto scopes = Stack<Nonnull<Scope*>>(arena_->New<Scope>(values));
-  stack_ =
-      Stack<Nonnull<Frame*>>(arena_->New<Frame>("InterpExp", scopes, todo));
-
-  while (stack_.Count() > 1 || !stack_.Top()->todo.IsEmpty()) {
-    Step();
-  }
-  CHECK(program_value_ != std::nullopt);
-  return *program_value_;
+  return ExecuteAction(arena_->New<ExpressionAction>(e), values,
+                       /*trace_steps=*/false);
 }
 
 auto Interpreter::InterpPattern(Env values, Nonnull<const Pattern*> p)
     -> Nonnull<const Value*> {
-  CHECK(program_value_ == std::nullopt);
-  auto program_value_guard =
-      llvm::make_scope_exit([&] { program_value_ = std::nullopt; });
-  auto todo = Stack<Nonnull<Action*>>(arena_->New<PatternAction>(p));
-  auto scopes = Stack<Nonnull<Scope*>>(arena_->New<Scope>(values));
-  stack_ =
-      Stack<Nonnull<Frame*>>(arena_->New<Frame>("InterpPattern", scopes, todo));
-
-  while (stack_.Count() > 1 || !stack_.Top()->todo.IsEmpty()) {
-    Step();
-  }
-  CHECK(program_value_ != std::nullopt);
-  return *program_value_;
+  return ExecuteAction(arena_->New<PatternAction>(p), values,
+                       /*trace_steps=*/false);
 }
 
 }  // namespace Carbon

+ 26 - 17
executable_semantics/interpreter/interpreter.h

@@ -13,7 +13,7 @@
 #include "executable_semantics/ast/declaration.h"
 #include "executable_semantics/ast/expression.h"
 #include "executable_semantics/ast/pattern.h"
-#include "executable_semantics/interpreter/frame.h"
+#include "executable_semantics/interpreter/action.h"
 #include "executable_semantics/interpreter/heap.h"
 #include "executable_semantics/interpreter/stack.h"
 #include "executable_semantics/interpreter/value.h"
@@ -21,8 +21,6 @@
 
 namespace Carbon {
 
-using Env = Dictionary<std::string, Address>;
-
 class Interpreter {
  public:
   explicit Interpreter(Nonnull<Arena*> arena, bool trace)
@@ -85,16 +83,19 @@ class Interpreter {
   // and increments its position counter.
   struct RunAgain {};
 
-  // Transition type which unwinds the `todo` and `scopes` stacks until it
-  // reaches a specified Action lower in the stack.
+  // Transition type which unwinds the `todo` stack until it reaches the
+  // StatementAction associated with `ast_node`. Execution then resumes with
+  // that StatementAction.
   struct UnwindTo {
-    const Nonnull<Action*> new_top;
+    Nonnull<const Statement*> ast_node;
   };
 
-  // Transition type which unwinds the entire current stack frame, and returns
-  // a specified value to the caller.
-  struct UnwindFunctionCall {
-    Nonnull<const Value*> return_val;
+  // Transition type which unwinds the `todo` stack down to and including the
+  // StatementAction associated with `ast_node`. If `result` is set, it will be
+  // treated as the result of that StatementAction.
+  struct UnwindPast {
+    Nonnull<const Statement*> ast_node;
+    std::optional<Nonnull<const Value*>> result;
   };
 
   // Transition type which removes the current action from the top of the todo
@@ -112,12 +113,12 @@ class Interpreter {
   // uses of this type should be replaced with meaningful transitions.
   struct ManualTransition {};
 
-  using Transition =
-      std::variant<Done, Spawn, Delegate, RunAgain, UnwindTo,
-                   UnwindFunctionCall, CallFunction, ManualTransition>;
+  using Transition = std::variant<Done, Spawn, Delegate, RunAgain, UnwindTo,
+                                  UnwindPast, CallFunction, ManualTransition>;
 
   // Visitor which implements the behavior associated with each transition type.
   class DoTransition;
+  friend class DoTransition;
 
   void Step();
 
@@ -131,12 +132,12 @@ class Interpreter {
   auto StepStmt() -> Transition;
 
   void InitGlobals(llvm::ArrayRef<Nonnull<Declaration*>> fs);
+  auto CurrentScope() -> Scope&;
   auto CurrentEnv() -> Env;
   auto GetFromEnv(SourceLocation source_loc, const std::string& name)
       -> Address;
 
-  void DeallocateScope(Nonnull<Scope*> scope);
-  void DeallocateLocals(Nonnull<Frame*> frame);
+  void DeallocateScope(Scope& scope);
 
   auto CreateTuple(Nonnull<Action*> act, Nonnull<const Expression*> exp)
       -> Nonnull<const Value*>;
@@ -157,14 +158,22 @@ class Interpreter {
 
   void PrintState(llvm::raw_ostream& out);
 
+  // Runs `action` in a scope consisting of `values`, and returns the result.
+  // `action` must produce a result. In other words, it must not be a
+  // StatementAction or ScopeAction.
+  //
+  // TODO: consider whether to use this->trace_ rather than a separate
+  // trace_steps parameter.
+  auto ExecuteAction(Nonnull<Action*> action, Env values, bool trace_steps)
+      -> Nonnull<const Value*>;
+
   Nonnull<Arena*> arena_;
 
   // Globally-defined entities, such as functions, structs, or choices.
   Env globals_;
 
-  Stack<Nonnull<Frame*>> stack_;
+  Stack<Nonnull<Action*>> todo_;
   Heap heap_;
-  std::optional<Nonnull<const Value*>> program_value_;
 
   bool trace_;
 };

+ 104 - 0
executable_semantics/interpreter/resolve_control_flow.cpp

@@ -0,0 +1,104 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "executable_semantics/interpreter/resolve_control_flow.h"
+
+#include "executable_semantics/ast/declaration.h"
+#include "executable_semantics/ast/statement.h"
+#include "executable_semantics/common/error.h"
+#include "llvm/Support/Casting.h"
+
+using llvm::cast;
+
+namespace Carbon {
+
+// Resolves control-flow edges in the AST rooted at `statement`. `return`
+// statements will resolve to `*function`, and `break` and `continue`
+// statements will resolve to `*loop`. If either parameter is nullopt, that
+// indicates a context where the corresponding statements are not permitted.
+static void ResolveControlFlow(
+    Nonnull<Statement*> statement,
+    std::optional<Nonnull<const FunctionDeclaration*>> function,
+    std::optional<Nonnull<const Statement*>> loop) {
+  switch (statement->kind()) {
+    case Statement::Kind::Return:
+      if (!function.has_value()) {
+        FATAL_COMPILATION_ERROR(statement->source_loc())
+            << "return is not within a function body";
+      }
+      cast<Return>(*statement).set_function(*function);
+      return;
+    case Statement::Kind::Break:
+      if (!loop.has_value()) {
+        FATAL_COMPILATION_ERROR(statement->source_loc())
+            << "break is not within a loop body";
+      }
+      cast<Break>(*statement).set_loop(*loop);
+      return;
+    case Statement::Kind::Continue:
+      if (!loop.has_value()) {
+        FATAL_COMPILATION_ERROR(statement->source_loc())
+            << "continue is not within a loop body";
+      }
+      cast<Continue>(*statement).set_loop(*loop);
+      return;
+    case Statement::Kind::If: {
+      auto& if_stmt = cast<If>(*statement);
+      ResolveControlFlow(&if_stmt.then_statement(), function, loop);
+      if (if_stmt.else_statement().has_value()) {
+        ResolveControlFlow(*if_stmt.else_statement(), function, loop);
+      }
+      return;
+    }
+    case Statement::Kind::Sequence: {
+      auto& seq = cast<Sequence>(*statement);
+      ResolveControlFlow(&seq.statement(), function, loop);
+      if (seq.next().has_value()) {
+        ResolveControlFlow(*seq.next(), function, loop);
+      }
+      return;
+    }
+    case Statement::Kind::Block: {
+      auto& block = cast<Block>(*statement);
+      if (block.statement().has_value()) {
+        ResolveControlFlow(*block.statement(), function, loop);
+      }
+      return;
+    }
+    case Statement::Kind::While:
+      ResolveControlFlow(&cast<While>(*statement).body(), function, statement);
+      return;
+    case Statement::Kind::Match: {
+      auto& match = cast<Match>(*statement);
+      for (Match::Clause& clause : match.clauses()) {
+        ResolveControlFlow(&clause.statement(), function, loop);
+      }
+      return;
+    }
+    case Statement::Kind::Continuation:
+      ResolveControlFlow(&cast<Continuation>(*statement).body(), std::nullopt,
+                         std::nullopt);
+      return;
+    case Statement::Kind::ExpressionStatement:
+    case Statement::Kind::Assign:
+    case Statement::Kind::VariableDefinition:
+    case Statement::Kind::Run:
+    case Statement::Kind::Await:
+      return;
+  }
+}
+
+void ResolveControlFlow(AST& ast) {
+  for (auto declaration : ast.declarations) {
+    if (declaration->kind() != Declaration::Kind::FunctionDeclaration) {
+      continue;
+    }
+    auto& function = cast<FunctionDeclaration>(*declaration);
+    if (function.body().has_value()) {
+      ResolveControlFlow(*function.body(), &function, std::nullopt);
+    }
+  }
+}
+
+}  // namespace Carbon

+ 19 - 0
executable_semantics/interpreter/resolve_control_flow.h

@@ -0,0 +1,19 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_RESOLVE_CONTROL_FLOW_H_
+#define EXECUTABLE_SEMANTICS_INTERPRETER_RESOLVE_CONTROL_FLOW_H_
+
+#include "executable_semantics/ast/ast.h"
+#include "executable_semantics/common/nonnull.h"
+
+namespace Carbon {
+
+// Resolves non-local control-flow edges, such as `break` and `return`, in the
+// given AST.
+void ResolveControlFlow(AST& ast);
+
+}  // namespace Carbon
+
+#endif  // EXECUTABLE_SEMANTICS_INTERPRETER_RESOLVE_CONTROL_FLOW_H_

+ 4 - 3
executable_semantics/interpreter/value.cpp

@@ -9,7 +9,7 @@
 #include "common/check.h"
 #include "executable_semantics/common/arena.h"
 #include "executable_semantics/common/error.h"
-#include "executable_semantics/interpreter/frame.h"
+#include "executable_semantics/interpreter/action.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Casting.h"
 
@@ -274,8 +274,9 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::ContinuationValue: {
       out << "{";
       llvm::ListSeparator sep(" :: ");
-      for (Nonnull<Frame*> frame : cast<ContinuationValue>(*this).stack()) {
-        out << sep << *frame;
+      for (Nonnull<const Action*> action :
+           cast<ContinuationValue>(*this).stack()) {
+        out << sep << *action;
       }
       out << "}";
       break;

+ 7 - 7
executable_semantics/interpreter/value.h

@@ -21,6 +21,8 @@
 
 namespace Carbon {
 
+class Action;
+
 // Abstract base class of all AST nodes representing values.
 //
 // Value and its derived classes support LLVM-style RTTI, including
@@ -106,8 +108,6 @@ struct StructElement {
   Nonnull<const Value*> value;
 };
 
-struct Frame;  // Used by continuation.
-
 // An integer value.
 class IntValue : public Value {
  public:
@@ -489,21 +489,21 @@ class VariableType : public Value {
 // fragment, which is exposed by `Stack()`.
 class ContinuationValue : public Value {
  public:
-  explicit ContinuationValue(Nonnull<std::vector<Nonnull<Frame*>>*> stack)
+  explicit ContinuationValue(Nonnull<std::vector<Nonnull<Action*>>*> stack)
       : Value(Kind::ContinuationValue), stack_(stack) {}
 
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::ContinuationValue;
   }
 
-  // The call stack of the suspended continuation, starting with the top
-  // frame (the reverse of the usual order). Note that this provides mutable
+  // The todo stack of the suspended continuation, starting with the top
+  // Action (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 -> std::vector<Nonnull<Frame*>>& { return *stack_; }
+  auto stack() const -> std::vector<Nonnull<Action*>>& { return *stack_; }
 
  private:
-  Nonnull<std::vector<Nonnull<Frame*>>*> stack_;
+  Nonnull<std::vector<Nonnull<Action*>>*> stack_;
 };
 
 // The String type.