mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 19:51:09 +01:00
Use static name resolution in Interpreter (#1022)
Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
Jon Meow
parent
d28e75629e
commit
c311c8849c
@@ -110,6 +110,7 @@ cc_library(
|
||||
hdrs = ["heap_allocation_interface.h"],
|
||||
deps = [
|
||||
":address",
|
||||
"//executable_semantics/common:arena",
|
||||
"//executable_semantics/common:nonnull",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_
|
||||
#define EXECUTABLE_SEMANTICS_INTERPRETER_ACTION_H_
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/ostream.h"
|
||||
@@ -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) {}
|
||||
// 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;
|
||||
|
||||
// Equivalent to `Scope(values, {}, heap)`.
|
||||
Scope(Env values, Nonnull<HeapAllocationInterface*> heap)
|
||||
: Scope(values, std::vector<std::string>(), heap) {}
|
||||
// Constructs a RuntimeScope that allocates storage in `heap`.
|
||||
explicit RuntimeScope(Nonnull<HeapAllocationInterface*> 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<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);
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
@@ -114,13 +120,13 @@ class Action {
|
||||
void AddResult(Nonnull<const Value*> result) { results_.push_back(result); }
|
||||
|
||||
// Returns the scope associated with this Action, if any.
|
||||
auto scope() -> std::optional<Scope>& { return scope_; }
|
||||
auto scope() -> std::optional<RuntimeScope>& { 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<Nonnull<const Value*>> results_;
|
||||
std::optional<Scope> scope_;
|
||||
std::optional<RuntimeScope> 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
@@ -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)));
|
||||
|
||||
@@ -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<HeapAllocationInterface*> 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<const Value*> 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<const Value*>;
|
||||
|
||||
// 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<const Statement*> 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<const Value*> { 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<Action> child);
|
||||
void Spawn(std::unique_ptr<Action> child, Scope scope);
|
||||
void Spawn(std::unique_ptr<Action> 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<std::unique_ptr<Action>> todo_;
|
||||
std::optional<Nonnull<const Value*>> result_;
|
||||
mutable Scope globals_;
|
||||
std::optional<RuntimeScope> globals_;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<AllocationId> 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<FieldInitializer>& fields,
|
||||
}
|
||||
|
||||
auto Interpreter::PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
|
||||
SourceLocation source_loc)
|
||||
-> std::optional<Env> {
|
||||
SourceLocation source_loc,
|
||||
std::optional<Nonnull<RuntimeScope*>> bindings)
|
||||
-> bool {
|
||||
switch (p->kind()) {
|
||||
case Value::Kind::BindingPlaceholderValue: {
|
||||
const auto& placeholder = cast<BindingPlaceholderValue>(*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<BindingPlaceholderValue>(*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<const Value*> p, Nonnull<const Value*> 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<Env> 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<const Value*> p, Nonnull<const Value*> v,
|
||||
const auto& p_struct = cast<StructValue>(*p);
|
||||
const auto& v_struct = cast<StructValue>(*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<Env> 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<const Value*> p, Nonnull<const Value*> v,
|
||||
const auto& v_alt = cast<AlternativeValue>(*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<const Value*> p, Nonnull<const Value*> v,
|
||||
case Value::Kind::FunctionType: {
|
||||
const auto& p_fn = cast<FunctionType>(*p);
|
||||
const auto& v_fn = cast<FunctionType>(*v);
|
||||
std::optional<Env> 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<Env> 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<IdentifierExpression>(exp).has_named_entity())
|
||||
<< "Identifier '" << exp << "' at " << exp.source_loc()
|
||||
<< " was not resolved";
|
||||
Address pointer =
|
||||
GetFromEnv(exp.source_loc(), cast<IdentifierExpression>(exp).name());
|
||||
Nonnull<const Value*> v = arena_->New<LValue>(pointer);
|
||||
return todo_.FinishAction(v);
|
||||
Nonnull<const Value*> value = todo_.ValueOfName(
|
||||
cast<IdentifierExpression>(exp).named_entity(), exp.source_loc());
|
||||
CHECK(isa<LValue>(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<IdentifierExpression>(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<Nonnull<const Value*>> value =
|
||||
ident.named_entity().constant_value();
|
||||
value.has_value()) {
|
||||
return todo_.FinishAction(*value);
|
||||
Nonnull<const Value*> value =
|
||||
todo_.ValueOfName(ident.named_entity(), ident.source_loc());
|
||||
if (const auto* lvalue = dyn_cast<LValue>(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<FunctionValue>(*act.results()[0]).declaration();
|
||||
Nonnull<const Value*> converted_args = Convert(
|
||||
act.results()[1], &function.param_pattern().static_type());
|
||||
std::optional<Env> 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<StatementAction>(*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<ExpressionAction>(&match_stmt.expression()));
|
||||
} else {
|
||||
@@ -680,17 +629,13 @@ void Interpreter::StepStmt() {
|
||||
return todo_.FinishAction();
|
||||
}
|
||||
auto c = match_stmt.clauses()[clause_num];
|
||||
std::optional<Env> 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<StatementAction>(&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<const Value*> p =
|
||||
&cast<VariableDefinition>(stmt).pattern().value();
|
||||
|
||||
std::optional<Env> 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<Continuation>(stmt);
|
||||
// Create a continuation object by creating a frame similar the
|
||||
// way one is created in a function call.
|
||||
auto fragment = arena_->New<ContinuationValue::StackFragment>();
|
||||
stack_fragments_.push_back(fragment);
|
||||
std::vector<std::unique_ptr<Action>> reversed_todo;
|
||||
reversed_todo.push_back(
|
||||
std::make_unique<StatementAction>(&cast<Continuation>(stmt).body()));
|
||||
reversed_todo.push_back(
|
||||
std::make_unique<ScopeAction>(Scope(CurrentEnv(), &heap_)));
|
||||
fragment->StoreReversed(std::move(reversed_todo));
|
||||
AllocationId continuation_address =
|
||||
heap_.AllocateValue(arena_->New<ContinuationValue>(fragment));
|
||||
todo_.InitializeFragment(*fragment, &continuation.body());
|
||||
// Bind the continuation object to the continuation variable
|
||||
todo_.CurrentScope().AddLocal(cast<Continuation>(stmt).name(),
|
||||
continuation_address);
|
||||
todo_.Initialize(&cast<Continuation>(stmt),
|
||||
arena_->New<ContinuationValue>(fragment));
|
||||
return todo_.FinishAction();
|
||||
}
|
||||
case StatementKind::Run: {
|
||||
@@ -892,8 +828,7 @@ void Interpreter::StepDeclaration() {
|
||||
return todo_.Spawn(
|
||||
std::make_unique<ExpressionAction>(&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";
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ namespace Carbon {
|
||||
class Interpreter {
|
||||
public:
|
||||
explicit Interpreter(Nonnull<Arena*> 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<const Pattern*> p) -> Nonnull<const Value*>;
|
||||
|
||||
// Attempts to match `v` against the pattern `p`. If matching succeeds,
|
||||
// returns the bindings of pattern variables to their matched values.
|
||||
auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
|
||||
SourceLocation source_loc) -> std::optional<Env>;
|
||||
// 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<const Value*> p, Nonnull<const Value*> v,
|
||||
SourceLocation source_loc, std::optional<Nonnull<RuntimeScope*>> bindings)
|
||||
-> bool;
|
||||
|
||||
// Support TypeChecker allocating values on the heap.
|
||||
auto AllocateValue(Nonnull<const Value*> 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);
|
||||
|
||||
@@ -732,9 +732,8 @@ void TypeChecker::TypeCheckPattern(
|
||||
if (IsConcreteType(type)) {
|
||||
ExpectType(p->source_loc(), "name binding", type, *expected);
|
||||
} else {
|
||||
std::optional<Env> 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 << "'";
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_TYPE_CHECKER_H_
|
||||
#define EXECUTABLE_SEMANTICS_INTERPRETER_TYPE_CHECKER_H_
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "common/ostream.h"
|
||||
|
||||
Reference in New Issue
Block a user