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>
This commit is contained in:
Geoff Romer
2022-01-26 09:57:55 -08:00
committed by GitHub
co-authored by Jon Meow
parent 6a4901a995
commit 1723b4e0b2
7 changed files with 158 additions and 140 deletions
@@ -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);
@@ -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
View File
@@ -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();
return cast<IntValue>(*interpreter.result()).value();
}
auto Interpreter::RunCompileTimeAction(std::unique_ptr<Action> action)
auto InterpExp(Nonnull<const Expression*> e, Nonnull<Arena*> arena, bool trace)
-> Nonnull<const Value*> {
todo_.Start(std::move(action));
RunAllSteps(/*trace_steps=*/false);
CHECK(stack_fragments_.empty());
return todo_.result();
Interpreter interpreter(Phase::CompileTime, arena, trace);
interpreter.RunAllSteps(std::make_unique<ExpressionAction>(e));
return interpreter.result();
}
auto Interpreter::InterpExp(Nonnull<const Expression*> e)
auto InterpPattern(Nonnull<const Pattern*> p, Nonnull<Arena*> arena, bool trace)
-> Nonnull<const Value*> {
return RunCompileTimeAction(std::make_unique<ExpressionAction>(e));
}
auto Interpreter::InterpPattern(Nonnull<const Pattern*> p)
-> 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
+23 -78
View File
@@ -22,87 +22,32 @@
namespace Carbon {
class Interpreter {
public:
explicit Interpreter(Nonnull<Arena*> arena, bool trace)
: arena_(arena), heap_(arena), trace_(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;
// Interpret the whole program.
auto InterpProgram(const AST& ast) -> 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*>;
// Interpret an expression at compile-time.
auto InterpExp(Nonnull<const Expression*> e) -> 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*>;
// 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_;
};
// 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
@@ -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;
}
@@ -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_;