Factor AllocationId out of Address (#916)

This lets us statically distinguish between code that works with arbitrary `Address`es and code that can only work with pointers to separately-allocated storage, and so we no longer need to worry about the latter code crashing at run-time (as `Heap::Deallocate` did) or silently doing the wrong thing (as `Heap::PrintAddress` did) if it's given the wrong kind of `Address`.
This commit is contained in:
Geoff Romer
2021-10-27 16:40:17 -07:00
committed by GitHub
parent 0da907fd2a
commit 0df9e8666c
8 changed files with 85 additions and 66 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
namespace Carbon {
using Env = Dictionary<std::string, Address>;
using Env = Dictionary<std::string, AllocationId>;
struct Scope {
explicit Scope(Env values) : Scope(values, std::vector<std::string>()) {}
+35 -18
View File
@@ -15,32 +15,51 @@
namespace Carbon {
// An AllocationId identifies an _allocation_ produced by a Heap. An allocation
// is analogous to the C++ notion of a complete object: the the `Value` in an
// allocation is not a sub-part of any other `Value`.
class AllocationId {
public:
AllocationId(const AllocationId&) = default;
auto operator=(const AllocationId&) -> AllocationId& = default;
// Prints a human-readable representation of *this to `out`.
//
// Currently that representation consists of an integer index.
void Print(llvm::raw_ostream& out) const {
out << "Allocation(" << index_ << ")";
}
private:
// The representation of AllocationId describes how to locate an object within
// a Heap, so its implementation details are tied to the implementation
// details of Heap.
friend class Heap;
AllocationId(size_t index) : index_(index) {}
size_t index_;
};
// An Address represents a memory address in the Carbon virtual machine.
// Addresses are used to access values stored in a Heap, and are obtained
// from a Heap (or by deriving them from other Addresses).
// Addresses are used to access values stored in a Heap. Unlike an
// AllocationId, an Address can refer to a sub-Value of some larger Value.
class Address {
public:
// Constructs an `Address` that refers to the value stored in `allocation`.
explicit Address(AllocationId allocation) : allocation_(allocation) {}
Address(const Address&) = default;
Address(Address&&) = default;
auto operator=(const Address&) -> Address& = default;
auto operator=(Address&&) -> Address& = default;
// Returns true if the two addresses refer to the same memory location.
friend auto operator==(const Address& lhs, const Address& rhs) -> bool {
return lhs.index_ == rhs.index_;
}
friend auto operator!=(const Address& lhs, const Address& rhs) -> bool {
return !(lhs == rhs);
}
// Prints a human-readable representation of `a` to `out`.
//
// Currently, that representation consists of an integer index identifying
// the whole memory allocation, and an optional FieldPath specifying a
// particular field within that allocation.
// Currently, that representation consists of an AllocationId followed by an
// optional FieldPath specifying a particular field within that allocation.
void Print(llvm::raw_ostream& out) const {
out << "Address(" << index_ << ")" << field_path_;
out << allocation_ << field_path_;
}
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
@@ -59,9 +78,7 @@ class Address {
// details of the Heap.
friend class Heap;
explicit Address(uint64_t index) : index_(index) {}
uint64_t index_;
AllocationId allocation_;
FieldPath field_path_;
};
+19 -18
View File
@@ -9,12 +9,12 @@
namespace Carbon {
auto Heap::AllocateValue(Nonnull<const Value*> v) -> Address {
auto Heap::AllocateValue(Nonnull<const Value*> v) -> AllocationId {
// Putting the following two side effects together in this function
// ensures that we don't do anything else in between, which is really bad!
// Consider whether to include a copy of the input v in this function
// or to leave it up to the caller.
Address a(values_.size());
AllocationId a(values_.size());
values_.push_back(v);
alive_.push_back(true);
return a;
@@ -22,29 +22,29 @@ auto Heap::AllocateValue(Nonnull<const Value*> v) -> Address {
auto Heap::Read(const Address& a, SourceLocation source_loc)
-> Nonnull<const Value*> {
this->CheckAlive(a, source_loc);
return values_[a.index_]->GetField(arena_, a.field_path_, source_loc);
this->CheckAlive(a.allocation_, source_loc);
return values_[a.allocation_.index_]->GetField(arena_, a.field_path_,
source_loc);
}
void Heap::Write(const Address& a, Nonnull<const Value*> v,
SourceLocation source_loc) {
this->CheckAlive(a, source_loc);
values_[a.index_] =
values_[a.index_]->SetField(arena_, a.field_path_, v, source_loc);
this->CheckAlive(a.allocation_, source_loc);
values_[a.allocation_.index_] = values_[a.allocation_.index_]->SetField(
arena_, a.field_path_, v, source_loc);
}
void Heap::CheckAlive(const Address& address, SourceLocation source_loc) {
if (!alive_[address.index_]) {
void Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) {
if (!alive_[allocation.index_]) {
FATAL_RUNTIME_ERROR(source_loc)
<< "undefined behavior: access to dead value "
<< *values_[address.index_];
<< *values_[allocation.index_];
}
}
void Heap::Deallocate(const Address& address) {
CHECK(address.field_path_.IsEmpty());
if (alive_[address.index_]) {
alive_[address.index_] = false;
void Heap::Deallocate(AllocationId allocation) {
if (alive_[allocation.index_]) {
alive_[allocation.index_] = false;
} else {
FATAL_RUNTIME_ERROR_NO_LINE() << "deallocating an already dead value";
}
@@ -54,15 +54,16 @@ void Heap::Print(llvm::raw_ostream& out) const {
llvm::ListSeparator sep;
for (size_t i = 0; i < values_.size(); ++i) {
out << sep;
PrintAddress(Address(i), out);
PrintAllocation(AllocationId(i), out);
}
}
void Heap::PrintAddress(const Address& a, llvm::raw_ostream& out) const {
if (!alive_[a.index_]) {
void Heap::PrintAllocation(AllocationId allocation,
llvm::raw_ostream& out) const {
if (!alive_[allocation.index_]) {
out << "!!";
}
out << *values_[a.index_];
out << *values_[allocation.index_];
}
} // namespace Carbon
+10 -10
View File
@@ -2,8 +2,8 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_MEMORY_H_
#define EXECUTABLE_SEMANTICS_INTERPRETER_MEMORY_H_
#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_HEAP_H_
#define EXECUTABLE_SEMANTICS_INTERPRETER_HEAP_H_
#include <vector>
@@ -34,13 +34,13 @@ class Heap {
SourceLocation source_loc);
// Put the given value on the heap and mark it as alive.
auto AllocateValue(Nonnull<const Value*> v) -> Address;
auto AllocateValue(Nonnull<const Value*> v) -> AllocationId;
// Marks the object at this address, and all of its sub-objects, as dead.
void Deallocate(const Address& address);
// Marks this allocation, and all of its sub-objects, as dead.
void Deallocate(AllocationId allocation);
// Print the value at the given address to the stream `out`.
void PrintAddress(const Address& a, llvm::raw_ostream& out) const;
// 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;
@@ -48,8 +48,8 @@ class Heap {
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
private:
// Signal an error if the address is no longer alive.
void CheckAlive(const Address& address, SourceLocation source_loc);
// Signal an error if the allocation is no longer alive.
void CheckAlive(AllocationId allocation, SourceLocation source_loc);
Nonnull<Arena*> arena_;
std::vector<Nonnull<const Value*>> values_;
@@ -58,4 +58,4 @@ class Heap {
} // namespace Carbon
#endif // EXECUTABLE_SEMANTICS_INTERPRETER_MEMORY_H_
#endif // EXECUTABLE_SEMANTICS_INTERPRETER_HEAP_H_
@@ -32,9 +32,9 @@ namespace Carbon {
void Interpreter::PrintEnv(Env values, llvm::raw_ostream& out) {
llvm::ListSeparator sep;
for (const auto& [name, address] : values) {
for (const auto& [name, allocation] : values) {
out << sep << name << ": ";
heap_.PrintAddress(address, out);
heap_.PrintAllocation(allocation, out);
}
}
@@ -56,11 +56,11 @@ auto Interpreter::CurrentEnv() -> Env { return CurrentScope().values; }
// Returns the given name from the environment, printing an error if not found.
auto Interpreter::GetFromEnv(SourceLocation source_loc, const std::string& name)
-> Address {
std::optional<Address> pointer = CurrentEnv().Get(name);
std::optional<AllocationId> pointer = CurrentEnv().Get(name);
if (!pointer) {
FATAL_RUNTIME_ERROR(source_loc) << "could not find `" << name << "`";
}
return *pointer;
return Address(*pointer);
}
void Interpreter::PrintState(llvm::raw_ostream& out) {
@@ -116,12 +116,12 @@ void Interpreter::InitEnv(const Declaration& d, Env* env) {
Env new_env = *env;
// Bring the deduced parameters into scope.
for (const auto& deduced : func_def.deduced_parameters()) {
Address a =
AllocationId a =
heap_.AllocateValue(arena_->New<VariableType>(deduced.name));
new_env.Set(deduced.name, a);
}
Nonnull<const FunctionValue*> f = arena_->New<FunctionValue>(&func_def);
Address a = heap_.AllocateValue(f);
AllocationId a = heap_.AllocateValue(f);
env->Set(func_def.name(), a);
break;
}
@@ -144,7 +144,7 @@ void Interpreter::InitEnv(const Declaration& d, Env* env) {
}
auto st = arena_->New<NominalClassType>(
class_def.name(), std::move(fields), std::move(methods));
auto a = heap_.AllocateValue(st);
AllocationId a = heap_.AllocateValue(st);
env->Set(class_def.name(), a);
break;
}
@@ -157,7 +157,7 @@ void Interpreter::InitEnv(const Declaration& d, Env* env) {
alts.push_back({.name = alternative.name(), .value = t});
}
auto ct = arena_->New<ChoiceType>(choice.name(), std::move(alts));
auto a = heap_.AllocateValue(ct);
AllocationId a = heap_.AllocateValue(ct);
env->Set(choice.name(), a);
break;
}
@@ -168,7 +168,7 @@ void Interpreter::InitEnv(const Declaration& d, Env* env) {
// result of evaluating the initializer.
Nonnull<const Value*> v =
Convert(InterpExp(*env, &var.initializer()), &var.static_type());
Address a = heap_.AllocateValue(v);
AllocationId a = heap_.AllocateValue(v);
env->Set(*var.binding().name(), a);
break;
}
@@ -184,7 +184,7 @@ void Interpreter::InitGlobals(llvm::ArrayRef<Nonnull<Declaration*>> fs) {
void Interpreter::DeallocateScope(Scope& scope) {
CHECK(!scope.deallocated);
for (const auto& l : scope.locals) {
std::optional<Address> a = scope.values.Get(l);
std::optional<AllocationId> a = scope.values.Get(l);
CHECK(a);
heap_.Deallocate(*a);
}
@@ -221,7 +221,7 @@ auto Interpreter::PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
const auto& placeholder = cast<BindingPlaceholderValue>(*p);
Env values(arena_);
if (placeholder.name().has_value()) {
Address a = heap_.AllocateValue(v);
AllocationId a = heap_.AllocateValue(v);
values.Set(*placeholder.name(), a);
}
return values;
@@ -986,7 +986,7 @@ auto Interpreter::StepStmt() -> Transition {
arena_->New<StatementAction>(&cast<Continuation>(stmt).body()));
continuation_stack->push_back(
arena_->New<ScopeAction>(Scope(CurrentEnv())));
Address continuation_address = heap_.AllocateValue(
AllocationId continuation_address = heap_.AllocateValue(
arena_->New<ContinuationValue>(continuation_stack));
// Bind the continuation object to the continuation variable
CurrentScope().values.Set(
@@ -44,7 +44,7 @@ class Interpreter {
SourceLocation source_loc) -> std::optional<Env>;
// Support TypeChecker allocating values on the heap.
auto AllocateValue(Nonnull<const Value*> v) -> Address {
auto AllocateValue(Nonnull<const Value*> v) -> AllocationId {
return heap_.AllocateValue(v);
}
@@ -1051,7 +1051,7 @@ auto TypeChecker::TypeCheckFunDef(FunctionDeclaration* f, TypeEnv types,
for (const auto& deduced : f->deduced_parameters()) {
// auto t = interpreter_.InterpExp(values, deduced.type);
types.Set(deduced.name, arena_->New<VariableType>(deduced.name));
Address a = interpreter_.AllocateValue(*types.Get(deduced.name));
AllocationId a = interpreter_.AllocateValue(*types.Get(deduced.name));
values.Set(deduced.name, a);
}
// Type check the parameter pattern
@@ -1092,7 +1092,7 @@ auto TypeChecker::TypeOfFunDef(TypeEnv types, Env values,
for (const auto& deduced : fun_def->deduced_parameters()) {
// auto t = interpreter_.InterpExp(values, deduced.type);
types.Set(deduced.name, arena_->New<VariableType>(deduced.name));
Address a = interpreter_.AllocateValue(*types.Get(deduced.name));
AllocationId a = interpreter_.AllocateValue(*types.Get(deduced.name));
values.Set(deduced.name, a);
}
// Type check the parameter pattern
@@ -1205,7 +1205,7 @@ void TypeChecker::TopLevel(Nonnull<Declaration*> d, TypeCheckContext* tops) {
case Declaration::Kind::ClassDeclaration: {
const auto& class_def = cast<ClassDeclaration>(*d).definition();
auto st = TypeOfClassDef(&class_def, tops->types, tops->values);
Address a = interpreter_.AllocateValue(st);
AllocationId a = interpreter_.AllocateValue(st);
tops->values.Set(class_def.name(), a); // Is this obsolete?
tops->types.Set(class_def.name(), st);
break;
@@ -1219,7 +1219,7 @@ void TypeChecker::TopLevel(Nonnull<Declaration*> d, TypeCheckContext* tops) {
alts.push_back({.name = alternative.name(), .value = t});
}
auto ct = arena_->New<ChoiceType>(choice.name(), std::move(alts));
Address a = interpreter_.AllocateValue(ct);
AllocationId a = interpreter_.AllocateValue(ct);
tops->values.Set(choice.name(), a); // Is this obsolete?
tops->types.Set(choice.name(), ct);
break;
+3 -2
View File
@@ -338,8 +338,6 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2,
return cast<IntValue>(*v1).value() == cast<IntValue>(*v2).value();
case Value::Kind::BoolValue:
return cast<BoolValue>(*v1).value() == cast<BoolValue>(*v2).value();
case Value::Kind::PointerValue:
return cast<PointerValue>(*v1).value() == cast<PointerValue>(*v2).value();
case Value::Kind::FunctionValue: {
std::optional<Nonnull<const Statement*>> body1 =
cast<FunctionValue>(*v1).declaration().body();
@@ -396,6 +394,9 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2,
case Value::Kind::BindingPlaceholderValue:
case Value::Kind::AlternativeConstructorValue:
case Value::Kind::ContinuationValue:
case Value::Kind::PointerValue:
// TODO: support pointer comparisons once we have a clearer distinction
// between pointers and lvalues.
FATAL() << "ValueEqual does not support this kind of value: " << *v1;
}
}