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

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
This commit is contained in:
Geoff Romer
2021-10-21 14:55:41 -07:00
committed by GitHub
co-authored by Jon Meow
parent 44154c8663
commit 3bec7f8dc0
13 changed files with 450 additions and 329 deletions
+162 -213
View File
@@ -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