mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
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:
committed by
GitHub
co-authored by
Jon Meow
parent
6a4901a995
commit
1723b4e0b2
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user