diff --git a/executable_semantics/ast/statement.h b/executable_semantics/ast/statement.h index fccf46b03da4..3c7d924df54b 100644 --- a/executable_semantics/ast/statement.h +++ b/executable_semantics/ast/statement.h @@ -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 function) { + CHECK(!function_.has_value()); + function_ = function; + } + private: Nonnull expression_; bool is_omitted_expression_; + std::optional> 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 loop) { + CHECK(!loop_.has_value()); + loop_ = loop; + } + + private: + std::optional> 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 loop) { + CHECK(!loop_.has_value()); + loop_ = loop; + } + + private: + std::optional> loop_; }; class Match : public Statement { diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index 651bd049a676..f9d8ba44a2b2 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -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"], diff --git a/executable_semantics/interpreter/action.cpp b/executable_semantics/interpreter/action.cpp index bdbdff297c74..7f6d678345d2 100644 --- a/executable_semantics/interpreter/action.cpp +++ b/executable_semantics/interpreter/action.cpp @@ -35,6 +35,8 @@ void Action::Print(llvm::raw_ostream& out) const { case Action::Kind::StatementAction: cast(*this).statement().PrintDepth(1, out); break; + case Action::Kind::ScopeAction: + out << "ScopeAction"; } out << "<" << pos_ << ">"; if (results_.size() > 0) { diff --git a/executable_semantics/interpreter/action.h b/executable_semantics/interpreter/action.h index e7a5d197a600..f2c2a47d0645 100644 --- a/executable_semantics/interpreter/action.h +++ b/executable_semantics/interpreter/action.h @@ -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; + +struct Scope { + explicit Scope(Env values) : Scope(values, std::vector()) {} + Scope(Env values, std::vector l) + : values(values), locals(std::move(l)) {} + + Env values; + std::vector 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 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& { return scope_; } + static void PrintList(const Stack>& ls, llvm::raw_ostream& out); @@ -69,10 +96,14 @@ class Action { private: int pos_ = 0; std::vector> results_; + std::optional 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 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 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 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 expression_; }; +// An Action which implements evaluation of a Pattern. The result is expressed +// as a Value. class PatternAction : public Action { public: explicit PatternAction(Nonnull 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 pattern_; }; +// An Action which implements execution of a Statement. Does not produce a +// result. class StatementAction : public Action { public: explicit StatementAction(Nonnull 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 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_ diff --git a/executable_semantics/interpreter/exec_program.cpp b/executable_semantics/interpreter/exec_program.cpp index 7d9d2f0decce..3a425bb0bb13 100644 --- a/executable_semantics/interpreter/exec_program.cpp +++ b/executable_semantics/interpreter/exec_program.cpp @@ -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, 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; diff --git a/executable_semantics/interpreter/frame.cpp b/executable_semantics/interpreter/frame.cpp deleted file mode 100644 index 3f852bb3a911..000000000000 --- a/executable_semantics/interpreter/frame.cpp +++ /dev/null @@ -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 diff --git a/executable_semantics/interpreter/frame.h b/executable_semantics/interpreter/frame.h deleted file mode 100644 index 97b4473df0b1..000000000000 --- a/executable_semantics/interpreter/frame.h +++ /dev/null @@ -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 -#include -#include - -#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; - -struct Scope { - explicit Scope(Env values) : Scope(values, std::vector()) {} - Scope(Env values, std::vector l) - : values(values), locals(std::move(l)) {} - - Env values; - std::vector 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> s, Stack> 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> 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> todo; - // If this frame is the bottom frame of a continuation, then it stores - // the address of the continuation. - std::optional
continuation; -}; - -} // namespace Carbon - -#endif // EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_ diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index d8d7027cdcda..9efdd2f0ea1e 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -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 = stack_.Top(); - return frame->scopes.Top()->values; +auto Interpreter::CurrentScope() -> Scope& { + for (Nonnull 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 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> fs) { } } -void Interpreter::DeallocateScope(Nonnull scope) { - for (const auto& l : scope->locals) { - std::optional
a = scope->values.Get(l); +void Interpreter::DeallocateScope(Scope& scope) { + CHECK(!scope.deallocated); + for (const auto& l : scope.locals) { + std::optional
a = scope.values.Get(l); CHECK(a); heap_.Deallocate(*a); } -} - -void Interpreter::DeallocateLocals(Nonnull frame) { - while (!frame->scopes.IsEmpty()) { - DeallocateScope(frame->scopes.Top()); - frame->scopes.Pop(); - } + scope.deallocated = true; } auto Interpreter::CreateTuple(Nonnull act, @@ -374,7 +374,7 @@ void Interpreter::PatternAssignment(Nonnull pat, } auto Interpreter::StepLvalue() -> Transition { - Nonnull act = stack_.Top()->todo.Top(); + Nonnull act = todo_.Top(); const Expression& exp = cast(*act).expression(); if (trace_) { llvm::outs() << "--- step lvalue " << exp << " (" << exp.source_loc() @@ -524,7 +524,7 @@ auto Interpreter::Convert(Nonnull value, } auto Interpreter::StepExp() -> Transition { - Nonnull act = stack_.Top()->todo.Top(); + Nonnull act = todo_.Top(); const Expression& exp = cast(*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 act = stack_.Top()->todo.Top(); + Nonnull act = todo_.Top(); const Pattern& pattern = cast(*act).pattern(); if (trace_) { llvm::outs() << "--- step pattern " << pattern << " (" @@ -772,38 +779,13 @@ auto Interpreter::StepPattern() -> Transition { } } -static auto IsWhileAct(Nonnull act) -> bool { - switch (act->kind()) { - case Action::Kind::StatementAction: - switch (cast(*act).statement().kind()) { - case Statement::Kind::While: - return true; - default: - return false; - } - default: - return false; - } -} - -static auto HasLocalScope(Nonnull act) -> bool { - switch (act->kind()) { - case Action::Kind::StatementAction: - switch (cast(*act).statement().kind()) { - case Statement::Kind::Block: - case Statement::Kind::Match: - return true; - default: - return false; - } - default: - return false; - } +static auto IsRunAction(Nonnull action) -> bool { + const auto* statement = dyn_cast(action); + return statement != nullptr && llvm::isa(statement->statement()); } auto Interpreter::StepStmt() -> Transition { - Nonnull frame = stack_.Top(); - Nonnull act = frame->todo.Top(); + Nonnull act = todo_.Top(); const Statement& stmt = cast(*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(CurrentEnv())); + act->StartScope(Scope(CurrentEnv())); return Spawn{arena_->New(&match_stmt.expression())}; } else { int clause_num = act->pos() - 1; if (clause_num >= static_cast(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(&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(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(stmt).loop()}; } case Statement::Kind::Block: { if (act->pos() == 0) { - const auto& block = cast(stmt); + const Block& block = cast(stmt); if (block.statement()) { - frame->scopes.Push(arena_->New(CurrentEnv())); + act->StartScope(Scope(CurrentEnv())); return Spawn{arena_->New(*block.statement())}; } else { return Done{}; } } else { - Nonnull 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(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>(arena_->New(CurrentEnv())); - Stack> todo; - todo.Push(arena_->New( - arena_->New(arena_, stmt.source_loc()))); - todo.Push(arena_->New(&cast(stmt).body())); - auto continuation_stack = arena_->New>>(); - auto continuation_frame = - arena_->New("__continuation", scopes, todo); - continuation_stack->push_back(continuation_frame); + auto continuation_stack = arena_->New>>(); + continuation_stack->push_back( + arena_->New(&cast(stmt).body())); + continuation_stack->push_back( + arena_->New(Scope(CurrentEnv()))); Address continuation_address = heap_.AllocateValue( arena_->New(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(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(stmt); if (act->pos() == 0) { // Evaluate the argument of the run statement. - return Spawn{ - arena_->New(&cast(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(arena_->New( - stmt.source_loc(), - arena_->New(stmt.source_loc()))); - frame->todo.Push(ignore_result); - // Push the continuation onto the current stack_. - Nonnull arg = - Convert(act->results()[0], arena_->New()); - std::vector>& continuation_vector = - cast(*arg).stack(); + return Spawn{arena_->New(&run.argument())}; + } else if (act->pos() == 1) { + // Push the continuation onto the current stack. + std::vector>& continuation_vector = + cast(*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> paused; - do { - paused.push_back(stack_.Pop()); - } while (paused.back()->continuation == std::nullopt); - // Update the continuation with the paused stack_. - const auto& continuation = cast( - *heap_.Read(*paused.back()->continuation, stmt.source_loc())); + todo_.Pop(); + std::vector> paused; + while (!IsRunAction(todo_.Top())) { + paused.push_back(todo_.Pop()); + } + const auto& continuation = + cast(*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 = 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 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 = interpreter->stack_.Top(); - Nonnull action = frame->todo.Top(); + Nonnull 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 = interpreter->stack_.Top(); - frame->todo.Pop(); - frame->todo.Push(delegate.delegate); + Nonnull 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 = interpreter->stack_.Top()->todo.Top(); + Nonnull action = interpreter->todo_.Top(); action->set_pos(action->pos() + 1); } void operator()(const UnwindTo& unwind_to) { - Nonnull 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(interpreter->todo_.Top()); + statement_action != nullptr && + &statement_action->statement() == unwind_to.ast_node) { + break; + } + Nonnull 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 = interpreter->todo_.Pop(); + if (action->scope().has_value()) { + interpreter->DeallocateScope(*action->scope()); + } + if (const auto* statement_action = dyn_cast(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 = interpreter->todo_.Top(); + action->set_pos(action->pos() + 1); Nonnull converted_args = interpreter->Convert( call.args, &call.function->param_pattern().static_type()); std::optional 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 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>(interpreter->arena_->New(values, params)); + interpreter->todo_.Push( + interpreter->arena_->New(std::move(new_scope))); CHECK(call.function->body()) << "Calling a function that's missing a body"; - auto todo = Stack>( + interpreter->todo_.Push( interpreter->arena_->New(*call.function->body())); - auto frame = - interpreter->arena_->New(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 = stack_.Top(); - if (frame->todo.IsEmpty()) { - std::visit(DoTransition(this), - Transition{UnwindFunctionCall{TupleValue::Empty()}}); - return; - } - - Nonnull act = frame->todo.Top(); + Nonnull 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, Env values, + bool trace_steps) -> Nonnull { + todo_ = {}; + todo_.Push(arena_->New(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> fs, Nonnull 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>(arena_->New(call_main)); - auto scopes = Stack>(arena_->New(globals_)); - stack_ = Stack>(arena_->New("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(**program_value_).value(); + return cast(*ExecuteAction(arena_->New(call_main), + globals_, trace_)) + .value(); } auto Interpreter::InterpExp(Env values, Nonnull e) -> Nonnull { - CHECK(program_value_ == std::nullopt); - auto program_value_guard = - llvm::make_scope_exit([&] { program_value_ = std::nullopt; }); - auto todo = Stack>(arena_->New(e)); - auto scopes = Stack>(arena_->New(values)); - stack_ = - Stack>(arena_->New("InterpExp", scopes, todo)); - - while (stack_.Count() > 1 || !stack_.Top()->todo.IsEmpty()) { - Step(); - } - CHECK(program_value_ != std::nullopt); - return *program_value_; + return ExecuteAction(arena_->New(e), values, + /*trace_steps=*/false); } auto Interpreter::InterpPattern(Env values, Nonnull p) -> Nonnull { - CHECK(program_value_ == std::nullopt); - auto program_value_guard = - llvm::make_scope_exit([&] { program_value_ = std::nullopt; }); - auto todo = Stack>(arena_->New(p)); - auto scopes = Stack>(arena_->New(values)); - stack_ = - Stack>(arena_->New("InterpPattern", scopes, todo)); - - while (stack_.Count() > 1 || !stack_.Top()->todo.IsEmpty()) { - Step(); - } - CHECK(program_value_ != std::nullopt); - return *program_value_; + return ExecuteAction(arena_->New(p), values, + /*trace_steps=*/false); } } // namespace Carbon diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index 55252b7ca742..7ffcf99fff6d 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -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; - class Interpreter { public: explicit Interpreter(Nonnull 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 new_top; + Nonnull ast_node; }; - // Transition type which unwinds the entire current stack frame, and returns - // a specified value to the caller. - struct UnwindFunctionCall { - Nonnull 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 ast_node; + std::optional> 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; + using Transition = std::variant; // 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> fs); + auto CurrentScope() -> Scope&; auto CurrentEnv() -> Env; auto GetFromEnv(SourceLocation source_loc, const std::string& name) -> Address; - void DeallocateScope(Nonnull scope); - void DeallocateLocals(Nonnull frame); + void DeallocateScope(Scope& scope); auto CreateTuple(Nonnull act, Nonnull exp) -> Nonnull; @@ -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, Env values, bool trace_steps) + -> Nonnull; + Nonnull arena_; // Globally-defined entities, such as functions, structs, or choices. Env globals_; - Stack> stack_; + Stack> todo_; Heap heap_; - std::optional> program_value_; bool trace_; }; diff --git a/executable_semantics/interpreter/resolve_control_flow.cpp b/executable_semantics/interpreter/resolve_control_flow.cpp new file mode 100644 index 000000000000..2ace13c31f83 --- /dev/null +++ b/executable_semantics/interpreter/resolve_control_flow.cpp @@ -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, + std::optional> function, + std::optional> 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(*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(*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(*statement).set_loop(*loop); + return; + case Statement::Kind::If: { + auto& if_stmt = cast(*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(*statement); + ResolveControlFlow(&seq.statement(), function, loop); + if (seq.next().has_value()) { + ResolveControlFlow(*seq.next(), function, loop); + } + return; + } + case Statement::Kind::Block: { + auto& block = cast(*statement); + if (block.statement().has_value()) { + ResolveControlFlow(*block.statement(), function, loop); + } + return; + } + case Statement::Kind::While: + ResolveControlFlow(&cast(*statement).body(), function, statement); + return; + case Statement::Kind::Match: { + auto& match = cast(*statement); + for (Match::Clause& clause : match.clauses()) { + ResolveControlFlow(&clause.statement(), function, loop); + } + return; + } + case Statement::Kind::Continuation: + ResolveControlFlow(&cast(*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(*declaration); + if (function.body().has_value()) { + ResolveControlFlow(*function.body(), &function, std::nullopt); + } + } +} + +} // namespace Carbon diff --git a/executable_semantics/interpreter/resolve_control_flow.h b/executable_semantics/interpreter/resolve_control_flow.h new file mode 100644 index 000000000000..fd78167a730c --- /dev/null +++ b/executable_semantics/interpreter/resolve_control_flow.h @@ -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_ diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 1e197097c44d..aeae8bda3cd9 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -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 : cast(*this).stack()) { - out << sep << *frame; + for (Nonnull action : + cast(*this).stack()) { + out << sep << *action; } out << "}"; break; diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index c3172400de3d..3f7d04d34b10 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -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 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>*> stack) + explicit ContinuationValue(Nonnull>*> 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>& { return *stack_; } + auto stack() const -> std::vector>& { return *stack_; } private: - Nonnull>*> stack_; + Nonnull>*> stack_; }; // The String type.