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

Hide `Interpreter` in .cpp (#1036)

This improves encapsulation, and makes Interpreter lifetimes clearer (and shorter).

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
Geoff Romer 4 лет назад
Родитель
Сommit
1723b4e0b2

+ 1 - 0
.codespell_ignore

@@ -9,3 +9,4 @@ crate
 inout
 pullrequest
 statics
+compiletime

+ 6 - 9
executable_semantics/interpreter/action_stack.h

@@ -18,23 +18,20 @@ namespace Carbon {
 // The stack of Actions currently being executed by the interpreter.
 class ActionStack {
  public:
-  // Constructs an empty ActionStack
+  // Constructs an empty compile-time ActionStack.
   ActionStack() = default;
 
+  // Constructs an empty run-time ActionStack that allocates global variables
+  // on `heap`.
+  explicit ActionStack(Nonnull<HeapAllocationInterface*> heap)
+      : globals_(RuntimeScope(heap)) {}
+
   void Print(llvm::raw_ostream& out) const;
   LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
   // TODO: consider unifying with Print.
   void PrintScopes(llvm::raw_ostream& out) const;
 
-  // Sets the heap that variables will be allocated on. Cannot be called at
-  // run time, or when IsEmpty() is false, and marks the start of run time.
-  void SetHeap(Nonnull<HeapAllocationInterface*> heap) {
-    CHECK(todo_.IsEmpty());
-    CHECK(!globals_.has_value());
-    globals_ = RuntimeScope(heap);
-  }
-
   // Starts execution with `action` at the top of the stack. Cannot be called
   // when IsEmpty() is false.
   void Start(std::unique_ptr<Action> action);

+ 1 - 1
executable_semantics/interpreter/exec_program.cpp

@@ -41,7 +41,7 @@ void ExecProgram(Nonnull<Arena*> arena, AST ast, bool trace) {
     }
     llvm::outs() << "********** starting execution **********\n";
   }
-  int result = Interpreter(arena, trace).InterpProgram(ast);
+  int result = InterpProgram(ast, arena, trace);
   llvm::outs() << "result: " << result << "\n";
 }
 

+ 109 - 34
executable_semantics/interpreter/interpreter.cpp

@@ -27,6 +27,91 @@ using llvm::isa;
 
 namespace Carbon {
 
+// Selects between compile-time and run-time behavior.
+enum class Phase { CompileTime, RunTime };
+
+// Constructs an ActionStack suitable for the specified phase.
+static auto MakeTodo(Phase phase, Nonnull<Heap*> heap) -> ActionStack {
+  switch (phase) {
+    case Phase::CompileTime:
+      return ActionStack();
+    case Phase::RunTime:
+      return ActionStack(heap);
+  }
+}
+
+// An Interpreter represents an instance of the Carbon abstract machine. It
+// manages the state of the abstract machine, and executes the steps of Actions
+// passed to it.
+class Interpreter {
+ public:
+  // Constructs an Interpreter which allocates values on `arena`, and prints
+  // traces if `trace` is true. `phase` indicates whether it executes at
+  // compile time or run time.
+  Interpreter(Phase phase, Nonnull<Arena*> arena, bool trace)
+      : arena_(arena),
+        heap_(arena),
+        todo_(MakeTodo(phase, &heap_)),
+        trace_(trace) {}
+
+  ~Interpreter();
+
+  // Runs all the steps of `action`.
+  void RunAllSteps(std::unique_ptr<Action> action);
+
+  // The result produced by the `action` argument of the most recent
+  // RunAllSteps call. Cannot be called if `action` was an action that doesn't
+  // produce results.
+  auto result() const -> Nonnull<const Value*> { return todo_.result(); }
+
+ private:
+  void Step();
+
+  // State transitions for expressions.
+  void StepExp();
+  // State transitions for lvalues.
+  void StepLvalue();
+  // State transitions for patterns.
+  void StepPattern();
+  // State transition for statements.
+  void StepStmt();
+  // State transition for declarations.
+  void StepDeclaration();
+
+  auto CreateStruct(const std::vector<FieldInitializer>& fields,
+                    const std::vector<Nonnull<const Value*>>& values)
+      -> Nonnull<const Value*>;
+
+  auto EvalPrim(Operator op, const std::vector<Nonnull<const Value*>>& args,
+                SourceLocation source_loc) -> Nonnull<const Value*>;
+
+  // Returns the result of converting `value` to type `destination_type`.
+  auto Convert(Nonnull<const Value*> value,
+               Nonnull<const Value*> destination_type) const
+      -> Nonnull<const Value*>;
+
+  void PrintState(llvm::raw_ostream& out);
+
+  Nonnull<Arena*> arena_;
+
+  Heap heap_;
+  ActionStack todo_;
+
+  // The underlying states of continuation values. All StackFragments created
+  // during execution are tracked here, in order to safely deallocate the
+  // contents of any non-completed continuations at the end of execution.
+  std::vector<Nonnull<ContinuationValue::StackFragment*>> stack_fragments_;
+
+  bool trace_;
+};
+
+Interpreter::~Interpreter() {
+  // Clean up any remaining suspended continuations.
+  for (Nonnull<ContinuationValue::StackFragment*> fragment : stack_fragments_) {
+    fragment->Clear();
+  }
+}
+
 //
 // State Operations
 //
@@ -85,10 +170,9 @@ auto Interpreter::CreateStruct(const std::vector<FieldInitializer>& fields,
   return arena_->New<StructValue>(std::move(elements));
 }
 
-auto Interpreter::PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
-                               SourceLocation source_loc,
-                               std::optional<Nonnull<RuntimeScope*>> bindings)
-    -> bool {
+auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
+                  SourceLocation source_loc,
+                  std::optional<Nonnull<RuntimeScope*>> bindings) -> bool {
   switch (p->kind()) {
     case Value::Kind::BindingPlaceholderValue: {
       if (!bindings.has_value()) {
@@ -864,59 +948,50 @@ void Interpreter::Step() {
   }  // switch
 }
 
-void Interpreter::RunAllSteps(bool trace_steps) {
+void Interpreter::RunAllSteps(std::unique_ptr<Action> action) {
+  if (trace_) {
+    PrintState(llvm::outs());
+  }
+  todo_.Start(std::move(action));
   while (!todo_.IsEmpty()) {
     Step();
-    if (trace_steps) {
+    if (trace_) {
       PrintState(llvm::outs());
     }
   }
 }
 
-auto Interpreter::InterpProgram(const AST& ast) -> int {
-  todo_.SetHeap(&heap_);
-
-  if (trace_) {
+auto InterpProgram(const AST& ast, Nonnull<Arena*> arena, bool trace) -> int {
+  Interpreter interpreter(Phase::RunTime, arena, trace);
+  if (trace) {
     llvm::outs() << "********** initializing globals **********\n";
   }
 
   for (Nonnull<Declaration*> declaration : ast.declarations) {
-    todo_.Start(std::make_unique<DeclarationAction>(declaration));
-    RunAllSteps(trace_);
+    interpreter.RunAllSteps(std::make_unique<DeclarationAction>(declaration));
   }
 
-  if (trace_) {
+  if (trace) {
     llvm::outs() << "********** calling main function **********\n";
-    PrintState(llvm::outs());
   }
 
-  todo_.Start(std::make_unique<ExpressionAction>(*ast.main_call));
-  RunAllSteps(trace_);
+  interpreter.RunAllSteps(std::make_unique<ExpressionAction>(*ast.main_call));
 
-  // Clean up any remaining suspended continuations.
-  for (Nonnull<ContinuationValue::StackFragment*> fragment : stack_fragments_) {
-    fragment->Clear();
-  }
-
-  return cast<IntValue>(*todo_.result()).value();
-}
-
-auto Interpreter::RunCompileTimeAction(std::unique_ptr<Action> action)
-    -> Nonnull<const Value*> {
-  todo_.Start(std::move(action));
-  RunAllSteps(/*trace_steps=*/false);
-  CHECK(stack_fragments_.empty());
-  return todo_.result();
+  return cast<IntValue>(*interpreter.result()).value();
 }
 
-auto Interpreter::InterpExp(Nonnull<const Expression*> e)
+auto InterpExp(Nonnull<const Expression*> e, Nonnull<Arena*> arena, bool trace)
     -> Nonnull<const Value*> {
-  return RunCompileTimeAction(std::make_unique<ExpressionAction>(e));
+  Interpreter interpreter(Phase::CompileTime, arena, trace);
+  interpreter.RunAllSteps(std::make_unique<ExpressionAction>(e));
+  return interpreter.result();
 }
 
-auto Interpreter::InterpPattern(Nonnull<const Pattern*> p)
+auto InterpPattern(Nonnull<const Pattern*> p, Nonnull<Arena*> arena, bool trace)
     -> Nonnull<const Value*> {
-  return RunCompileTimeAction(std::make_unique<PatternAction>(p));
+  Interpreter interpreter(Phase::CompileTime, arena, trace);
+  interpreter.RunAllSteps(std::make_unique<PatternAction>(p));
+  return interpreter.result();
 }
 
 }  // namespace Carbon

+ 26 - 81
executable_semantics/interpreter/interpreter.h

@@ -22,87 +22,32 @@
 
 namespace Carbon {
 
-class Interpreter {
- public:
-  explicit Interpreter(Nonnull<Arena*> arena, bool trace)
-      : arena_(arena), heap_(arena), trace_(trace) {}
-
-  // Interpret the whole program.
-  auto InterpProgram(const AST& ast) -> int;
-
-  // Interpret an expression at compile-time.
-  auto InterpExp(Nonnull<const Expression*> e) -> Nonnull<const Value*>;
-
-  // Interpret a pattern at compile-time.
-  auto InterpPattern(Nonnull<const Pattern*> p) -> Nonnull<const Value*>;
-
-  // Attempts to match `v` against the pattern `p`, returning whether matching
-  // is successful. If it is, populates **bindings with the variables bound by
-  // the match; `bindings` should only be nullopt in contexts where `p`
-  // is not permitted to bind variables. **bindings may be modified even if the
-  // match is unsuccessful, so it should typically be created for the
-  // PatternMatch call and then merged into an existing scope on success.
-  [[nodiscard]] auto PatternMatch(
-      Nonnull<const Value*> p, Nonnull<const Value*> v,
-      SourceLocation source_loc, std::optional<Nonnull<RuntimeScope*>> bindings)
-      -> bool;
-
-  // Support TypeChecker allocating values on the heap.
-  auto AllocateValue(Nonnull<const Value*> v) -> AllocationId {
-    return heap_.AllocateValue(v);
-  }
-
- private:
-  void Step();
-
-  // State transitions for expressions.
-  void StepExp();
-  // State transitions for lvalues.
-  void StepLvalue();
-  // State transitions for patterns.
-  void StepPattern();
-  // State transition for statements.
-  void StepStmt();
-  // State transition for declarations.
-  void StepDeclaration();
-
-  // Calls Step() repeatedly until there are no steps left to execute. Produces
-  // trace output if trace_steps is true.
-  void RunAllSteps(bool trace_steps);
-
-  auto CreateStruct(const std::vector<FieldInitializer>& fields,
-                    const std::vector<Nonnull<const Value*>>& values)
-      -> Nonnull<const Value*>;
-
-  auto EvalPrim(Operator op, const std::vector<Nonnull<const Value*>>& args,
-                SourceLocation source_loc) -> Nonnull<const Value*>;
-
-  // Returns the result of converting `value` to type `destination_type`.
-  auto Convert(Nonnull<const Value*> value,
-               Nonnull<const Value*> destination_type) const
-      -> Nonnull<const Value*>;
-
-  void PrintState(llvm::raw_ostream& out);
-
-  // Runs `action` in an environment where the given constants are defined, and
-  // returns the result. `action` must produce a result. In other words, it must
-  // not be a StatementAction, ScopeAction, or DeclarationAction. Can only be
-  // called at compile time (before InterpProgram), and while `todo_` is empty.
-  auto RunCompileTimeAction(std::unique_ptr<Action> action)
-      -> Nonnull<const Value*>;
-
-  Nonnull<Arena*> arena_;
-
-  Heap heap_;
-  ActionStack todo_;
-
-  // The underlying states of continuation values. All StackFragments created
-  // during execution are tracked here, in order to safely deallocate the
-  // contents of any non-completed continuations at the end of execution.
-  std::vector<Nonnull<ContinuationValue::StackFragment*>> stack_fragments_;
-
-  bool trace_;
-};
+// Interprets the program defined by `ast`, allocating values on `arena` and
+// printing traces if `trace` is true.
+auto InterpProgram(const AST& ast, Nonnull<Arena*> arena, bool trace) -> int;
+
+// Interprets `e` at compile-time, allocating values on `arena` and
+// printing traces if `trace` is true.
+auto InterpExp(Nonnull<const Expression*> e, Nonnull<Arena*> arena, bool trace)
+    -> Nonnull<const Value*>;
+
+// Interprets `p` at compile-time, allocating values on `arena` and
+// printing traces if `trace` is true.
+auto InterpPattern(Nonnull<const Pattern*> p, Nonnull<Arena*> arena, bool trace)
+    -> Nonnull<const Value*>;
+
+// Attempts to match `v` against the pattern `p`, returning whether matching
+// is successful. If it is, populates **bindings with the variables bound by
+// the match; `bindings` should only be nullopt in contexts where `p`
+// is not permitted to bind variables. **bindings may be modified even if the
+// match is unsuccessful, so it should typically be created for the
+// PatternMatch call and then merged into an existing scope on success.
+// TODO: consider moving this to a separate header.
+[[nodiscard]] auto PatternMatch(Nonnull<const Value*> p,
+                                Nonnull<const Value*> v,
+                                SourceLocation source_loc,
+                                std::optional<Nonnull<RuntimeScope*>> bindings)
+    -> bool;
 
 }  // namespace Carbon
 

+ 17 - 16
executable_semantics/interpreter/type_checker.cpp

@@ -404,8 +404,8 @@ void TypeChecker::TypeCheckExp(Nonnull<Expression*> e) {
       switch (aggregate_type.kind()) {
         case Value::Kind::TupleValue: {
           const auto& tuple_type = cast<TupleValue>(aggregate_type);
-          int i =
-              cast<IntValue>(*interpreter_.InterpExp(&index.offset())).value();
+          int i = cast<IntValue>(*InterpExp(&index.offset(), arena_, trace_))
+                      .value();
           if (i < 0 || i >= static_cast<int>(tuple_type.elements().size())) {
             FATAL_COMPILATION_ERROR(e->source_loc())
                 << "index " << i << " is out of range for type " << tuple_type;
@@ -443,7 +443,7 @@ void TypeChecker::TypeCheckExp(Nonnull<Expression*> e) {
       for (auto& arg : struct_type.fields()) {
         TypeCheckExp(&arg.expression());
         ExpectIsConcreteType(arg.expression().source_loc(),
-                             interpreter_.InterpExp(&arg.expression()));
+                             InterpExp(&arg.expression(), arena_, trace_));
       }
       if (struct_type.fields().empty()) {
         // `{}` is the type of `{}`, just as `()` is the type of `()`.
@@ -667,9 +667,9 @@ void TypeChecker::TypeCheckExp(Nonnull<Expression*> e) {
     case ExpressionKind::FunctionTypeLiteral: {
       auto& fn = cast<FunctionTypeLiteral>(*e);
       ExpectIsConcreteType(fn.parameter().source_loc(),
-                           interpreter_.InterpExp(&fn.parameter()));
+                           InterpExp(&fn.parameter(), arena_, trace_));
       ExpectIsConcreteType(fn.return_type().source_loc(),
-                           interpreter_.InterpExp(&fn.return_type()));
+                           InterpExp(&fn.return_type(), arena_, trace_));
       SetStaticType(&fn, arena_->New<TypeType>());
       fn.set_value_category(ValueCategory::Let);
       return;
@@ -727,13 +727,14 @@ void TypeChecker::TypeCheckPattern(
     case PatternKind::BindingPattern: {
       auto& binding = cast<BindingPattern>(*p);
       TypeCheckPattern(&binding.type(), std::nullopt);
-      Nonnull<const Value*> type = interpreter_.InterpPattern(&binding.type());
+      Nonnull<const Value*> type =
+          InterpPattern(&binding.type(), arena_, trace_);
       if (expected) {
         if (IsConcreteType(type)) {
           ExpectType(p->source_loc(), "name binding", type, *expected);
         } else {
-          if (!interpreter_.PatternMatch(
-                  type, *expected, binding.type().source_loc(), std::nullopt)) {
+          if (!PatternMatch(type, *expected, binding.type().source_loc(),
+                            std::nullopt)) {
             FATAL_COMPILATION_ERROR(binding.type().source_loc())
                 << "Type pattern '" << *type << "' does not match actual type '"
                 << **expected << "'";
@@ -743,7 +744,7 @@ void TypeChecker::TypeCheckPattern(
       }
       ExpectIsConcreteType(binding.source_loc(), type);
       SetStaticType(&binding, type);
-      SetValue(&binding, interpreter_.InterpPattern(&binding));
+      SetValue(&binding, InterpPattern(&binding, arena_, trace_));
       return;
     }
     case PatternKind::TuplePattern: {
@@ -767,7 +768,7 @@ void TypeChecker::TypeCheckPattern(
         field_types.push_back(&field->static_type());
       }
       SetStaticType(&tuple, arena_->New<TupleValue>(std::move(field_types)));
-      SetValue(&tuple, interpreter_.InterpPattern(&tuple));
+      SetValue(&tuple, InterpPattern(&tuple, arena_, trace_));
       return;
     }
     case PatternKind::AlternativePattern: {
@@ -795,14 +796,14 @@ void TypeChecker::TypeCheckPattern(
       }
       TypeCheckPattern(&alternative.arguments(), *parameter_types);
       SetStaticType(&alternative, &choice_type);
-      SetValue(&alternative, interpreter_.InterpPattern(&alternative));
+      SetValue(&alternative, InterpPattern(&alternative, arena_, trace_));
       return;
     }
     case PatternKind::ExpressionPattern: {
       auto& expression = cast<ExpressionPattern>(*p).expression();
       TypeCheckExp(&expression);
       SetStaticType(p, &expression.static_type());
-      SetValue(p, interpreter_.InterpPattern(p));
+      SetValue(p, InterpPattern(p, arena_, trace_));
       return;
     }
   }
@@ -1000,7 +1001,7 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
     // new types into scope.
     TypeCheckExp(*return_expression);
     SetStaticType(&f->return_term(),
-                  interpreter_.InterpExp(*return_expression));
+                  InterpExp(*return_expression, arena_, trace_));
   } else if (f->return_term().is_omitted()) {
     SetStaticType(&f->return_term(), TupleValue::Empty());
   } else {
@@ -1065,7 +1066,7 @@ void TypeChecker::TypeCheckChoiceDeclaration(
   std::vector<NamedValue> alternatives;
   for (Nonnull<AlternativeSignature*> alternative : choice->alternatives()) {
     TypeCheckExp(&alternative->signature());
-    auto signature = interpreter_.InterpExp(&alternative->signature());
+    auto signature = InterpExp(&alternative->signature(), arena_, trace_);
     alternatives.push_back({.name = alternative->name(), .value = signature});
   }
   auto ct = arena_->New<ChoiceType>(choice->name(), std::move(alternatives));
@@ -1108,7 +1109,7 @@ void TypeChecker::TypeCheckDeclaration(Nonnull<Declaration*> d) {
             << "Type of a top-level variable must be an expression.";
       }
       Nonnull<const Value*> declared_type =
-          interpreter_.InterpExp(&binding_type->expression());
+          InterpExp(&binding_type->expression(), arena_, trace_);
       SetStaticType(&var, declared_type);
       ExpectType(var.source_loc(), "initializer of variable", declared_type,
                  &var.initializer().static_type());
@@ -1149,7 +1150,7 @@ void TypeChecker::TopLevel(Nonnull<Declaration*> d) {
       Expression& type =
           cast<ExpressionPattern>(var.binding().type()).expression();
       TypeCheckPattern(&var.binding(), std::nullopt);
-      Nonnull<const Value*> declared_type = interpreter_.InterpExp(&type);
+      Nonnull<const Value*> declared_type = InterpExp(&type, arena_, trace_);
       SetStaticType(&var, declared_type);
       break;
     }

+ 1 - 2
executable_semantics/interpreter/type_checker.h

@@ -20,7 +20,7 @@ namespace Carbon {
 class TypeChecker {
  public:
   explicit TypeChecker(Nonnull<Arena*> arena, bool trace)
-      : arena_(arena), interpreter_(arena, trace), trace_(trace) {}
+      : arena_(arena), trace_(trace) {}
 
   void TypeCheck(AST& ast);
 
@@ -96,7 +96,6 @@ class TypeChecker {
   void PrintConstants(llvm::raw_ostream& out);
 
   Nonnull<Arena*> arena_;
-  Interpreter interpreter_;
   std::set<NamedEntityView> constants_;
 
   bool trace_;