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
+51
View File
@@ -17,6 +17,8 @@
namespace Carbon {
class FunctionDeclaration;
class Statement {
public:
enum class Kind {
@@ -167,9 +169,24 @@ class Return : public Statement {
auto expression() -> Expression& { return *expression_; }
auto is_omitted_expression() const -> bool { return is_omitted_expression_; }
// The AST node representing the function body this statement returns from.
// Can only be called after ResolveControlFlow has visited this node.
//
// Note that this function does not represent an edge in the tree
// structure of the AST: the return value is not a child of this node,
// but an ancestor.
auto function() const -> const FunctionDeclaration& { return **function_; }
// Can only be called once, by ResolveControlFlow.
void set_function(Nonnull<const FunctionDeclaration*> function) {
CHECK(!function_.has_value());
function_ = function;
}
private:
Nonnull<Expression*> expression_;
bool is_omitted_expression_;
std::optional<Nonnull<const FunctionDeclaration*>> function_;
};
class Sequence : public Statement {
@@ -244,6 +261,23 @@ class Break : public Statement {
static auto classof(const Statement* stmt) -> bool {
return stmt->kind() == Kind::Break;
}
// The AST node representing the loop this statement breaks out of.
// Can only be called after ResolveControlFlow has visited this node.
//
// Note that this function does not represent an edge in the tree
// structure of the AST: the return value is not a child of this node,
// but an ancestor.
auto loop() const -> const Statement& { return **loop_; }
// Can only be called once, by ResolveControlFlow.
void set_loop(Nonnull<const Statement*> loop) {
CHECK(!loop_.has_value());
loop_ = loop;
}
private:
std::optional<Nonnull<const Statement*>> loop_;
};
class Continue : public Statement {
@@ -254,6 +288,23 @@ class Continue : public Statement {
static auto classof(const Statement* stmt) -> bool {
return stmt->kind() == Kind::Continue;
}
// The AST node representing the loop this statement continues.
// Can only be called after ResolveControlFlow has visited this node.
//
// Note that this function does not represent an edge in the tree
// structure of the AST: the return value is not a child of this node,
// but an ancestor.
auto loop() const -> const Statement& { return **loop_; }
// Can only be called once, by ResolveControlFlow.
void set_loop(Nonnull<const Statement*> loop) {
CHECK(!loop_.has_value());
loop_ = loop;
}
private:
std::optional<Nonnull<const Statement*>> loop_;
};
class Match : public Statement {
+17 -5
View File
@@ -7,15 +7,13 @@ package(default_visibility = ["//executable_semantics:__pkg__"])
# These currently have to be a single build rule because of a dependency cycle
# in printing.
cc_library(
name = "action_frame_and_value",
name = "action_and_value",
srcs = [
"action.cpp",
"frame.cpp",
"value.cpp",
],
hdrs = [
"action.h",
"frame.h",
"value.h",
],
deps = [
@@ -55,6 +53,7 @@ cc_library(
hdrs = ["exec_program.h"],
deps = [
":interpreter",
":resolve_control_flow",
":type_checker",
"//executable_semantics/ast",
],
@@ -74,7 +73,7 @@ cc_library(
srcs = ["heap.cpp"],
hdrs = ["heap.h"],
deps = [
":action_frame_and_value",
":action_and_value",
":address",
"//common:ostream",
"@llvm-project//llvm:Support",
@@ -90,7 +89,7 @@ cc_library(
"interpreter.h",
],
deps = [
":action_frame_and_value",
":action_and_value",
":address",
":heap",
"//common:check",
@@ -102,6 +101,19 @@ cc_library(
],
)
cc_library(
name = "resolve_control_flow",
srcs = ["resolve_control_flow.cpp"],
hdrs = ["resolve_control_flow.h"],
deps = [
"//common:check",
"//executable_semantics/ast",
"//executable_semantics/ast:declaration",
"//executable_semantics/ast:statement",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "stack",
hdrs = ["stack.h"],
@@ -35,6 +35,8 @@ void Action::Print(llvm::raw_ostream& out) const {
case Action::Kind::StatementAction:
cast<StatementAction>(*this).statement().PrintDepth(1, out);
break;
case Action::Kind::ScopeAction:
out << "ScopeAction";
}
out << "<" << pos_ << ">";
if (results_.size() > 0) {
+56
View File
@@ -11,12 +11,25 @@
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/ast/pattern.h"
#include "executable_semantics/ast/statement.h"
#include "executable_semantics/interpreter/dictionary.h"
#include "executable_semantics/interpreter/stack.h"
#include "executable_semantics/interpreter/value.h"
#include "llvm/Support/Compiler.h"
namespace Carbon {
using Env = Dictionary<std::string, Address>;
struct Scope {
explicit Scope(Env values) : Scope(values, std::vector<std::string>()) {}
Scope(Env values, std::vector<std::string> l)
: values(values), locals(std::move(l)) {}
Env values;
std::vector<std::string> locals;
bool deallocated = false;
};
class Action {
public:
enum class Kind {
@@ -24,6 +37,7 @@ class Action {
ExpressionAction,
PatternAction,
StatementAction,
ScopeAction,
};
Action(const Value&) = delete;
@@ -32,10 +46,23 @@ class Action {
void AddResult(Nonnull<const Value*> result) { results_.push_back(result); }
void Clear() {
CHECK(!scope_.has_value());
pos_ = 0;
results_.clear();
}
// Associates this action with a new scope, with initial state `scope`.
// Values that are local to this scope will be deallocated when this
// Action is completed or unwound. Can only be called once on a given
// Action.
void StartScope(Scope scope) {
CHECK(!scope_.has_value());
scope_ = std::move(scope);
}
// Returns the scope associated with this Action, if any.
auto scope() -> std::optional<Scope>& { return scope_; }
static void PrintList(const Stack<Nonnull<Action*>>& ls,
llvm::raw_ostream& out);
@@ -69,10 +96,14 @@ class Action {
private:
int pos_ = 0;
std::vector<Nonnull<const Value*>> results_;
std::optional<Scope> scope_;
const Kind kind_;
};
// An Action which implements evaluation of an Expression to produce an
// lvalue. The result be expressed as a PointerValue which points to the
// Expression's value.
class LValAction : public Action {
public:
explicit LValAction(Nonnull<const Expression*> expression)
@@ -82,12 +113,15 @@ class LValAction : public Action {
return action->kind() == Kind::LValAction;
}
// The Expression this Action evaluates.
auto expression() const -> const Expression& { return *expression_; }
private:
Nonnull<const Expression*> expression_;
};
// An Action which implements evaluation of an Expression to produce an
// rvalue. The result is expressed as a Value.
class ExpressionAction : public Action {
public:
explicit ExpressionAction(Nonnull<const Expression*> expression)
@@ -97,12 +131,15 @@ class ExpressionAction : public Action {
return action->kind() == Kind::ExpressionAction;
}
// The Expression this Action evaluates.
auto expression() const -> const Expression& { return *expression_; }
private:
Nonnull<const Expression*> expression_;
};
// An Action which implements evaluation of a Pattern. The result is expressed
// as a Value.
class PatternAction : public Action {
public:
explicit PatternAction(Nonnull<const Pattern*> pattern)
@@ -112,12 +149,15 @@ class PatternAction : public Action {
return action->kind() == Kind::PatternAction;
}
// The Pattern this Action evaluates.
auto pattern() const -> const Pattern& { return *pattern_; }
private:
Nonnull<const Pattern*> pattern_;
};
// An Action which implements execution of a Statement. Does not produce a
// result.
class StatementAction : public Action {
public:
explicit StatementAction(Nonnull<const Statement*> statement)
@@ -127,12 +167,28 @@ class StatementAction : public Action {
return action->kind() == Kind::StatementAction;
}
// The Statement this Action executes.
auto statement() const -> const Statement& { return *statement_; }
private:
Nonnull<const Statement*> statement_;
};
// Action which does nothing except introduce a new scope into the action
// stack. This is useful when a distinct scope doesn't otherwise have an
// Action it can naturally be associated with. ScopeActions are not associated
// with AST nodes.
class ScopeAction : public Action {
public:
ScopeAction(Scope scope) : Action(Kind::ScopeAction) {
StartScope(std::move(scope));
}
static auto classof(const Action* action) -> bool {
return action->kind() == Kind::ScopeAction;
}
};
} // namespace Carbon
#endif // EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_
@@ -8,6 +8,7 @@
#include "common/ostream.h"
#include "executable_semantics/common/arena.h"
#include "executable_semantics/interpreter/interpreter.h"
#include "executable_semantics/interpreter/resolve_control_flow.h"
#include "executable_semantics/interpreter/type_checker.h"
namespace Carbon {
@@ -42,6 +43,7 @@ void ExecProgram(Nonnull<Arena*> arena, AST ast, bool trace) {
}
llvm::outs() << "********** type checking **********\n";
}
ResolveControlFlow(ast);
TypeChecker type_checker(arena, trace);
TypeChecker::TypeCheckContext p = type_checker.TopLevel(&ast.declarations);
TypeEnv top = p.types;
@@ -1,18 +0,0 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "executable_semantics/interpreter/frame.h"
#include "common/ostream.h"
#include "executable_semantics/interpreter/action.h"
namespace Carbon {
void Frame::Print(llvm::raw_ostream& out) const {
out << name << "{";
Action::PrintList(todo, out);
out << "}";
}
} // namespace Carbon
-66
View File
@@ -1,66 +0,0 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_
#define EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_
#include <string>
#include <utility>
#include <vector>
#include "common/ostream.h"
#include "executable_semantics/interpreter/action.h"
#include "executable_semantics/interpreter/address.h"
#include "executable_semantics/interpreter/dictionary.h"
#include "executable_semantics/interpreter/stack.h"
#include "llvm/Support/Compiler.h"
namespace Carbon {
using Env = Dictionary<std::string, Address>;
struct Scope {
explicit Scope(Env values) : Scope(values, std::vector<std::string>()) {}
Scope(Env values, std::vector<std::string> l)
: values(values), locals(std::move(l)) {}
Env values;
std::vector<std::string> locals;
};
// A frame represents either a function call or a delimited continuation.
struct Frame {
Frame(const Frame&) = delete;
auto operator=(const Frame&) -> Frame& = delete;
Frame(std::string n, Stack<Nonnull<Scope*>> s, Stack<Nonnull<Action*>> c)
: name(std::move(n)),
scopes(std::move(s)),
todo(std::move(c)),
continuation() {}
void Print(llvm::raw_ostream& out) const;
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
// The name of the function.
std::string name;
// If the frame represents a function call, the bottom scope
// contains the parameter-argument bindings for this function
// call. The rest of the scopes contain local variables defined by
// blocks within the function. The scope at the top of the stack is
// the current scope and its environment is the one used for looking
// up the value associated with a variable.
Stack<Nonnull<Scope*>> scopes;
// The actions that need to be executed in the future of the
// current function call. The top of the stack is the action
// that is executed first.
Stack<Nonnull<Action*>> todo;
// If this frame is the bottom frame of a continuation, then it stores
// the address of the continuation.
std::optional<Address> continuation;
};
} // namespace Carbon
#endif // EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_
+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
+26 -17
View File
@@ -13,7 +13,7 @@
#include "executable_semantics/ast/declaration.h"
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/ast/pattern.h"
#include "executable_semantics/interpreter/frame.h"
#include "executable_semantics/interpreter/action.h"
#include "executable_semantics/interpreter/heap.h"
#include "executable_semantics/interpreter/stack.h"
#include "executable_semantics/interpreter/value.h"
@@ -21,8 +21,6 @@
namespace Carbon {
using Env = Dictionary<std::string, Address>;
class Interpreter {
public:
explicit Interpreter(Nonnull<Arena*> arena, bool trace)
@@ -85,16 +83,19 @@ class Interpreter {
// and increments its position counter.
struct RunAgain {};
// Transition type which unwinds the `todo` and `scopes` stacks until it
// reaches a specified Action lower in the stack.
// Transition type which unwinds the `todo` stack until it reaches the
// StatementAction associated with `ast_node`. Execution then resumes with
// that StatementAction.
struct UnwindTo {
const Nonnull<Action*> new_top;
Nonnull<const Statement*> ast_node;
};
// Transition type which unwinds the entire current stack frame, and returns
// a specified value to the caller.
struct UnwindFunctionCall {
Nonnull<const Value*> return_val;
// Transition type which unwinds the `todo` stack down to and including the
// StatementAction associated with `ast_node`. If `result` is set, it will be
// treated as the result of that StatementAction.
struct UnwindPast {
Nonnull<const Statement*> ast_node;
std::optional<Nonnull<const Value*>> result;
};
// Transition type which removes the current action from the top of the todo
@@ -112,12 +113,12 @@ class Interpreter {
// uses of this type should be replaced with meaningful transitions.
struct ManualTransition {};
using Transition =
std::variant<Done, Spawn, Delegate, RunAgain, UnwindTo,
UnwindFunctionCall, CallFunction, ManualTransition>;
using Transition = std::variant<Done, Spawn, Delegate, RunAgain, UnwindTo,
UnwindPast, CallFunction, ManualTransition>;
// Visitor which implements the behavior associated with each transition type.
class DoTransition;
friend class DoTransition;
void Step();
@@ -131,12 +132,12 @@ class Interpreter {
auto StepStmt() -> Transition;
void InitGlobals(llvm::ArrayRef<Nonnull<Declaration*>> fs);
auto CurrentScope() -> Scope&;
auto CurrentEnv() -> Env;
auto GetFromEnv(SourceLocation source_loc, const std::string& name)
-> Address;
void DeallocateScope(Nonnull<Scope*> scope);
void DeallocateLocals(Nonnull<Frame*> frame);
void DeallocateScope(Scope& scope);
auto CreateTuple(Nonnull<Action*> act, Nonnull<const Expression*> exp)
-> Nonnull<const Value*>;
@@ -157,14 +158,22 @@ class Interpreter {
void PrintState(llvm::raw_ostream& out);
// Runs `action` in a scope consisting of `values`, and returns the result.
// `action` must produce a result. In other words, it must not be a
// StatementAction or ScopeAction.
//
// TODO: consider whether to use this->trace_ rather than a separate
// trace_steps parameter.
auto ExecuteAction(Nonnull<Action*> action, Env values, bool trace_steps)
-> Nonnull<const Value*>;
Nonnull<Arena*> arena_;
// Globally-defined entities, such as functions, structs, or choices.
Env globals_;
Stack<Nonnull<Frame*>> stack_;
Stack<Nonnull<Action*>> todo_;
Heap heap_;
std::optional<Nonnull<const Value*>> program_value_;
bool trace_;
};
@@ -0,0 +1,104 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "executable_semantics/interpreter/resolve_control_flow.h"
#include "executable_semantics/ast/declaration.h"
#include "executable_semantics/ast/statement.h"
#include "executable_semantics/common/error.h"
#include "llvm/Support/Casting.h"
using llvm::cast;
namespace Carbon {
// Resolves control-flow edges in the AST rooted at `statement`. `return`
// statements will resolve to `*function`, and `break` and `continue`
// statements will resolve to `*loop`. If either parameter is nullopt, that
// indicates a context where the corresponding statements are not permitted.
static void ResolveControlFlow(
Nonnull<Statement*> statement,
std::optional<Nonnull<const FunctionDeclaration*>> function,
std::optional<Nonnull<const Statement*>> loop) {
switch (statement->kind()) {
case Statement::Kind::Return:
if (!function.has_value()) {
FATAL_COMPILATION_ERROR(statement->source_loc())
<< "return is not within a function body";
}
cast<Return>(*statement).set_function(*function);
return;
case Statement::Kind::Break:
if (!loop.has_value()) {
FATAL_COMPILATION_ERROR(statement->source_loc())
<< "break is not within a loop body";
}
cast<Break>(*statement).set_loop(*loop);
return;
case Statement::Kind::Continue:
if (!loop.has_value()) {
FATAL_COMPILATION_ERROR(statement->source_loc())
<< "continue is not within a loop body";
}
cast<Continue>(*statement).set_loop(*loop);
return;
case Statement::Kind::If: {
auto& if_stmt = cast<If>(*statement);
ResolveControlFlow(&if_stmt.then_statement(), function, loop);
if (if_stmt.else_statement().has_value()) {
ResolveControlFlow(*if_stmt.else_statement(), function, loop);
}
return;
}
case Statement::Kind::Sequence: {
auto& seq = cast<Sequence>(*statement);
ResolveControlFlow(&seq.statement(), function, loop);
if (seq.next().has_value()) {
ResolveControlFlow(*seq.next(), function, loop);
}
return;
}
case Statement::Kind::Block: {
auto& block = cast<Block>(*statement);
if (block.statement().has_value()) {
ResolveControlFlow(*block.statement(), function, loop);
}
return;
}
case Statement::Kind::While:
ResolveControlFlow(&cast<While>(*statement).body(), function, statement);
return;
case Statement::Kind::Match: {
auto& match = cast<Match>(*statement);
for (Match::Clause& clause : match.clauses()) {
ResolveControlFlow(&clause.statement(), function, loop);
}
return;
}
case Statement::Kind::Continuation:
ResolveControlFlow(&cast<Continuation>(*statement).body(), std::nullopt,
std::nullopt);
return;
case Statement::Kind::ExpressionStatement:
case Statement::Kind::Assign:
case Statement::Kind::VariableDefinition:
case Statement::Kind::Run:
case Statement::Kind::Await:
return;
}
}
void ResolveControlFlow(AST& ast) {
for (auto declaration : ast.declarations) {
if (declaration->kind() != Declaration::Kind::FunctionDeclaration) {
continue;
}
auto& function = cast<FunctionDeclaration>(*declaration);
if (function.body().has_value()) {
ResolveControlFlow(*function.body(), &function, std::nullopt);
}
}
}
} // namespace Carbon
@@ -0,0 +1,19 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_RESOLVE_CONTROL_FLOW_H_
#define EXECUTABLE_SEMANTICS_INTERPRETER_RESOLVE_CONTROL_FLOW_H_
#include "executable_semantics/ast/ast.h"
#include "executable_semantics/common/nonnull.h"
namespace Carbon {
// Resolves non-local control-flow edges, such as `break` and `return`, in the
// given AST.
void ResolveControlFlow(AST& ast);
} // namespace Carbon
#endif // EXECUTABLE_SEMANTICS_INTERPRETER_RESOLVE_CONTROL_FLOW_H_
+4 -3
View File
@@ -9,7 +9,7 @@
#include "common/check.h"
#include "executable_semantics/common/arena.h"
#include "executable_semantics/common/error.h"
#include "executable_semantics/interpreter/frame.h"
#include "executable_semantics/interpreter/action.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Casting.h"
@@ -274,8 +274,9 @@ void Value::Print(llvm::raw_ostream& out) const {
case Value::Kind::ContinuationValue: {
out << "{";
llvm::ListSeparator sep(" :: ");
for (Nonnull<Frame*> frame : cast<ContinuationValue>(*this).stack()) {
out << sep << *frame;
for (Nonnull<const Action*> action :
cast<ContinuationValue>(*this).stack()) {
out << sep << *action;
}
out << "}";
break;
+7 -7
View File
@@ -21,6 +21,8 @@
namespace Carbon {
class Action;
// Abstract base class of all AST nodes representing values.
//
// Value and its derived classes support LLVM-style RTTI, including
@@ -106,8 +108,6 @@ struct StructElement {
Nonnull<const Value*> value;
};
struct Frame; // Used by continuation.
// An integer value.
class IntValue : public Value {
public:
@@ -489,21 +489,21 @@ class VariableType : public Value {
// fragment, which is exposed by `Stack()`.
class ContinuationValue : public Value {
public:
explicit ContinuationValue(Nonnull<std::vector<Nonnull<Frame*>>*> stack)
explicit ContinuationValue(Nonnull<std::vector<Nonnull<Action*>>*> stack)
: Value(Kind::ContinuationValue), stack_(stack) {}
static auto classof(const Value* value) -> bool {
return value->kind() == Kind::ContinuationValue;
}
// The call stack of the suspended continuation, starting with the top
// frame (the reverse of the usual order). Note that this provides mutable
// The todo stack of the suspended continuation, starting with the top
// Action (the reverse of the usual order). Note that this provides mutable
// access, even when *this is const, because of the reference-like semantics
// of ContinuationValue.
auto stack() const -> std::vector<Nonnull<Frame*>>& { return *stack_; }
auto stack() const -> std::vector<Nonnull<Action*>>& { return *stack_; }
private:
Nonnull<std::vector<Nonnull<Frame*>>*> stack_;
Nonnull<std::vector<Nonnull<Action*>>*> stack_;
};
// The String type.