mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
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:
@@ -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>()) {}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user