diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index bc63f923d649..60ec449c4c99 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -133,11 +133,6 @@ class IdentifierExpression : public Expression { named_entity_ = std::move(named_entity); } - // Returns true if set_named_entity has been called. Should be used only - // for debugging purposes. - // TODO: remove this once we no longer need the CHECKs that use it. - auto has_named_entity() const -> bool { return named_entity_.has_value(); } - private: std::string name_; std::optional named_entity_; diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index d74345734c06..316ad38e3a89 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -110,6 +110,7 @@ cc_library( hdrs = ["heap_allocation_interface.h"], deps = [ ":address", + "//executable_semantics/common:arena", "//executable_semantics/common:nonnull", ], ) diff --git a/executable_semantics/interpreter/action.cpp b/executable_semantics/interpreter/action.cpp index ed5129137414..a341d1f6b8c2 100644 --- a/executable_semantics/interpreter/action.cpp +++ b/executable_semantics/interpreter/action.cpp @@ -21,26 +21,80 @@ namespace Carbon { using llvm::cast; -Scope::Scope(Scope&& other) noexcept - : values_(other.values_), - locals_(std::exchange(other.locals_, {})), +RuntimeScope::RuntimeScope(RuntimeScope&& other) noexcept + : locals_(std::move(other.locals_)), + // To transfer ownership of other.allocations_, we have to empty it out. + allocations_(std::exchange(other.allocations_, {})), heap_(other.heap_) {} -auto Scope::operator=(Scope&& rhs) noexcept -> Scope& { - values_ = rhs.values_; - locals_ = std::exchange(rhs.locals_, {}); +auto RuntimeScope::operator=(RuntimeScope&& rhs) noexcept -> RuntimeScope& { + locals_ = std::move(rhs.locals_); + // To transfer ownership of rhs.allocations_, we have to empty it out. + allocations_ = std::exchange(rhs.allocations_, {}); 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); +RuntimeScope::~RuntimeScope() { + for (AllocationId allocation : allocations_) { + heap_->Deallocate(allocation); } } +void RuntimeScope::Print(llvm::raw_ostream& out) const { + out << "{"; + llvm::ListSeparator sep; + for (const auto& [named_entity, value] : locals_) { + out << sep << named_entity.name() << ": " << *value; + } + out << "}"; +} + +void RuntimeScope::Initialize(NamedEntityView named_entity, + Nonnull value) { + CHECK(!named_entity.constant_value().has_value()); + CHECK(value->kind() != Value::Kind::LValue); + allocations_.push_back(heap_->AllocateValue(value)); + auto [it, success] = locals_.insert( + {named_entity, heap_->arena().New(Address(allocations_.back()))}); + CHECK(success) << "Duplicate definition of " << named_entity.name(); +} + +void RuntimeScope::Merge(RuntimeScope other) { + CHECK(heap_ == other.heap_); + locals_.merge(other.locals_); + CHECK(other.locals_.empty()) + << "Duplicate definition of " << other.locals_.size() + << " names, including " << other.locals_.begin()->first.name(); + allocations_.insert(allocations_.end(), other.allocations_.begin(), + other.allocations_.end()); + other.allocations_.clear(); +} + +auto RuntimeScope::Get(NamedEntityView named_entity) const + -> std::optional> { + auto it = locals_.find(named_entity); + if (it != locals_.end()) { + return it->second; + } else { + return std::nullopt; + } +} + +auto RuntimeScope::Capture( + const std::vector>& scopes) -> RuntimeScope { + CHECK(!scopes.empty()); + RuntimeScope result(scopes.front()->heap_); + for (Nonnull scope : scopes) { + CHECK(scope->heap_ == result.heap_); + for (const auto& entry : scope->locals_) { + // Intentionally disregards duplicates later in the vector. + result.locals_.insert(entry); + } + } + return result; +} + 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 30b4239f398f..abfacd48065c 100644 --- a/executable_semantics/interpreter/action.h +++ b/executable_semantics/interpreter/action.h @@ -5,6 +5,7 @@ #ifndef EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_ #define EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_ +#include #include #include "common/ostream.h" @@ -19,42 +20,47 @@ namespace Carbon { -using Env = Dictionary; - -// 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 { +// A RuntimeScope manages and provides access to the storage for names that are +// not compile-time constants. +class RuntimeScope { 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) {} + // Returns a RuntimeScope whose Get() operation for a given name returns the + // storage owned by the first entry in `scopes` that defines that name. This + // behavior is closely analogous to a `[&]` capture in C++, hence the name. + // `scopes` must contain at least one entry, and all entries must be backed + // by the same Heap. + static auto Capture(const std::vector>& scopes) + -> RuntimeScope; - // Equivalent to `Scope(values, {}, heap)`. - Scope(Env values, Nonnull heap) - : Scope(values, std::vector(), heap) {} + // Constructs a RuntimeScope that allocates storage in `heap`. + explicit RuntimeScope(Nonnull heap) : heap_(heap) {} - // Moving a Scope transfers ownership of its local variables. - Scope(Scope&&) noexcept; - auto operator=(Scope&&) noexcept -> Scope&; + // Moving a RuntimeScope transfers ownership of its allocations. + RuntimeScope(RuntimeScope&&) noexcept; + auto operator=(RuntimeScope&&) noexcept -> RuntimeScope&; - ~Scope(); + // Deallocates any allocations in this scope from `heap`. + ~RuntimeScope(); - // 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); - } + void Print(llvm::raw_ostream& out) const; + LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } - auto values() const -> Env { return values_; } + // Allocates storage for `named_entity` in `heap`, and initializes it with + // `value`. + void Initialize(NamedEntityView named_entity, Nonnull value); + + // Transfers the names and allocations from `other` into *this. The two + // scopes must not define the same name, and must be backed by the same Heap. + void Merge(RuntimeScope other); + + // Returns the local storage for named_entity, if it has storage local to + // this scope. + auto Get(NamedEntityView named_entity) const + -> std::optional>; private: - Env values_; - std::vector locals_; + std::map> locals_; + std::vector allocations_; Nonnull heap_; }; @@ -114,13 +120,13 @@ class Action { void AddResult(Nonnull result) { results_.push_back(result); } // Returns the scope associated with this Action, if any. - auto scope() -> std::optional& { return scope_; } + auto scope() -> std::optional& { return scope_; } // 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) { + void StartScope(RuntimeScope scope) { CHECK(!scope_.has_value()); scope_ = std::move(scope); } @@ -133,7 +139,7 @@ class Action { private: int pos_ = 0; std::vector> results_; - std::optional scope_; + std::optional scope_; const Kind kind_; }; @@ -234,7 +240,7 @@ class DeclarationAction : public Action { // with AST nodes. class ScopeAction : public Action { public: - explicit ScopeAction(Scope scope) : Action(Kind::ScopeAction) { + explicit ScopeAction(RuntimeScope scope) : Action(Kind::ScopeAction) { StartScope(std::move(scope)); } diff --git a/executable_semantics/interpreter/action_stack.cpp b/executable_semantics/interpreter/action_stack.cpp index 459a5d4e1463..5b4ed82926ec 100644 --- a/executable_semantics/interpreter/action_stack.cpp +++ b/executable_semantics/interpreter/action_stack.cpp @@ -17,20 +17,97 @@ void ActionStack::Print(llvm::raw_ostream& out) const { } } +void ActionStack::PrintScopes(llvm::raw_ostream& out) const { + llvm::ListSeparator sep(" :: "); + for (const std::unique_ptr& action : todo_) { + if (action->scope().has_value()) { + out << sep << *action->scope(); + } + } + if (globals_.has_value()) { + out << sep << *globals_; + } + // TODO: should we print constants as well? +} + void ActionStack::Start(std::unique_ptr action) { result_ = std::nullopt; CHECK(todo_.IsEmpty()); - todo_ = {}; todo_.Push(std::move(action)); } -auto ActionStack::CurrentScope() const -> Scope& { +void ActionStack::Initialize(NamedEntityView named_entity, + Nonnull value) { for (const std::unique_ptr& action : todo_) { if (action->scope().has_value()) { - return *action->scope(); + action->scope()->Initialize(named_entity, value); + return; } } - return globals_; + globals_->Initialize(named_entity, value); +} + +auto ActionStack::ValueOfName(NamedEntityView named_entity, + SourceLocation source_loc) const + -> Nonnull { + if (std::optional> constant_value = + named_entity.constant_value(); + constant_value.has_value()) { + return *constant_value; + } + for (const std::unique_ptr& action : todo_) { + // TODO: have static name resolution identify the scope of named_entity + // as an AstNode, and then perform lookup _only_ on the Action associated + // with that node. This will help keep unwanted dynamic-scoping behavior + // from sneaking in. + if (action->scope().has_value()) { + std::optional> result = + action->scope()->Get(named_entity); + if (result.has_value()) { + return *result; + } + } + } + if (globals_.has_value()) { + std::optional> result = globals_->Get(named_entity); + if (result.has_value()) { + return *result; + } + } + // TODO: Move these errors to compile time and explain them more clearly. + FATAL_RUNTIME_ERROR(source_loc) + << "could not find `" << named_entity.name() << "`"; +} + +void ActionStack::MergeScope(RuntimeScope scope) { + for (const std::unique_ptr& action : todo_) { + if (action->scope().has_value()) { + action->scope()->Merge(std::move(scope)); + return; + } + } + if (globals_.has_value()) { + globals_->Merge(std::move(scope)); + return; + } + FATAL() << "No current scope"; +} + +void ActionStack::InitializeFragment(ContinuationValue::StackFragment& fragment, + Nonnull body) { + std::vector> scopes; + for (const std::unique_ptr& action : todo_) { + if (action->scope().has_value()) { + scopes.push_back(&*action->scope()); + } + } + // We don't capture globals_ or constants_ because they're global. + + std::vector> reversed_todo; + reversed_todo.push_back(std::make_unique(body)); + reversed_todo.push_back( + std::make_unique(RuntimeScope::Capture(scopes))); + fragment.StoreReversed(std::move(reversed_todo)); } void ActionStack::FinishAction() { @@ -70,7 +147,7 @@ void ActionStack::Spawn(std::unique_ptr child) { todo_.Push(std::move(child)); } -void ActionStack::Spawn(std::unique_ptr child, Scope scope) { +void ActionStack::Spawn(std::unique_ptr child, RuntimeScope scope) { Action& action = *todo_.Top(); action.set_pos(action.pos() + 1); todo_.Push(std::make_unique(std::move(scope))); diff --git a/executable_semantics/interpreter/action_stack.h b/executable_semantics/interpreter/action_stack.h index 6e24218d921e..739de309f11d 100644 --- a/executable_semantics/interpreter/action_stack.h +++ b/executable_semantics/interpreter/action_stack.h @@ -19,13 +19,21 @@ namespace Carbon { class ActionStack { public: // Constructs an empty ActionStack - explicit ActionStack(Scope globals) : globals_(std::move(globals)) {} + ActionStack() = default; void Print(llvm::raw_ostream& out) const; LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } - // Returns an Env containing the currently-defined global variables. - auto GlobalEnv() const -> Env { return globals_.values(); } + // TODO: consider unifying with Print. + void PrintScopes(llvm::raw_ostream& out) const; + + // Sets the heap that variables will be allocated on. Cannot be called at + // run time, or when IsEmpty() is false, and marks the start of run time. + void SetHeap(Nonnull heap) { + CHECK(todo_.IsEmpty()); + CHECK(!globals_.has_value()); + globals_ = RuntimeScope(heap); + } // Starts execution with `action` at the top of the stack. Cannot be called // when IsEmpty() is false. @@ -38,13 +46,25 @@ class ActionStack { // ScopeAction. auto CurrentAction() -> Action& { return *todo_.Top(); } - // The scope that should be used to resolve name lookups in the current - // action. - auto CurrentScope() const -> Scope&; + // Allocates storage for `named_entity`, and initializes it to `value`. + void Initialize(NamedEntityView named_entity, Nonnull value); + + // Returns the value bound to `named_entity`. If `named_entity` is a local + // variable, this will be an LValue. + auto ValueOfName(NamedEntityView named_entity, + SourceLocation source_loc) const -> Nonnull; + + // Merges `scope` into the innermost scope currently on the stack. + void MergeScope(RuntimeScope scope); + + // Initializes `fragment` so that, when resumed, it begins execution of + // `body`. + void InitializeFragment(ContinuationValue::StackFragment& fragment, + Nonnull body); // The result produced by the `action` argument of the most recent - // `Start` call. *this must be empty, signifying that the action has been - // fully executed. + // Start call. Cannot be called if IsEmpty() is false, or if `action` + // was an action that doesn't produce results. auto result() const -> Nonnull { return *result_; } // The following methods, called "transition methods", update the state of @@ -65,7 +85,7 @@ class ActionStack { // Advances the current action one step, and push `child` onto the stack. // If `scope` is specified, `child` will be executed in that scope. void Spawn(std::unique_ptr child); - void Spawn(std::unique_ptr child, Scope scope); + void Spawn(std::unique_ptr child, RuntimeScope scope); // Advances the current action one step. void RunAgain(); @@ -100,7 +120,7 @@ class ActionStack { // TODO: consider defining a non-nullable unique_ptr-like type to use here. Stack> todo_; std::optional> result_; - mutable Scope globals_; + std::optional globals_; }; } // namespace Carbon diff --git a/executable_semantics/interpreter/heap.cpp b/executable_semantics/interpreter/heap.cpp index 54180f007918..387e11962382 100644 --- a/executable_semantics/interpreter/heap.cpp +++ b/executable_semantics/interpreter/heap.cpp @@ -55,16 +55,11 @@ void Heap::Print(llvm::raw_ostream& out) const { llvm::ListSeparator sep; for (size_t i = 0; i < values_.size(); ++i) { out << sep; - PrintAllocation(AllocationId(i), out); + if (!alive_[i]) { + out << "!!"; + } + out << *values_[i]; } } -void Heap::PrintAllocation(AllocationId allocation, - llvm::raw_ostream& out) const { - if (!alive_[allocation.index_]) { - out << "!!"; - } - out << *values_[allocation.index_]; -} - } // namespace Carbon diff --git a/executable_semantics/interpreter/heap.h b/executable_semantics/interpreter/heap.h index c7eb111a2482..3647c8858c93 100644 --- a/executable_semantics/interpreter/heap.h +++ b/executable_semantics/interpreter/heap.h @@ -41,14 +41,13 @@ class Heap : public HeapAllocationInterface { // Marks this allocation, and all of its sub-objects, as dead. void Deallocate(AllocationId allocation) override; - // Print the value at the given allocation to the stream `out`. - void PrintAllocation(AllocationId allocation, llvm::raw_ostream& out) const; - // Print all the values on the heap to the stream `out`. void Print(llvm::raw_ostream& out) const; LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + auto arena() const -> Arena& override { return *arena_; } + private: // Signal an error if the allocation is no longer alive. void CheckAlive(AllocationId allocation, SourceLocation source_loc); diff --git a/executable_semantics/interpreter/heap_allocation_interface.h b/executable_semantics/interpreter/heap_allocation_interface.h index dfa849fcbffe..5a791966aa85 100644 --- a/executable_semantics/interpreter/heap_allocation_interface.h +++ b/executable_semantics/interpreter/heap_allocation_interface.h @@ -5,6 +5,7 @@ #ifndef EXECUTABLE_SEMANTICS_INTERPRETER_HEAP_ALLOCATION_INTERFACE_H_ #define EXECUTABLE_SEMANTICS_INTERPRETER_HEAP_ALLOCATION_INTERFACE_H_ +#include "executable_semantics/common/arena.h" #include "executable_semantics/common/nonnull.h" #include "executable_semantics/interpreter/address.h" @@ -26,6 +27,9 @@ class HeapAllocationInterface { // Marks this allocation, and all of its sub-objects, as dead. virtual void Deallocate(AllocationId allocation) = 0; + // Returns the arena used to allocate the values in this heap. + virtual auto arena() const -> Arena& = 0; + protected: HeapAllocationInterface() = default; virtual ~HeapAllocationInterface() = default; diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index ebdfd7376d96..e119c383d601 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -23,43 +23,20 @@ using llvm::cast; using llvm::dyn_cast; +using llvm::isa; namespace Carbon { -// -// Auxiliary Functions -// - -void Interpreter::PrintEnv(Env values, llvm::raw_ostream& out) { - llvm::ListSeparator sep; - for (const auto& [name, allocation] : values) { - out << sep << name << ": "; - heap_.PrintAllocation(allocation, out); - } -} - // // State Operations // -auto Interpreter::CurrentEnv() -> Env { return todo_.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 { - std::optional pointer = CurrentEnv().Get(name); - if (!pointer) { - FATAL_RUNTIME_ERROR(source_loc) << "could not find `" << name << "`"; - } - return Address(*pointer); -} - void Interpreter::PrintState(llvm::raw_ostream& out) { out << "{\nstack: " << todo_; out << "\nheap: " << heap_; if (!todo_.IsEmpty()) { out << "\nvalues: "; - PrintEnv(CurrentEnv(), out); + todo_.PrintScopes(out); } out << "\n}\n"; } @@ -109,17 +86,21 @@ auto Interpreter::CreateStruct(const std::vector& fields, } auto Interpreter::PatternMatch(Nonnull p, Nonnull v, - SourceLocation source_loc) - -> std::optional { + SourceLocation source_loc, + std::optional> bindings) + -> bool { switch (p->kind()) { case Value::Kind::BindingPlaceholderValue: { - const auto& placeholder = cast(*p); - Env values(arena_); - if (placeholder.named_entity().has_value()) { - AllocationId a = heap_.AllocateValue(v); - values.Set(std::string(placeholder.named_entity()->name()), a); + if (!bindings.has_value()) { + // TODO: move this to typechecker. + FATAL_COMPILATION_ERROR(source_loc) + << "Name bindings are not supported in this context"; } - return values; + const auto& placeholder = cast(*p); + if (placeholder.named_entity().has_value()) { + (*bindings)->Initialize(*placeholder.named_entity(), v); + } + return true; } case Value::Kind::TupleValue: switch (v->kind()) { @@ -131,18 +112,13 @@ auto Interpreter::PatternMatch(Nonnull p, Nonnull v, << "arity mismatch in tuple pattern match:\n pattern: " << p_tup << "\n value: " << v_tup; } - Env values(arena_); for (size_t i = 0; i < p_tup.elements().size(); ++i) { - std::optional matches = PatternMatch( - p_tup.elements()[i], v_tup.elements()[i], source_loc); - if (!matches) { - return std::nullopt; - } - for (const auto& [name, value] : *matches) { - values.Set(name, value); + if (!PatternMatch(p_tup.elements()[i], v_tup.elements()[i], + source_loc, bindings)) { + return false; } } // for - return values; + return true; } default: FATAL() << "expected a tuple value in pattern, not " << *v; @@ -151,20 +127,14 @@ auto Interpreter::PatternMatch(Nonnull p, Nonnull v, const auto& p_struct = cast(*p); const auto& v_struct = cast(*v); CHECK(p_struct.elements().size() == v_struct.elements().size()); - Env values(arena_); for (size_t i = 0; i < p_struct.elements().size(); ++i) { CHECK(p_struct.elements()[i].name == v_struct.elements()[i].name); - std::optional matches = - PatternMatch(p_struct.elements()[i].value, - v_struct.elements()[i].value, source_loc); - if (!matches) { - return std::nullopt; - } - for (const auto& [name, value] : *matches) { - values.Set(name, value); + if (!PatternMatch(p_struct.elements()[i].value, + v_struct.elements()[i].value, source_loc, bindings)) { + return false; } } - return values; + return true; } case Value::Kind::AlternativeValue: switch (v->kind()) { @@ -173,9 +143,10 @@ auto Interpreter::PatternMatch(Nonnull p, Nonnull v, const auto& v_alt = cast(*v); if (p_alt.choice_name() != v_alt.choice_name() || p_alt.alt_name() != v_alt.alt_name()) { - return std::nullopt; + return false; } - return PatternMatch(&p_alt.argument(), &v_alt.argument(), source_loc); + return PatternMatch(&p_alt.argument(), &v_alt.argument(), source_loc, + bindings); } default: FATAL() << "expected a choice alternative in pattern, not " << *v; @@ -185,35 +156,25 @@ auto Interpreter::PatternMatch(Nonnull p, Nonnull v, case Value::Kind::FunctionType: { const auto& p_fn = cast(*p); const auto& v_fn = cast(*v); - std::optional param_matches = - PatternMatch(&p_fn.parameters(), &v_fn.parameters(), source_loc); - if (!param_matches) { - return std::nullopt; + if (!PatternMatch(&p_fn.parameters(), &v_fn.parameters(), source_loc, + bindings)) { + return false; } - std::optional ret_matches = PatternMatch( - &p_fn.return_type(), &v_fn.return_type(), source_loc); - if (!ret_matches) { - return std::nullopt; + if (!PatternMatch(&p_fn.return_type(), &v_fn.return_type(), + source_loc, bindings)) { + return false; } - Env values = *param_matches; - for (const auto& [name, value] : *ret_matches) { - values.Set(name, value); - } - return values; + return true; } default: - return std::nullopt; + return false; } case Value::Kind::AutoType: // `auto` matches any type, without binding any new names. We rely // on the typechecker to ensure that `v` is a type. - return Env(arena_); + return true; default: - if (ValueEqual(p, v)) { - return Env(arena_); - } else { - return std::nullopt; - } + return ValueEqual(p, v); } } @@ -228,13 +189,10 @@ void Interpreter::StepLvalue() { case ExpressionKind::IdentifierExpression: { // { {x :: C, E, F} :: S, H} // -> { {E(x) :: C, E, F} :: S, H} - CHECK(cast(exp).has_named_entity()) - << "Identifier '" << exp << "' at " << exp.source_loc() - << " was not resolved"; - Address pointer = - GetFromEnv(exp.source_loc(), cast(exp).name()); - Nonnull v = arena_->New(pointer); - return todo_.FinishAction(v); + Nonnull value = todo_.ValueOfName( + cast(exp).named_entity(), exp.source_loc()); + CHECK(isa(value)) << *value; + return todo_.FinishAction(value); } case ExpressionKind::FieldAccessExpression: { if (act.pos() == 0) { @@ -443,17 +401,13 @@ void Interpreter::StepExp() { case ExpressionKind::IdentifierExpression: { CHECK(act.pos() == 0); const auto& ident = cast(exp); - CHECK(ident.has_named_entity()) - << "Identifier '" << exp << "' at " << exp.source_loc() - << " was not resolved"; // { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H} - if (std::optional> value = - ident.named_entity().constant_value(); - value.has_value()) { - return todo_.FinishAction(*value); + Nonnull value = + todo_.ValueOfName(ident.named_entity(), ident.source_loc()); + if (const auto* lvalue = dyn_cast(value)) { + value = heap_.Read(lvalue->address(), exp.source_loc()); } - Address pointer = GetFromEnv(exp.source_loc(), ident.name()); - return todo_.FinishAction(heap_.Read(pointer, exp.source_loc())); + return todo_.FinishAction(value); } case ExpressionKind::IntLiteral: CHECK(act.pos() == 0); @@ -505,20 +459,15 @@ void Interpreter::StepExp() { cast(*act.results()[0]).declaration(); Nonnull converted_args = Convert( act.results()[1], &function.param_pattern().static_type()); - std::optional matches = - PatternMatch(&function.param_pattern().value(), converted_args, - exp.source_loc()); - CHECK(matches.has_value()) - << "internal error in call_function, pattern match failed"; - Scope new_scope(todo_.GlobalEnv(), &heap_); - for (const auto& [name, value] : *matches) { - new_scope.AddLocal(name, value); - } + RuntimeScope function_scope(&heap_); + CHECK(PatternMatch(&function.param_pattern().value(), + converted_args, exp.source_loc(), + &function_scope)); CHECK(function.body().has_value()) << "Calling a function that's missing a body"; return todo_.Spawn( std::make_unique(*function.body()), - std::move(new_scope)); + std::move(function_scope)); } default: FATAL_RUNTIME_ERROR(exp.source_loc()) @@ -671,7 +620,7 @@ void Interpreter::StepStmt() { if (act.pos() == 0) { // { { (match (e) ...) :: C, E, F} :: S, H} // -> { { e :: (match ([]) ...) :: C, E, F} :: S, H} - act.StartScope(Scope(CurrentEnv(), &heap_)); + act.StartScope(RuntimeScope(&heap_)); return todo_.Spawn( std::make_unique(&match_stmt.expression())); } else { @@ -680,17 +629,13 @@ void Interpreter::StepStmt() { return todo_.FinishAction(); } auto c = match_stmt.clauses()[clause_num]; - std::optional matches = - PatternMatch(&c.pattern().value(), + RuntimeScope matches(&heap_); + if (PatternMatch(&c.pattern().value(), Convert(act.results()[0], &c.pattern().static_type()), - stmt.source_loc()); - if (matches) { // We have a match, start the body. + stmt.source_loc(), &matches)) { // Ensure we don't process any more clauses. act.set_pos(match_stmt.clauses().size() + 1); - - for (const auto& [name, value] : *matches) { - act.scope()->AddLocal(name, value); - } + todo_.MergeScope(std::move(matches)); return todo_.Spawn(std::make_unique(&c.statement())); } else { return todo_.RunAgain(); @@ -739,7 +684,7 @@ void Interpreter::StepStmt() { } // Initialize a scope when starting a block. if (act.pos() == 0) { - act.StartScope(Scope(CurrentEnv(), &heap_)); + act.StartScope(RuntimeScope(&heap_)); } // Process the next statement in the block. The position will be // incremented as part of Spawn. @@ -761,14 +706,11 @@ void Interpreter::StepStmt() { Nonnull p = &cast(stmt).pattern().value(); - std::optional matches = PatternMatch(p, v, stmt.source_loc()); - CHECK(matches) + RuntimeScope matches(&heap_); + CHECK(PatternMatch(p, v, stmt.source_loc(), &matches)) << stmt.source_loc() << ": internal error in variable definition, match failed"; - for (const auto& [name, value] : *matches) { - Scope& current_scope = todo_.CurrentScope(); - current_scope.AddLocal(name, value); - } + todo_.MergeScope(std::move(matches)); return todo_.FinishAction(); } } @@ -844,21 +786,15 @@ void Interpreter::StepStmt() { } case StatementKind::Continuation: { CHECK(act.pos() == 0); + const auto& continuation = cast(stmt); // Create a continuation object by creating a frame similar the // way one is created in a function call. 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)); + todo_.InitializeFragment(*fragment, &continuation.body()); // Bind the continuation object to the continuation variable - todo_.CurrentScope().AddLocal(cast(stmt).name(), - continuation_address); + todo_.Initialize(&cast(stmt), + arena_->New(fragment)); return todo_.FinishAction(); } case StatementKind::Run: { @@ -892,8 +828,7 @@ void Interpreter::StepDeclaration() { return todo_.Spawn( std::make_unique(&var_decl.initializer())); } else { - todo_.CurrentScope().AddLocal(var_decl.binding().name(), - heap_.AllocateValue(act.results()[0])); + todo_.Initialize(&var_decl.binding(), act.results()[0]); return todo_.FinishAction(); } } @@ -939,6 +874,8 @@ void Interpreter::RunAllSteps(bool trace_steps) { } auto Interpreter::InterpProgram(const AST& ast) -> int { + todo_.SetHeap(&heap_); + if (trace_) { llvm::outs() << "********** initializing globals **********\n"; } diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index e943518fd785..470338b8545c 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -25,10 +25,7 @@ namespace Carbon { class Interpreter { public: explicit Interpreter(Nonnull arena, bool trace) - : arena_(arena), - heap_(arena), - todo_(Scope(Env(arena_), &heap_)), - trace_(trace) {} + : arena_(arena), heap_(arena), trace_(trace) {} // Interpret the whole program. auto InterpProgram(const AST& ast) -> int; @@ -39,18 +36,22 @@ class Interpreter { // Interpret a pattern at compile-time. auto InterpPattern(Nonnull p) -> Nonnull; - // Attempts to match `v` against the pattern `p`. If matching succeeds, - // returns the bindings of pattern variables to their matched values. - auto PatternMatch(Nonnull p, Nonnull v, - SourceLocation source_loc) -> std::optional; + // Attempts to match `v` against the pattern `p`, returning whether matching + // is successful. If it is, populates **bindings with the variables bound by + // the match; `bindings` should only be nullopt in contexts where `p` + // is not permitted to bind variables. **bindings may be modified even if the + // match is unsuccessful, so it should typically be created for the + // PatternMatch call and then merged into an existing scope on success. + [[nodiscard]] auto PatternMatch( + Nonnull p, Nonnull v, + SourceLocation source_loc, std::optional> bindings) + -> bool; // Support TypeChecker allocating values on the heap. auto AllocateValue(Nonnull v) -> AllocationId { return heap_.AllocateValue(v); } - void PrintEnv(Env values, llvm::raw_ostream& out); - private: void Step(); @@ -65,10 +66,6 @@ class Interpreter { // State transition for declarations. void StepDeclaration(); - auto CurrentEnv() -> Env; - auto GetFromEnv(SourceLocation source_loc, const std::string& name) - -> Address; - // Calls Step() repeatedly until there are no steps left to execute. Produces // trace output if trace_steps is true. void RunAllSteps(bool trace_steps); diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 34d917384358..e4c466d51360 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -732,9 +732,8 @@ void TypeChecker::TypeCheckPattern( if (IsConcreteType(type)) { ExpectType(p->source_loc(), "name binding", type, *expected); } else { - std::optional values = interpreter_.PatternMatch( - type, *expected, binding.type().source_loc()); - if (values == std::nullopt) { + if (!interpreter_.PatternMatch( + type, *expected, binding.type().source_loc(), std::nullopt)) { FATAL_COMPILATION_ERROR(binding.type().source_loc()) << "Type pattern '" << *type << "' does not match actual type '" << **expected << "'"; diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index 5194309099b8..43628bc91535 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -5,7 +5,6 @@ #ifndef EXECUTABLE_SEMANTICS_INTERPRETER_TYPE_CHECKER_H_ #define EXECUTABLE_SEMANTICS_INTERPRETER_TYPE_CHECKER_H_ -#include #include #include "common/ostream.h"