From a4aff26821a17a55801f0f568cec6913d02fd7b2 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Wed, 10 Nov 2021 16:35:06 -0800 Subject: [PATCH] Stop allocating Actions on the Arena (#934) This enables us to manage local variables in Carbon using C++ RAII. Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com> --- executable_semantics/interpreter/BUILD | 1 + executable_semantics/interpreter/action.cpp | 20 + executable_semantics/interpreter/action.h | 44 +- executable_semantics/interpreter/heap.cpp | 3 +- .../interpreter/interpreter.cpp | 428 +++++++++--------- .../interpreter/interpreter.h | 20 +- executable_semantics/interpreter/stack.h | 2 +- executable_semantics/interpreter/value.cpp | 45 +- executable_semantics/interpreter/value.h | 46 +- 9 files changed, 354 insertions(+), 255 deletions(-) diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index 3e92b1383a19..a8c0f65a6f82 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -20,6 +20,7 @@ cc_library( ":address", ":dictionary", ":field_path", + ":heap_allocation_interface", ":stack", "//common:ostream", "//executable_semantics/ast:declaration", diff --git a/executable_semantics/interpreter/action.cpp b/executable_semantics/interpreter/action.cpp index 7f6d678345d2..4306c90a030c 100644 --- a/executable_semantics/interpreter/action.cpp +++ b/executable_semantics/interpreter/action.cpp @@ -21,6 +21,26 @@ namespace Carbon { using llvm::cast; +Scope::Scope(Scope&& other) noexcept + : values_(other.values_), + locals_(std::exchange(other.locals_, {})), + heap_(other.heap_) {} + +auto Scope::operator=(Scope&& rhs) noexcept -> Scope& { + values_ = rhs.values_; + locals_ = std::exchange(rhs.locals_, {}); + heap_ = rhs.heap_; + return *this; +} + +Scope::~Scope() { + for (const auto& l : locals_) { + std::optional a = values_.Get(l); + CHECK(a.has_value()); + heap_->Deallocate(*a); + } +} + void Action::Print(llvm::raw_ostream& out) const { switch (kind()) { case Action::Kind::LValAction: diff --git a/executable_semantics/interpreter/action.h b/executable_semantics/interpreter/action.h index e4b6aeb2d90c..d1852b6de2f0 100644 --- a/executable_semantics/interpreter/action.h +++ b/executable_semantics/interpreter/action.h @@ -12,6 +12,7 @@ #include "executable_semantics/ast/pattern.h" #include "executable_semantics/ast/statement.h" #include "executable_semantics/interpreter/dictionary.h" +#include "executable_semantics/interpreter/heap_allocation_interface.h" #include "executable_semantics/interpreter/stack.h" #include "executable_semantics/interpreter/value.h" #include "llvm/Support/Compiler.h" @@ -20,14 +21,41 @@ 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)) {} +// A Scope represents the name lookup environment associated with an Action, +// including any variables that are local to that action. Local variables +// will be deallocated from the Carbon Heap when the Scope is destroyed. +class Scope { + public: + // Constructs a Scope whose name environment is `values`, containing the local + // variables in `locals`. The elements of `locals` must also be keys in + // `values`, and their values must be allocated in `heap`. + Scope(Env values, std::vector locals, + Nonnull heap) + : values_(values), locals_(std::move(locals)), heap_(heap) {} - Env values; - std::vector locals; - bool deallocated = false; + // Equivalent to `Scope(values, {}, heap)`. + Scope(Env values, Nonnull heap) + : Scope(values, std::vector(), heap) {} + + // Moving a Scope transfers ownership of its local variables. + Scope(Scope&&) noexcept; + auto operator=(Scope&&) noexcept -> Scope&; + + ~Scope(); + + // Binds `name` to the value of `allocation` in `heap`, and takes + // ownership of it. + void AddLocal(const std::string& name, AllocationId allocation) { + values_.Set(name, allocation); + locals_.push_back(name); + } + + auto values() const -> Env { return values_; } + + private: + Env values_; + std::vector locals_; + Nonnull heap_; }; class Action { @@ -88,6 +116,8 @@ class Action { return results_; } + virtual ~Action() = default; + protected: // Constructs an Action. `kind` must be the enumerator corresponding to the // most-derived type being constructed. diff --git a/executable_semantics/interpreter/heap.cpp b/executable_semantics/interpreter/heap.cpp index c82d484d755d..8bb50772590a 100644 --- a/executable_semantics/interpreter/heap.cpp +++ b/executable_semantics/interpreter/heap.cpp @@ -46,7 +46,8 @@ void Heap::Deallocate(AllocationId allocation) { if (alive_[allocation.index_]) { alive_[allocation.index_] = false; } else { - FATAL_RUNTIME_ERROR_NO_LINE() << "deallocating an already dead value"; + FATAL_RUNTIME_ERROR_NO_LINE() << "deallocating an already dead value: " + << *values_[allocation.index_]; } } diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 84f465e6cb83..0116ab8cc435 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -43,7 +43,7 @@ void Interpreter::PrintEnv(Env values, llvm::raw_ostream& out) { // auto Interpreter::CurrentScope() -> Scope& { - for (Nonnull action : todo_) { + for (const std::unique_ptr& action : todo_) { if (action->scope().has_value()) { return *action->scope(); } @@ -51,7 +51,7 @@ auto Interpreter::CurrentScope() -> Scope& { FATAL() << "No current scope"; } -auto Interpreter::CurrentEnv() -> Env { return CurrentScope().values; } +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) @@ -66,7 +66,7 @@ auto Interpreter::GetFromEnv(SourceLocation source_loc, const std::string& name) void Interpreter::PrintState(llvm::raw_ostream& out) { out << "{\nstack: "; llvm::ListSeparator sep(" :: "); - for (Nonnull action : todo_) { + for (const std::unique_ptr& action : todo_) { out << sep << *action; } out << "\nheap: " << heap_; @@ -183,30 +183,6 @@ void Interpreter::InitGlobals(llvm::ArrayRef> fs) { } } -auto Interpreter::UnwindTodoTop() -> Nonnull { - Nonnull act = todo_.Pop(); - if (act->scope().has_value()) { - CHECK(!act->scope()->deallocated); - for (const auto& l : act->scope()->locals) { - std::optional a = act->scope()->values.Get(l); - CHECK(a); - heap_.Deallocate(*a); - } - act->scope()->deallocated = true; - } - return act; -} - -auto Interpreter::CreateTuple(Nonnull act, - Nonnull exp) - -> Nonnull { - // { { (v1,...,vn) :: C, E, F} :: S, H} - // -> { { `(v1,...,vn) :: C, E, F} :: S, H} - const auto& tup_lit = cast(*exp); - CHECK(act->results().size() == tup_lit.fields().size()); - return arena_->New(act->results()); -} - auto Interpreter::CreateStruct(const std::vector& fields, const std::vector>& values) -> Nonnull { @@ -380,8 +356,8 @@ void Interpreter::PatternAssignment(Nonnull pat, } auto Interpreter::StepLvalue() -> Transition { - Nonnull act = todo_.Top(); - const Expression& exp = cast(*act).expression(); + Action& act = *todo_.Top(); + const Expression& exp = cast(act).expression(); if (trace_) { llvm::outs() << "--- step lvalue " << exp << " (" << exp.source_loc() << ") --->\n"; @@ -396,51 +372,51 @@ auto Interpreter::StepLvalue() -> Transition { return Done{v}; } case Expression::Kind::FieldAccessExpression: { - if (act->pos() == 0) { + if (act.pos() == 0) { // { {e.f :: C, E, F} :: S, H} // -> { e :: [].f :: C, E, F} :: S, H} - return Spawn{arena_->New( + return Spawn{std::make_unique( &cast(exp).aggregate())}; } else { // { v :: [].f :: C, E, F} :: S, H} // -> { { &v.f :: C, E, F} :: S, H } - Address aggregate = cast(*act->results()[0]).value(); + Address aggregate = cast(*act.results()[0]).value(); Address field = aggregate.SubobjectAddress( cast(exp).field()); return Done{arena_->New(field)}; } } case Expression::Kind::IndexExpression: { - if (act->pos() == 0) { + if (act.pos() == 0) { // { {e[i] :: C, E, F} :: S, H} // -> { e :: [][i] :: C, E, F} :: S, H} - return Spawn{ - arena_->New(&cast(exp).aggregate())}; + return Spawn{std::make_unique( + &cast(exp).aggregate())}; - } else if (act->pos() == 1) { - return Spawn{arena_->New( + } else if (act.pos() == 1) { + return Spawn{std::make_unique( &cast(exp).offset())}; } else { // { v :: [][i] :: C, E, F} :: S, H} // -> { { &v[i] :: C, E, F} :: S, H } - Address aggregate = cast(*act->results()[0]).value(); + Address aggregate = cast(*act.results()[0]).value(); std::string f = - std::to_string(cast(*act->results()[1]).value()); + std::to_string(cast(*act.results()[1]).value()); Address field = aggregate.SubobjectAddress(f); return Done{arena_->New(field)}; } } case Expression::Kind::TupleLiteral: { - if (act->pos() < + if (act.pos() < static_cast(cast(exp).fields().size())) { // { { vk :: (f1=v1,..., fk=[],fk+1=ek+1,...) :: C, E, F} :: S, // H} // -> { { ek+1 :: (f1=v1,..., fk=vk, fk+1=[],...) :: C, E, F} :: S, // H} - return Spawn{arena_->New( - cast(exp).fields()[act->pos()])}; + return Spawn{std::make_unique( + cast(exp).fields()[act.pos()])}; } else { - return Done{CreateTuple(act, &exp)}; + return Done{arena_->New(act.results())}; } } case Expression::Kind::StructLiteral: @@ -530,27 +506,27 @@ auto Interpreter::Convert(Nonnull value, } auto Interpreter::StepExp() -> Transition { - Nonnull act = todo_.Top(); - const Expression& exp = cast(*act).expression(); + Action& act = *todo_.Top(); + const Expression& exp = cast(act).expression(); if (trace_) { llvm::outs() << "--- step exp " << exp << " (" << exp.source_loc() << ") --->\n"; } switch (exp.kind()) { case Expression::Kind::IndexExpression: { - if (act->pos() == 0) { + if (act.pos() == 0) { // { { e[i] :: C, E, F} :: S, H} // -> { { e :: [][i] :: C, E, F} :: S, H} - return Spawn{arena_->New( + return Spawn{std::make_unique( &cast(exp).aggregate())}; - } else if (act->pos() == 1) { - return Spawn{arena_->New( + } else if (act.pos() == 1) { + return Spawn{std::make_unique( &cast(exp).offset())}; } else { // { { v :: [][i] :: C, E, F} :: S, H} // -> { { v_i :: C, E, F} : S, H} - const auto& tuple = cast(*act->results()[0]); - int i = cast(*act->results()[1]).value(); + const auto& tuple = cast(*act.results()[0]); + int i = cast(*act.results()[1]).value(); if (i < 0 || i >= static_cast(tuple.elements().size())) { FATAL_RUNTIME_ERROR_NO_LINE() << "index " << i << " out of range in " << tuple; @@ -559,124 +535,124 @@ auto Interpreter::StepExp() -> Transition { } } case Expression::Kind::TupleLiteral: { - if (act->pos() < + if (act.pos() < static_cast(cast(exp).fields().size())) { // { { vk :: (f1=v1,..., fk=[],fk+1=ek+1,...) :: C, E, F} :: S, // H} // -> { { ek+1 :: (f1=v1,..., fk=vk, fk+1=[],...) :: C, E, F} :: S, // H} - return Spawn{arena_->New( - cast(exp).fields()[act->pos()])}; + return Spawn{std::make_unique( + cast(exp).fields()[act.pos()])}; } else { - return Done{CreateTuple(act, &exp)}; + return Done{arena_->New(act.results())}; } } case Expression::Kind::StructLiteral: { const auto& literal = cast(exp); - if (act->pos() < static_cast(literal.fields().size())) { - return Spawn{arena_->New( - &literal.fields()[act->pos()].expression())}; + if (act.pos() < static_cast(literal.fields().size())) { + return Spawn{std::make_unique( + &literal.fields()[act.pos()].expression())}; } else { - return Done{CreateStruct(literal.fields(), act->results())}; + return Done{CreateStruct(literal.fields(), act.results())}; } } case Expression::Kind::StructTypeLiteral: { const auto& struct_type = cast(exp); - if (act->pos() < static_cast(struct_type.fields().size())) { - return Spawn{arena_->New( - &struct_type.fields()[act->pos()].expression())}; + if (act.pos() < static_cast(struct_type.fields().size())) { + return Spawn{std::make_unique( + &struct_type.fields()[act.pos()].expression())}; } else { std::vector fields; for (size_t i = 0; i < struct_type.fields().size(); ++i) { - fields.push_back({struct_type.fields()[i].name(), act->results()[i]}); + fields.push_back({struct_type.fields()[i].name(), act.results()[i]}); } return Done{arena_->New(std::move(fields))}; } } case Expression::Kind::FieldAccessExpression: { const auto& access = cast(exp); - if (act->pos() == 0) { + if (act.pos() == 0) { // { { e.f :: C, E, F} :: S, H} // -> { { e :: [].f :: C, E, F} :: S, H} - return Spawn{arena_->New(&access.aggregate())}; + return Spawn{std::make_unique(&access.aggregate())}; } else { // { { v :: [].f :: C, E, F} :: S, H} // -> { { v_f :: C, E, F} : S, H} - return Done{act->results()[0]->GetField( + return Done{act.results()[0]->GetField( arena_, FieldPath(access.field()), exp.source_loc())}; } } case Expression::Kind::IdentifierExpression: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); const auto& ident = cast(exp); // { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H} Address pointer = GetFromEnv(exp.source_loc(), ident.name()); return Done{heap_.Read(pointer, exp.source_loc())}; } case Expression::Kind::IntLiteral: - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} return Done{arena_->New(cast(exp).value())}; case Expression::Kind::BoolLiteral: - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} return Done{arena_->New(cast(exp).value())}; case Expression::Kind::PrimitiveOperatorExpression: { const auto& op = cast(exp); - if (act->pos() != static_cast(op.arguments().size())) { + if (act.pos() != static_cast(op.arguments().size())) { // { {v :: op(vs,[],e,es) :: C, E, F} :: S, H} // -> { {e :: op(vs,v,[],es) :: C, E, F} :: S, H} - Nonnull arg = op.arguments()[act->pos()]; - return Spawn{arena_->New(arg)}; + Nonnull arg = op.arguments()[act.pos()]; + return Spawn{std::make_unique(arg)}; } else { // { {v :: op(vs,[]) :: C, E, F} :: S, H} // -> { {eval_prim(op, (vs,v)) :: C, E, F} :: S, H} - return Done{EvalPrim(op.op(), act->results(), exp.source_loc())}; + return Done{EvalPrim(op.op(), act.results(), exp.source_loc())}; } } case Expression::Kind::CallExpression: - if (act->pos() == 0) { + if (act.pos() == 0) { // { {e1(e2) :: C, E, F} :: S, H} // -> { {e1 :: [](e2) :: C, E, F} :: S, H} - return Spawn{arena_->New( + return Spawn{std::make_unique( &cast(exp).function())}; - } else if (act->pos() == 1) { + } else if (act.pos() == 1) { // { { v :: [](e) :: C, E, F} :: S, H} // -> { { e :: v([]) :: C, E, F} :: S, H} - return Spawn{arena_->New( + return Spawn{std::make_unique( &cast(exp).argument())}; - } else if (act->pos() == 2) { + } else if (act.pos() == 2) { // { { v2 :: v1([]) :: C, E, F} :: S, H} // -> { {C',E',F'} :: {C, E, F} :: S, H} - switch (act->results()[0]->kind()) { + switch (act.results()[0]->kind()) { case Value::Kind::AlternativeConstructorValue: { const auto& alt = - cast(*act->results()[0]); + cast(*act.results()[0]); return Done{arena_->New( - alt.alt_name(), alt.choice_name(), act->results()[1])}; + alt.alt_name(), alt.choice_name(), act.results()[1])}; } case Value::Kind::FunctionValue: return CallFunction{ .function = - &cast(*act->results()[0]).declaration(), - .args = act->results()[1], + &cast(*act.results()[0]).declaration(), + .args = act.results()[1], .source_loc = exp.source_loc()}; default: FATAL_RUNTIME_ERROR(exp.source_loc()) - << "in call, expected a function, not " << *act->results()[0]; + << "in call, expected a function, not " << *act.results()[0]; } - } else if (act->pos() == 3) { - if (act->results().size() < 3) { + } 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]}; + return Done{act.results()[2]}; } } else { - FATAL() << "in handle_value with Call pos " << act->pos(); + FATAL() << "in handle_value with Call pos " << act.pos(); } case Expression::Kind::IntrinsicExpression: - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} switch (cast(exp).intrinsic()) { case IntrinsicExpression::Intrinsic::Print: @@ -689,110 +665,112 @@ auto Interpreter::StepExp() -> Transition { } case Expression::Kind::IntTypeLiteral: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); return Done{arena_->New()}; } case Expression::Kind::BoolTypeLiteral: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); return Done{arena_->New()}; } case Expression::Kind::TypeTypeLiteral: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); return Done{arena_->New()}; } case Expression::Kind::FunctionTypeLiteral: { - if (act->pos() == 0) { - return Spawn{arena_->New( + if (act.pos() == 0) { + return Spawn{std::make_unique( &cast(exp).parameter())}; - } else if (act->pos() == 1) { + } else if (act.pos() == 1) { // { { pt :: fn [] -> e :: C, E, F} :: S, H} // -> { { e :: fn pt -> []) :: C, E, F} :: S, H} - return Spawn{arena_->New( + return Spawn{std::make_unique( &cast(exp).return_type())}; } else { // { { rt :: fn pt -> [] :: C, E, F} :: S, H} // -> { fn pt -> rt :: {C, E, F} :: S, H} return Done{arena_->New( - std::vector>(), act->results()[0], - act->results()[1])}; + std::vector>(), act.results()[0], + act.results()[1])}; } } case Expression::Kind::ContinuationTypeLiteral: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); return Done{arena_->New()}; } case Expression::Kind::StringLiteral: - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} return Done{arena_->New(cast(exp).value())}; case Expression::Kind::StringTypeLiteral: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); return Done{arena_->New()}; } } // switch (exp->kind) } auto Interpreter::StepPattern() -> Transition { - Nonnull act = todo_.Top(); - const Pattern& pattern = cast(*act).pattern(); + Action& act = *todo_.Top(); + const Pattern& pattern = cast(act).pattern(); if (trace_) { llvm::outs() << "--- step pattern " << pattern << " (" << pattern.source_loc() << ") --->\n"; } switch (pattern.kind()) { case Pattern::Kind::AutoPattern: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); return Done{arena_->New()}; } case Pattern::Kind::BindingPattern: { const auto& binding = cast(pattern); - if (act->pos() == 0) { - return Spawn{arena_->New(&binding.type())}; + if (act.pos() == 0) { + return Spawn{std::make_unique(&binding.type())}; } else { return Done{arena_->New(binding.name(), - act->results()[0])}; + act.results()[0])}; } } case Pattern::Kind::TuplePattern: { const auto& tuple = cast(pattern); - if (act->pos() < static_cast(tuple.fields().size())) { + if (act.pos() < static_cast(tuple.fields().size())) { // { { vk :: (f1=v1,..., fk=[],fk+1=ek+1,...) :: C, E, F} :: S, // H} // -> { { ek+1 :: (f1=v1,..., fk=vk, fk+1=[],...) :: C, E, F} :: S, // H} - return Spawn{arena_->New(tuple.fields()[act->pos()])}; + return Spawn{ + std::make_unique(tuple.fields()[act.pos()])}; } else { - return Done{arena_->New(act->results())}; + return Done{arena_->New(act.results())}; } } case Pattern::Kind::AlternativePattern: { const auto& alternative = cast(pattern); - if (act->pos() == 0) { - return Spawn{arena_->New(&alternative.choice_type())}; - } else if (act->pos() == 1) { - return Spawn{arena_->New(&alternative.arguments())}; + if (act.pos() == 0) { + return Spawn{ + std::make_unique(&alternative.choice_type())}; + } else if (act.pos() == 1) { + return Spawn{std::make_unique(&alternative.arguments())}; } else { - CHECK(act->pos() == 2); - const auto& choice_type = cast(*act->results()[0]); + CHECK(act.pos() == 2); + const auto& choice_type = cast(*act.results()[0]); return Done{arena_->New( alternative.alternative_name(), choice_type.name(), - act->results()[1])}; + act.results()[1])}; } } case Pattern::Kind::ExpressionPattern: - return Delegate{arena_->New( + return Delegate{std::make_unique( &cast(pattern).expression())}; } } -static auto IsRunAction(Nonnull action) -> bool { - const auto* statement = dyn_cast(action); +static auto IsRunAction(const Action& action) -> bool { + const auto* statement = dyn_cast(&action); return statement != nullptr && llvm::isa(statement->statement()); } auto Interpreter::StepStmt() -> Transition { - Nonnull act = todo_.Top(); - const Statement& stmt = cast(*act).statement(); + Action& act = *todo_.Top(); + const Statement& stmt = cast(act).statement(); if (trace_) { llvm::outs() << "--- step stmt "; stmt.PrintDepth(1, llvm::outs()); @@ -801,49 +779,50 @@ auto Interpreter::StepStmt() -> Transition { switch (stmt.kind()) { case Statement::Kind::Match: { const auto& match_stmt = cast(stmt); - if (act->pos() == 0) { + if (act.pos() == 0) { // { { (match (e) ...) :: C, E, F} :: S, H} // -> { { e :: (match ([]) ...) :: C, E, F} :: S, H} - act->StartScope(Scope(CurrentEnv())); - return Spawn{arena_->New(&match_stmt.expression())}; + act.StartScope(Scope(CurrentEnv(), &heap_)); + return Spawn{ + std::make_unique(&match_stmt.expression())}; } else { - int clause_num = act->pos() - 1; + int clause_num = act.pos() - 1; if (clause_num >= static_cast(match_stmt.clauses().size())) { return Done{}; } auto c = match_stmt.clauses()[clause_num]; std::optional matches = PatternMatch(&c.pattern().value(), - Convert(act->results()[0], &c.pattern().static_type()), + Convert(act.results()[0], &c.pattern().static_type()), stmt.source_loc()); if (matches) { // We have a match, start the body. // Ensure we don't process any more clauses. - act->set_pos(match_stmt.clauses().size() + 1); + act.set_pos(match_stmt.clauses().size() + 1); for (const auto& [name, value] : *matches) { - act->scope()->values.Set(name, value); - act->scope()->locals.push_back(name); + act.scope()->AddLocal(name, value); } - return Spawn{arena_->New(&c.statement())}; + return Spawn{std::make_unique(&c.statement())}; } else { return RunAgain{}; } } } case Statement::Kind::While: - if (act->pos() % 2 == 0) { + if (act.pos() % 2 == 0) { // { { (while (e) s) :: C, E, F} :: S, H} // -> { { e :: (while ([]) s) :: C, E, F} :: S, H} - act->Clear(); + act.Clear(); return Spawn{ - arena_->New(&cast(stmt).condition())}; + std::make_unique(&cast(stmt).condition())}; } else { Nonnull condition = - Convert(act->results().back(), arena_->New()); + Convert(act.results().back(), arena_->New()); if (cast(*condition).value()) { // { {true :: (while ([]) s) :: C, E, F} :: S, H} // -> { { s :: (while (e) s) :: C, E, F } :: S, H} - return Spawn{arena_->New(&cast(stmt).body())}; + return Spawn{ + std::make_unique(&cast(stmt).body())}; } else { // { {false :: (while ([]) s) :: C, E, F} :: S, H} // -> { { C, E, F } :: S, H} @@ -851,44 +830,44 @@ auto Interpreter::StepStmt() -> Transition { } } case Statement::Kind::Break: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // { { break; :: ... :: (while (e) s) :: C, E, F} :: S, H} // -> { { C, E', F} :: S, H} return UnwindPast{.ast_node = &cast(stmt).loop()}; } case Statement::Kind::Continue: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // { { continue; :: ... :: (while (e) s) :: C, E, F} :: S, H} // -> { { (while (e) s) :: C, E', F} :: S, H} return UnwindTo{.ast_node = &cast(stmt).loop()}; } case Statement::Kind::Block: { const auto& block = cast(stmt); - if (act->pos() >= static_cast(block.statements().size())) { + if (act.pos() >= static_cast(block.statements().size())) { // If the position is past the end of the block, end processing. Note // that empty blocks immediately end. return Done{}; } // Initialize a scope when starting a block. - if (act->pos() == 0) { - act->StartScope(Scope(CurrentEnv())); + if (act.pos() == 0) { + act.StartScope(Scope(CurrentEnv(), &heap_)); } // Process the next statement in the block. The position will be // incremented as part of Spawn. return Spawn{ - arena_->New(block.statements()[act->pos()])}; + std::make_unique(block.statements()[act.pos()])}; } case Statement::Kind::VariableDefinition: { const auto& definition = cast(stmt); - if (act->pos() == 0) { + if (act.pos() == 0) { // { {(var x = e) :: C, E, F} :: S, H} // -> { {e :: (var x = []) :: C, E, F} :: S, H} - return Spawn{arena_->New(&definition.init())}; + return Spawn{std::make_unique(&definition.init())}; } else { // { { v :: (x = []) :: C, E, F} :: S, H} // -> { { C, E(x := a), F} :: S, H(a := copy(v))} Nonnull v = - Convert(act->results()[0], &definition.pattern().static_type()); + Convert(act.results()[0], &definition.pattern().static_type()); Nonnull p = &cast(stmt).pattern().value(); @@ -898,71 +877,70 @@ auto Interpreter::StepStmt() -> Transition { << ": internal error in variable definition, match failed"; for (const auto& [name, value] : *matches) { Scope& current_scope = CurrentScope(); - current_scope.values.Set(name, value); - current_scope.locals.push_back(name); + current_scope.AddLocal(name, value); } return Done{}; } } case Statement::Kind::ExpressionStatement: - if (act->pos() == 0) { + if (act.pos() == 0) { // { {e :: C, E, F} :: S, H} // -> { {e :: C, E, F} :: S, H} - return Spawn{arena_->New( + return Spawn{std::make_unique( &cast(stmt).expression())}; } else { return Done{}; } case Statement::Kind::Assign: { const auto& assign = cast(stmt); - if (act->pos() == 0) { + if (act.pos() == 0) { // { {(lv = e) :: C, E, F} :: S, H} // -> { {lv :: ([] = e) :: C, E, F} :: S, H} - return Spawn{arena_->New(&assign.lhs())}; - } else if (act->pos() == 1) { + return Spawn{std::make_unique(&assign.lhs())}; + } else if (act.pos() == 1) { // { { a :: ([] = e) :: C, E, F} :: S, H} // -> { { e :: (a = []) :: C, E, F} :: S, H} - return Spawn{arena_->New(&assign.rhs())}; + return Spawn{std::make_unique(&assign.rhs())}; } else { // { { v :: (a = []) :: C, E, F} :: S, H} // -> { { C, E, F} :: S, H(a := v)} - auto pat = act->results()[0]; - auto val = Convert(act->results()[1], &assign.lhs().static_type()); + auto pat = act.results()[0]; + auto val = Convert(act.results()[1], &assign.lhs().static_type()); PatternAssignment(pat, val, stmt.source_loc()); return Done{}; } } case Statement::Kind::If: - if (act->pos() == 0) { + if (act.pos() == 0) { // { {(if (e) then_stmt else else_stmt) :: C, E, F} :: S, H} // -> { { e :: (if ([]) then_stmt else else_stmt) :: C, E, F} :: S, H} return Spawn{ - arena_->New(&cast(stmt).condition())}; + std::make_unique(&cast(stmt).condition())}; } else { Nonnull condition = - Convert(act->results()[0], arena_->New()); + Convert(act.results()[0], arena_->New()); if (cast(*condition).value()) { // { {true :: if ([]) then_stmt else else_stmt :: C, E, F} :: // S, H} // -> { { then_stmt :: C, E, F } :: S, H} return Delegate{ - arena_->New(&cast(stmt).then_block())}; + std::make_unique(&cast(stmt).then_block())}; } else if (cast(stmt).else_block()) { // { {false :: if ([]) then_stmt else else_stmt :: C, E, F} :: // S, H} // -> { { else_stmt :: C, E, F } :: S, H} return Delegate{ - arena_->New(*cast(stmt).else_block())}; + std::make_unique(*cast(stmt).else_block())}; } else { return Done{}; } } case Statement::Kind::Return: - if (act->pos() == 0) { + if (act.pos() == 0) { // { {return e :: C, E, F} :: S, H} // -> { {e :: return [] :: C, E, F} :: S, H} - return Spawn{ - arena_->New(&cast(stmt).expression())}; + return Spawn{std::make_unique( + &cast(stmt).expression())}; } else { // { {v :: return [] :: C, E, F} :: {C', E', F'} :: S, H} // -> { {v :: C', E', F'} :: S, H} @@ -970,57 +948,55 @@ auto Interpreter::StepStmt() -> Transition { // once #880 gives us a way to find that type. const FunctionDeclaration& function = cast(stmt).function(); return UnwindPast{.ast_node = *function.body(), - .result = act->results()[0]}; + .result = act.results()[0]}; } case Statement::Kind::Continuation: { - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // Create a continuation object by creating a frame similar the // way one is created in a function call. - auto continuation_stack = arena_->New>>(); - continuation_stack->push_back( - arena_->New(&cast(stmt).body())); - continuation_stack->push_back( - arena_->New(Scope(CurrentEnv()))); - AllocationId continuation_address = heap_.AllocateValue( - arena_->New(continuation_stack)); + auto fragment = arena_->New(); + stack_fragments_.push_back(fragment); + std::vector> reversed_todo; + reversed_todo.push_back( + std::make_unique(&cast(stmt).body())); + reversed_todo.push_back( + std::make_unique(Scope(CurrentEnv(), &heap_))); + fragment->StoreReversed(std::move(reversed_todo)); + AllocationId continuation_address = + heap_.AllocateValue(arena_->New(fragment)); // Bind the continuation object to the continuation variable - CurrentScope().values.Set( - cast(stmt).continuation_variable(), - continuation_address); + CurrentScope().AddLocal(cast(stmt).continuation_variable(), + continuation_address); return Done{}; } case Statement::Kind::Run: { auto& run = cast(stmt); - if (act->pos() == 0) { + if (act.pos() == 0) { // Evaluate the argument of the run statement. - return Spawn{arena_->New(&run.argument())}; - } else if (act->pos() == 1) { + return Spawn{std::make_unique(&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()) { - todo_.Push(continuation_vector.back()); - continuation_vector.pop_back(); - } - act->set_pos(2); + cast(*act.results()[0]) + .stack() + .RestoreTo(todo_); + act.set_pos(2); return ManualTransition{}; } else { return Done{}; } } case Statement::Kind::Await: - CHECK(act->pos() == 0); + CHECK(act.pos() == 0); // Pause the current continuation todo_.Pop(); - std::vector> paused; - while (!IsRunAction(todo_.Top())) { + 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); + continuation.stack().StoreReversed(std::move(paused)); return ManualTransition{}; } } @@ -1031,7 +1007,7 @@ class Interpreter::DoTransition { explicit DoTransition(Interpreter* interpreter) : interpreter(interpreter) {} void operator()(const Done& done) { - Nonnull act = interpreter->UnwindTodoTop(); + std::unique_ptr act = interpreter->todo_.Pop(); switch (act->kind()) { case Action::Kind::ExpressionAction: case Action::Kind::LValAction: @@ -1050,23 +1026,23 @@ class Interpreter::DoTransition { } } - void operator()(const Spawn& spawn) { - Nonnull action = interpreter->todo_.Top(); - action->set_pos(action->pos() + 1); - interpreter->todo_.Push(spawn.child); + void operator()(Spawn spawn) { + Action& action = *interpreter->todo_.Top(); + action.set_pos(action.pos() + 1); + interpreter->todo_.Push(std::move(spawn.child)); } - void operator()(const Delegate& delegate) { - Nonnull act = interpreter->todo_.Pop(); + void operator()(Delegate delegate) { + std::unique_ptr act = interpreter->todo_.Pop(); if (act->scope().has_value()) { - delegate.delegate->StartScope(*act->scope()); + delegate.delegate->StartScope(std::move(*act->scope())); } - interpreter->todo_.Push(delegate.delegate); + interpreter->todo_.Push(std::move(delegate.delegate)); } void operator()(const RunAgain&) { - Nonnull action = interpreter->todo_.Top(); - action->set_pos(action->pos() + 1); + Action& action = *interpreter->todo_.Top(); + action.set_pos(action.pos() + 1); } void operator()(const UnwindTo& unwind_to) { DoUnwindTo(unwind_to.ast_node); } @@ -1074,15 +1050,15 @@ class Interpreter::DoTransition { void operator()(const UnwindPast& unwind_past) { DoUnwindTo(unwind_past.ast_node); // Unwind past the statement and return a result if needed. - interpreter->UnwindTodoTop(); + interpreter->todo_.Pop(); if (unwind_past.result.has_value()) { interpreter->todo_.Top()->AddResult(*unwind_past.result); } } void operator()(const CallFunction& call) { - Nonnull action = interpreter->todo_.Top(); - action->set_pos(action->pos() + 1); + Action& 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 = @@ -1091,16 +1067,15 @@ 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 - Scope new_scope(interpreter->globals_); + Scope new_scope(interpreter->globals_, &interpreter->heap_); for (const auto& [name, value] : *matches) { - new_scope.values.Set(name, value); - new_scope.locals.push_back(name); + new_scope.AddLocal(name, value); } interpreter->todo_.Push( - interpreter->arena_->New(std::move(new_scope))); + std::make_unique(std::move(new_scope))); CHECK(call.function->body()) << "Calling a function that's missing a body"; interpreter->todo_.Push( - interpreter->arena_->New(*call.function->body())); + std::make_unique(*call.function->body())); } void operator()(const ManualTransition&) {} @@ -1110,12 +1085,12 @@ class Interpreter::DoTransition { void DoUnwindTo(Nonnull ast_node) { while (true) { if (const auto* statement_action = - dyn_cast(interpreter->todo_.Top()); + dyn_cast(interpreter->todo_.Top().get()); statement_action != nullptr && &statement_action->statement() == ast_node) { break; } - interpreter->UnwindTodoTop(); + interpreter->todo_.Pop(); } } @@ -1124,8 +1099,8 @@ class Interpreter::DoTransition { // State transition. void Interpreter::Step() { - Nonnull act = todo_.Top(); - switch (act->kind()) { + Action& act = *todo_.Top(); + switch (act.kind()) { case Action::Kind::LValAction: std::visit(DoTransition(this), StepLvalue()); break; @@ -1139,20 +1114,20 @@ void Interpreter::Step() { std::visit(DoTransition(this), StepStmt()); break; case Action::Kind::ScopeAction: - if (act->results().empty()) { + 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]}}); + CHECK(act.results().size() == 1); + std::visit(DoTransition(this), Transition{Done{act.results()[0]}}); } } // switch } -auto Interpreter::ExecuteAction(Nonnull action, Env values, +auto Interpreter::ExecuteAction(std::unique_ptr action, Env values, bool trace_steps) -> Nonnull { todo_ = {}; - todo_.Push(arena_->New(Scope(values))); - todo_.Push(action); + todo_.Push(std::make_unique(Scope(values, &heap_))); + todo_.Push(std::move(action)); while (todo_.Count() > 1) { Step(); @@ -1160,6 +1135,12 @@ auto Interpreter::ExecuteAction(Nonnull action, Env values, PrintState(llvm::outs()); } } + + // Clean up any remaining suspended continuations. + for (Nonnull fragment : stack_fragments_) { + fragment->Clear(); + } + CHECK(todo_.Top()->results().size() == 1); return todo_.Top()->results()[0]; } @@ -1180,20 +1161,21 @@ auto Interpreter::InterpProgram(llvm::ArrayRef> fs, PrintState(llvm::outs()); } - return cast(*ExecuteAction(arena_->New(call_main), - globals_, trace_)) + return cast( + *ExecuteAction(std::make_unique(call_main), + globals_, trace_)) .value(); } auto Interpreter::InterpExp(Env values, Nonnull e) -> Nonnull { - return ExecuteAction(arena_->New(e), values, + return ExecuteAction(std::make_unique(e), values, /*trace_steps=*/false); } auto Interpreter::InterpPattern(Env values, Nonnull p) -> Nonnull { - return ExecuteAction(arena_->New(p), values, + return ExecuteAction(std::make_unique(p), values, /*trace_steps=*/false); } diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index 23614ff9802b..db9b4f502b6c 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -70,13 +70,13 @@ class Interpreter { // Transition type which spawns a new Action on the todo stack above the // current Action, and increments the current Action's position counter. struct Spawn { - Nonnull child; + std::unique_ptr child; }; // Transition type which spawns a new Action that replaces the current action // on the todo stack. struct Delegate { - Nonnull delegate; + std::unique_ptr delegate; }; // Transition type which keeps the current Action at the top of the stack, @@ -137,10 +137,6 @@ class Interpreter { auto GetFromEnv(SourceLocation source_loc, const std::string& name) -> Address; - auto UnwindTodoTop() -> Nonnull; - - auto CreateTuple(Nonnull act, Nonnull exp) - -> Nonnull; auto CreateStruct(const std::vector& fields, const std::vector>& values) -> Nonnull; @@ -164,17 +160,23 @@ class Interpreter { // // TODO: consider whether to use this->trace_ rather than a separate // trace_steps parameter. - auto ExecuteAction(Nonnull action, Env values, bool trace_steps) - -> Nonnull; + auto ExecuteAction(std::unique_ptr action, Env values, + bool trace_steps) -> Nonnull; Nonnull arena_; // Globally-defined entities, such as functions, structs, or choices. Env globals_; - Stack> todo_; + // TODO: consider defining a non-nullable unique_ptr-like type to use here. + Stack> todo_; Heap heap_; + // The underlying states of continuation values. All StackFragments created + // during execution are tracked here, in order to safely deallocate the + // contents of any non-completed continuations at the end of execution. + std::vector> stack_fragments_; + bool trace_; }; diff --git a/executable_semantics/interpreter/stack.h b/executable_semantics/interpreter/stack.h index 9fe02067bb46..d69cd2a368ad 100644 --- a/executable_semantics/interpreter/stack.h +++ b/executable_semantics/interpreter/stack.h @@ -51,7 +51,7 @@ struct Stack { // Returns the top element of the stack. // // - Requires: !this->IsEmpty() - auto Top() const -> T { + auto Top() const -> const T& { CHECK(!IsEmpty()) << "Empty stack has no Top()."; return elements_.back(); } diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 5bbf3922e408..86d4539ad576 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -244,13 +244,7 @@ void Value::Print(llvm::raw_ostream& out) const { out << cast(*this).name(); break; case Value::Kind::ContinuationValue: { - out << "{"; - llvm::ListSeparator sep(" :: "); - for (Nonnull action : - cast(*this).stack()) { - out << sep << *action; - } - out << "}"; + out << cast(*this).stack(); break; } case Value::Kind::StringType: @@ -264,6 +258,43 @@ void Value::Print(llvm::raw_ostream& out) const { } } +ContinuationValue::StackFragment::~StackFragment() { + CHECK(reversed_todo_.empty()) + << "All StackFragments must be empty before the Carbon program ends."; +} + +void ContinuationValue::StackFragment::StoreReversed( + std::vector> reversed_todo) { + CHECK(reversed_todo_.empty()); + reversed_todo_ = std::move(reversed_todo); +} + +void ContinuationValue::StackFragment::RestoreTo( + Stack>& todo) { + while (!reversed_todo_.empty()) { + todo.Push(std::move(reversed_todo_.back())); + reversed_todo_.pop_back(); + } +} + +void ContinuationValue::StackFragment::Clear() { + // We destroy the underlying Actions explicitly to ensure they're + // destroyed in the correct order. + for (auto& action : reversed_todo_) { + action.reset(); + } + reversed_todo_.clear(); +} + +void ContinuationValue::StackFragment::Print(llvm::raw_ostream& out) const { + out << "{"; + llvm::ListSeparator sep(" :: "); + for (const std::unique_ptr& action : reversed_todo_) { + out << sep << *action; + } + out << "}"; +} + auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { if (t1->kind() != t2->kind()) { return false; diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index d30b3b5d5cfa..8eaa9a192c6b 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -485,21 +485,53 @@ class VariableType : public Value { // fragment, which is exposed by `Stack()`. class ContinuationValue : public Value { public: - explicit ContinuationValue(Nonnull>*> stack) + class StackFragment { + public: + // Constructs an empty StackFragment. + StackFragment() = default; + + // Requires *this to be empty, because by the time we're tearing down the + // Arena, it's no longer safe to invoke ~Action. + ~StackFragment(); + + StackFragment(StackFragment&&) = delete; + StackFragment& operator=(StackFragment&&) = delete; + + // Store the given partial todo stack in *this, which must currently be + // empty. The stack is represented with the top of the stack at the + // beginning of the vector, the reverse of the usual order. + void StoreReversed(std::vector> reversed_todo); + + // Restore the currently stored stack fragment to the top of `todo`, + // leaving *this empty. + void RestoreTo(Stack>& todo); + + // Destroy the currently stored stack fragment. + void Clear(); + + void Print(llvm::raw_ostream& out) const; + LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + + private: + // The todo stack of a suspended continuation, starting with the top + // Action. + std::vector> reversed_todo_; + }; + + explicit ContinuationValue(Nonnull stack) : Value(Kind::ContinuationValue), stack_(stack) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::ContinuationValue; } - // 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_; } + // The todo stack of the suspended continuation. Note that this provides + // mutable access, even when *this is const, because of the reference-like + // semantics of ContinuationValue. + auto stack() const -> StackFragment& { return *stack_; } private: - Nonnull>*> stack_; + Nonnull stack_; }; // The String type.