Skip to content

Commit 1723b4e

Browse files
geoffromerjonmeow
andauthored
Hide Interpreter in .cpp (carbon-language#1036)
This improves encapsulation, and makes Interpreter lifetimes clearer (and shorter). Co-authored-by: Jon Meow <[email protected]>
1 parent 6a4901a commit 1723b4e

File tree

7 files changed

+161
-143
lines changed

7 files changed

+161
-143
lines changed

.codespell_ignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ crate
99
inout
1010
pullrequest
1111
statics
12+
compiletime

executable_semantics/interpreter/action_stack.h

+6-9
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,20 @@ namespace Carbon {
1818
// The stack of Actions currently being executed by the interpreter.
1919
class ActionStack {
2020
public:
21-
// Constructs an empty ActionStack
21+
// Constructs an empty compile-time ActionStack.
2222
ActionStack() = default;
2323

24+
// Constructs an empty run-time ActionStack that allocates global variables
25+
// on `heap`.
26+
explicit ActionStack(Nonnull<HeapAllocationInterface*> heap)
27+
: globals_(RuntimeScope(heap)) {}
28+
2429
void Print(llvm::raw_ostream& out) const;
2530
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
2631

2732
// TODO: consider unifying with Print.
2833
void PrintScopes(llvm::raw_ostream& out) const;
2934

30-
// Sets the heap that variables will be allocated on. Cannot be called at
31-
// run time, or when IsEmpty() is false, and marks the start of run time.
32-
void SetHeap(Nonnull<HeapAllocationInterface*> heap) {
33-
CHECK(todo_.IsEmpty());
34-
CHECK(!globals_.has_value());
35-
globals_ = RuntimeScope(heap);
36-
}
37-
3835
// Starts execution with `action` at the top of the stack. Cannot be called
3936
// when IsEmpty() is false.
4037
void Start(std::unique_ptr<Action> action);

executable_semantics/interpreter/exec_program.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void ExecProgram(Nonnull<Arena*> arena, AST ast, bool trace) {
4141
}
4242
llvm::outs() << "********** starting execution **********\n";
4343
}
44-
int result = Interpreter(arena, trace).InterpProgram(ast);
44+
int result = InterpProgram(ast, arena, trace);
4545
llvm::outs() << "result: " << result << "\n";
4646
}
4747

executable_semantics/interpreter/interpreter.cpp

+109-34
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,91 @@ using llvm::isa;
2727

2828
namespace Carbon {
2929

30+
// Selects between compile-time and run-time behavior.
31+
enum class Phase { CompileTime, RunTime };
32+
33+
// Constructs an ActionStack suitable for the specified phase.
34+
static auto MakeTodo(Phase phase, Nonnull<Heap*> heap) -> ActionStack {
35+
switch (phase) {
36+
case Phase::CompileTime:
37+
return ActionStack();
38+
case Phase::RunTime:
39+
return ActionStack(heap);
40+
}
41+
}
42+
43+
// An Interpreter represents an instance of the Carbon abstract machine. It
44+
// manages the state of the abstract machine, and executes the steps of Actions
45+
// passed to it.
46+
class Interpreter {
47+
public:
48+
// Constructs an Interpreter which allocates values on `arena`, and prints
49+
// traces if `trace` is true. `phase` indicates whether it executes at
50+
// compile time or run time.
51+
Interpreter(Phase phase, Nonnull<Arena*> arena, bool trace)
52+
: arena_(arena),
53+
heap_(arena),
54+
todo_(MakeTodo(phase, &heap_)),
55+
trace_(trace) {}
56+
57+
~Interpreter();
58+
59+
// Runs all the steps of `action`.
60+
void RunAllSteps(std::unique_ptr<Action> action);
61+
62+
// The result produced by the `action` argument of the most recent
63+
// RunAllSteps call. Cannot be called if `action` was an action that doesn't
64+
// produce results.
65+
auto result() const -> Nonnull<const Value*> { return todo_.result(); }
66+
67+
private:
68+
void Step();
69+
70+
// State transitions for expressions.
71+
void StepExp();
72+
// State transitions for lvalues.
73+
void StepLvalue();
74+
// State transitions for patterns.
75+
void StepPattern();
76+
// State transition for statements.
77+
void StepStmt();
78+
// State transition for declarations.
79+
void StepDeclaration();
80+
81+
auto CreateStruct(const std::vector<FieldInitializer>& fields,
82+
const std::vector<Nonnull<const Value*>>& values)
83+
-> Nonnull<const Value*>;
84+
85+
auto EvalPrim(Operator op, const std::vector<Nonnull<const Value*>>& args,
86+
SourceLocation source_loc) -> Nonnull<const Value*>;
87+
88+
// Returns the result of converting `value` to type `destination_type`.
89+
auto Convert(Nonnull<const Value*> value,
90+
Nonnull<const Value*> destination_type) const
91+
-> Nonnull<const Value*>;
92+
93+
void PrintState(llvm::raw_ostream& out);
94+
95+
Nonnull<Arena*> arena_;
96+
97+
Heap heap_;
98+
ActionStack todo_;
99+
100+
// The underlying states of continuation values. All StackFragments created
101+
// during execution are tracked here, in order to safely deallocate the
102+
// contents of any non-completed continuations at the end of execution.
103+
std::vector<Nonnull<ContinuationValue::StackFragment*>> stack_fragments_;
104+
105+
bool trace_;
106+
};
107+
108+
Interpreter::~Interpreter() {
109+
// Clean up any remaining suspended continuations.
110+
for (Nonnull<ContinuationValue::StackFragment*> fragment : stack_fragments_) {
111+
fragment->Clear();
112+
}
113+
}
114+
30115
//
31116
// State Operations
32117
//
@@ -85,10 +170,9 @@ auto Interpreter::CreateStruct(const std::vector<FieldInitializer>& fields,
85170
return arena_->New<StructValue>(std::move(elements));
86171
}
87172

88-
auto Interpreter::PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
89-
SourceLocation source_loc,
90-
std::optional<Nonnull<RuntimeScope*>> bindings)
91-
-> bool {
173+
auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
174+
SourceLocation source_loc,
175+
std::optional<Nonnull<RuntimeScope*>> bindings) -> bool {
92176
switch (p->kind()) {
93177
case Value::Kind::BindingPlaceholderValue: {
94178
if (!bindings.has_value()) {
@@ -864,59 +948,50 @@ void Interpreter::Step() {
864948
} // switch
865949
}
866950

867-
void Interpreter::RunAllSteps(bool trace_steps) {
951+
void Interpreter::RunAllSteps(std::unique_ptr<Action> action) {
952+
if (trace_) {
953+
PrintState(llvm::outs());
954+
}
955+
todo_.Start(std::move(action));
868956
while (!todo_.IsEmpty()) {
869957
Step();
870-
if (trace_steps) {
958+
if (trace_) {
871959
PrintState(llvm::outs());
872960
}
873961
}
874962
}
875963

876-
auto Interpreter::InterpProgram(const AST& ast) -> int {
877-
todo_.SetHeap(&heap_);
878-
879-
if (trace_) {
964+
auto InterpProgram(const AST& ast, Nonnull<Arena*> arena, bool trace) -> int {
965+
Interpreter interpreter(Phase::RunTime, arena, trace);
966+
if (trace) {
880967
llvm::outs() << "********** initializing globals **********\n";
881968
}
882969

883970
for (Nonnull<Declaration*> declaration : ast.declarations) {
884-
todo_.Start(std::make_unique<DeclarationAction>(declaration));
885-
RunAllSteps(trace_);
971+
interpreter.RunAllSteps(std::make_unique<DeclarationAction>(declaration));
886972
}
887973

888-
if (trace_) {
974+
if (trace) {
889975
llvm::outs() << "********** calling main function **********\n";
890-
PrintState(llvm::outs());
891976
}
892977

893-
todo_.Start(std::make_unique<ExpressionAction>(*ast.main_call));
894-
RunAllSteps(trace_);
978+
interpreter.RunAllSteps(std::make_unique<ExpressionAction>(*ast.main_call));
895979

896-
// Clean up any remaining suspended continuations.
897-
for (Nonnull<ContinuationValue::StackFragment*> fragment : stack_fragments_) {
898-
fragment->Clear();
899-
}
900-
901-
return cast<IntValue>(*todo_.result()).value();
902-
}
903-
904-
auto Interpreter::RunCompileTimeAction(std::unique_ptr<Action> action)
905-
-> Nonnull<const Value*> {
906-
todo_.Start(std::move(action));
907-
RunAllSteps(/*trace_steps=*/false);
908-
CHECK(stack_fragments_.empty());
909-
return todo_.result();
980+
return cast<IntValue>(*interpreter.result()).value();
910981
}
911982

912-
auto Interpreter::InterpExp(Nonnull<const Expression*> e)
983+
auto InterpExp(Nonnull<const Expression*> e, Nonnull<Arena*> arena, bool trace)
913984
-> Nonnull<const Value*> {
914-
return RunCompileTimeAction(std::make_unique<ExpressionAction>(e));
985+
Interpreter interpreter(Phase::CompileTime, arena, trace);
986+
interpreter.RunAllSteps(std::make_unique<ExpressionAction>(e));
987+
return interpreter.result();
915988
}
916989

917-
auto Interpreter::InterpPattern(Nonnull<const Pattern*> p)
990+
auto InterpPattern(Nonnull<const Pattern*> p, Nonnull<Arena*> arena, bool trace)
918991
-> Nonnull<const Value*> {
919-
return RunCompileTimeAction(std::make_unique<PatternAction>(p));
992+
Interpreter interpreter(Phase::CompileTime, arena, trace);
993+
interpreter.RunAllSteps(std::make_unique<PatternAction>(p));
994+
return interpreter.result();
920995
}
921996

922997
} // namespace Carbon

executable_semantics/interpreter/interpreter.h

+26-81
Original file line numberDiff line numberDiff line change
@@ -22,87 +22,32 @@
2222

2323
namespace Carbon {
2424

25-
class Interpreter {
26-
public:
27-
explicit Interpreter(Nonnull<Arena*> arena, bool trace)
28-
: arena_(arena), heap_(arena), trace_(trace) {}
29-
30-
// Interpret the whole program.
31-
auto InterpProgram(const AST& ast) -> int;
32-
33-
// Interpret an expression at compile-time.
34-
auto InterpExp(Nonnull<const Expression*> e) -> Nonnull<const Value*>;
35-
36-
// Interpret a pattern at compile-time.
37-
auto InterpPattern(Nonnull<const Pattern*> p) -> Nonnull<const Value*>;
38-
39-
// Attempts to match `v` against the pattern `p`, returning whether matching
40-
// is successful. If it is, populates **bindings with the variables bound by
41-
// the match; `bindings` should only be nullopt in contexts where `p`
42-
// is not permitted to bind variables. **bindings may be modified even if the
43-
// match is unsuccessful, so it should typically be created for the
44-
// PatternMatch call and then merged into an existing scope on success.
45-
[[nodiscard]] auto PatternMatch(
46-
Nonnull<const Value*> p, Nonnull<const Value*> v,
47-
SourceLocation source_loc, std::optional<Nonnull<RuntimeScope*>> bindings)
48-
-> bool;
49-
50-
// Support TypeChecker allocating values on the heap.
51-
auto AllocateValue(Nonnull<const Value*> v) -> AllocationId {
52-
return heap_.AllocateValue(v);
53-
}
54-
55-
private:
56-
void Step();
57-
58-
// State transitions for expressions.
59-
void StepExp();
60-
// State transitions for lvalues.
61-
void StepLvalue();
62-
// State transitions for patterns.
63-
void StepPattern();
64-
// State transition for statements.
65-
void StepStmt();
66-
// State transition for declarations.
67-
void StepDeclaration();
68-
69-
// Calls Step() repeatedly until there are no steps left to execute. Produces
70-
// trace output if trace_steps is true.
71-
void RunAllSteps(bool trace_steps);
72-
73-
auto CreateStruct(const std::vector<FieldInitializer>& fields,
74-
const std::vector<Nonnull<const Value*>>& values)
75-
-> Nonnull<const Value*>;
76-
77-
auto EvalPrim(Operator op, const std::vector<Nonnull<const Value*>>& args,
78-
SourceLocation source_loc) -> Nonnull<const Value*>;
79-
80-
// Returns the result of converting `value` to type `destination_type`.
81-
auto Convert(Nonnull<const Value*> value,
82-
Nonnull<const Value*> destination_type) const
83-
-> Nonnull<const Value*>;
84-
85-
void PrintState(llvm::raw_ostream& out);
86-
87-
// Runs `action` in an environment where the given constants are defined, and
88-
// returns the result. `action` must produce a result. In other words, it must
89-
// not be a StatementAction, ScopeAction, or DeclarationAction. Can only be
90-
// called at compile time (before InterpProgram), and while `todo_` is empty.
91-
auto RunCompileTimeAction(std::unique_ptr<Action> action)
92-
-> Nonnull<const Value*>;
93-
94-
Nonnull<Arena*> arena_;
95-
96-
Heap heap_;
97-
ActionStack todo_;
98-
99-
// The underlying states of continuation values. All StackFragments created
100-
// during execution are tracked here, in order to safely deallocate the
101-
// contents of any non-completed continuations at the end of execution.
102-
std::vector<Nonnull<ContinuationValue::StackFragment*>> stack_fragments_;
103-
104-
bool trace_;
105-
};
25+
// Interprets the program defined by `ast`, allocating values on `arena` and
26+
// printing traces if `trace` is true.
27+
auto InterpProgram(const AST& ast, Nonnull<Arena*> arena, bool trace) -> int;
28+
29+
// Interprets `e` at compile-time, allocating values on `arena` and
30+
// printing traces if `trace` is true.
31+
auto InterpExp(Nonnull<const Expression*> e, Nonnull<Arena*> arena, bool trace)
32+
-> Nonnull<const Value*>;
33+
34+
// Interprets `p` at compile-time, allocating values on `arena` and
35+
// printing traces if `trace` is true.
36+
auto InterpPattern(Nonnull<const Pattern*> p, Nonnull<Arena*> arena, bool trace)
37+
-> Nonnull<const Value*>;
38+
39+
// Attempts to match `v` against the pattern `p`, returning whether matching
40+
// is successful. If it is, populates **bindings with the variables bound by
41+
// the match; `bindings` should only be nullopt in contexts where `p`
42+
// is not permitted to bind variables. **bindings may be modified even if the
43+
// match is unsuccessful, so it should typically be created for the
44+
// PatternMatch call and then merged into an existing scope on success.
45+
// TODO: consider moving this to a separate header.
46+
[[nodiscard]] auto PatternMatch(Nonnull<const Value*> p,
47+
Nonnull<const Value*> v,
48+
SourceLocation source_loc,
49+
std::optional<Nonnull<RuntimeScope*>> bindings)
50+
-> bool;
10651

10752
} // namespace Carbon
10853

0 commit comments

Comments
 (0)