Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use static name resolution in Interpreter #1022

Merged
merged 5 commits into from
Jan 21, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions executable_semantics/ast/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<NamedEntityView> named_entity_;
Expand Down
76 changes: 65 additions & 11 deletions executable_semantics/interpreter/action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<AllocationId> 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<const Value*> 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<LValue>(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<Nonnull<const LValue*>> {
auto it = locals_.find(named_entity);
if (it != locals_.end()) {
return it->second;
} else {
return std::nullopt;
}
}

auto RuntimeScope::Capture(
const std::vector<Nonnull<const RuntimeScope*>>& scopes) -> RuntimeScope {
CHECK(!scopes.empty());
RuntimeScope result(scopes.front()->heap_);
for (Nonnull<const RuntimeScope*> 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:
Expand Down
78 changes: 42 additions & 36 deletions executable_semantics/interpreter/action.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_
#define EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_

#include <map>
#include <vector>

#include "common/ostream.h"
Expand All @@ -19,42 +20,47 @@

namespace Carbon {

using Env = Dictionary<std::string, AllocationId>;

// 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<std::string> locals,
Nonnull<HeapAllocationInterface*> heap)
: values_(values), locals_(std::move(locals)), heap_(heap) {}

// Equivalent to `Scope(values, {}, heap)`.
Scope(Env values, Nonnull<HeapAllocationInterface*> heap)
: Scope(values, std::vector<std::string>(), 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);
}
// 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<Nonnull<const RuntimeScope*>>& scopes)
-> RuntimeScope;

// Constructs a RuntimeScope that allocates storage in `heap`.
explicit RuntimeScope(Nonnull<HeapAllocationInterface*> heap) : heap_(heap) {}

// Moving a RuntimeScope transfers ownership of its allocations.
RuntimeScope(RuntimeScope&&) noexcept;
auto operator=(RuntimeScope&&) noexcept -> RuntimeScope&;

// Deallocates any allocations in this scope from `heap`.
~RuntimeScope();

void Print(llvm::raw_ostream& out) const;
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }

// Allocates storage for `named_entity` in `heap`, and initializes it with
// `value`.
void Initialize(NamedEntityView named_entity, Nonnull<const Value*> 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);

auto values() const -> Env { return values_; }
// Returns the local storage for named_entity, if it has storage local to
// this scope.
auto Get(NamedEntityView named_entity) const
-> std::optional<Nonnull<const LValue*>>;

private:
Env values_;
std::vector<std::string> locals_;
std::map<NamedEntityView, Nonnull<const LValue*>> locals_;
std::vector<AllocationId> allocations_;
Nonnull<HeapAllocationInterface*> heap_;
};

Expand Down Expand Up @@ -84,13 +90,13 @@ class Action {
// 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);
}

// Returns the scope associated with this Action, if any.
auto scope() -> std::optional<Scope>& { return scope_; }
auto scope() -> std::optional<RuntimeScope>& { return scope_; }

static void PrintList(const Stack<Nonnull<Action*>>& ls,
llvm::raw_ostream& out);
Expand Down Expand Up @@ -127,7 +133,7 @@ class Action {
private:
int pos_ = 0;
std::vector<Nonnull<const Value*>> results_;
std::optional<Scope> scope_;
std::optional<RuntimeScope> scope_;

const Kind kind_;
};
Expand Down Expand Up @@ -228,7 +234,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));
}

Expand Down
87 changes: 82 additions & 5 deletions executable_semantics/interpreter/action_stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>& 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> 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<const Value*> value) {
for (const std::unique_ptr<Action>& 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<const Value*> {
if (std::optional<Nonnull<const Value*>> constant_value =
named_entity.constant_value();
constant_value.has_value()) {
return *constant_value;
}
for (const std::unique_ptr<Action>& 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<Nonnull<const Value*>> result =
action->scope()->Get(named_entity);
if (result.has_value()) {
return *result;
}
}
}
if (globals_.has_value()) {
std::optional<Nonnull<const Value*>> 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>& 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<const Statement*> body) {
std::vector<Nonnull<const RuntimeScope*>> scopes;
for (const std::unique_ptr<Action>& 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<std::unique_ptr<Action>> reversed_todo;
reversed_todo.push_back(std::make_unique<StatementAction>(body));
reversed_todo.push_back(
std::make_unique<ScopeAction>(RuntimeScope::Capture(scopes)));
fragment.StoreReversed(std::move(reversed_todo));
}

void ActionStack::FinishAction() {
Expand Down Expand Up @@ -70,7 +147,7 @@ void ActionStack::Spawn(std::unique_ptr<Action> child) {
todo_.Push(std::move(child));
}

void ActionStack::Spawn(std::unique_ptr<Action> child, Scope scope) {
void ActionStack::Spawn(std::unique_ptr<Action> child, RuntimeScope scope) {
Action& action = *todo_.Top();
action.set_pos(action.pos() + 1);
todo_.Push(std::make_unique<ScopeAction>(std::move(scope)));
Expand Down
Loading