Use static name resolution in Interpreter (#1022)

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
This commit is contained in:
Geoff Romer
2022-01-21 09:48:11 -08:00
committed by GitHub
co-authored by Jon Meow
parent d28e75629e
commit c311c8849c
13 changed files with 304 additions and 221 deletions
+1
View File
@@ -110,6 +110,7 @@ cc_library(
hdrs = ["heap_allocation_interface.h"],
deps = [
":address",
"//executable_semantics/common:arena",
"//executable_semantics/common:nonnull",
],
)
+65 -11
View File
@@ -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:
+38 -32
View File
@@ -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)));
+30 -10
View File
@@ -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
+4 -9
View File
@@ -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
+2 -3
View File
@@ -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;
+65 -128
View File
@@ -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";
}
+11 -14
View File
@@ -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"