diff --git a/executable_semantics/interpreter/action.h b/executable_semantics/interpreter/action.h index f2c2a47d0645..e4b6aeb2d90c 100644 --- a/executable_semantics/interpreter/action.h +++ b/executable_semantics/interpreter/action.h @@ -18,7 +18,7 @@ namespace Carbon { -using Env = Dictionary; +using Env = Dictionary; struct Scope { explicit Scope(Env values) : Scope(values, std::vector()) {} diff --git a/executable_semantics/interpreter/address.h b/executable_semantics/interpreter/address.h index 1f47a3985828..580b6b7ef483 100644 --- a/executable_semantics/interpreter/address.h +++ b/executable_semantics/interpreter/address.h @@ -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_; }; diff --git a/executable_semantics/interpreter/heap.cpp b/executable_semantics/interpreter/heap.cpp index c196830d2116..c82d484d755d 100644 --- a/executable_semantics/interpreter/heap.cpp +++ b/executable_semantics/interpreter/heap.cpp @@ -9,12 +9,12 @@ namespace Carbon { -auto Heap::AllocateValue(Nonnull v) -> Address { +auto Heap::AllocateValue(Nonnull 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 v) -> Address { auto Heap::Read(const Address& a, SourceLocation source_loc) -> Nonnull { - 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 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 diff --git a/executable_semantics/interpreter/heap.h b/executable_semantics/interpreter/heap.h index 580a88038ead..43a863b21074 100644 --- a/executable_semantics/interpreter/heap.h +++ b/executable_semantics/interpreter/heap.h @@ -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 @@ -34,13 +34,13 @@ class Heap { SourceLocation source_loc); // Put the given value on the heap and mark it as alive. - auto AllocateValue(Nonnull v) -> Address; + auto AllocateValue(Nonnull 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_; std::vector> values_; @@ -58,4 +58,4 @@ class Heap { } // namespace Carbon -#endif // EXECUTABLE_SEMANTICS_INTERPRETER_MEMORY_H_ +#endif // EXECUTABLE_SEMANTICS_INTERPRETER_HEAP_H_ diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index b159fd3ff55b..09a9eb1ddda8 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -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
pointer = CurrentEnv().Get(name); + std::optional 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(deduced.name)); new_env.Set(deduced.name, a); } Nonnull f = arena_->New(&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( 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(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 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> fs) { void Interpreter::DeallocateScope(Scope& scope) { CHECK(!scope.deallocated); for (const auto& l : scope.locals) { - std::optional
a = scope.values.Get(l); + std::optional a = scope.values.Get(l); CHECK(a); heap_.Deallocate(*a); } @@ -221,7 +221,7 @@ auto Interpreter::PatternMatch(Nonnull p, Nonnull v, const auto& placeholder = cast(*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(&cast(stmt).body())); continuation_stack->push_back( arena_->New(Scope(CurrentEnv()))); - Address continuation_address = heap_.AllocateValue( + AllocationId continuation_address = heap_.AllocateValue( arena_->New(continuation_stack)); // Bind the continuation object to the continuation variable CurrentScope().values.Set( diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index 7ffcf99fff6d..8f763bf178e4 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -44,7 +44,7 @@ class Interpreter { SourceLocation source_loc) -> std::optional; // Support TypeChecker allocating values on the heap. - auto AllocateValue(Nonnull v) -> Address { + auto AllocateValue(Nonnull v) -> AllocationId { return heap_.AllocateValue(v); } diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 1112f419de63..801c0ca4909e 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -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(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(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 d, TypeCheckContext* tops) { case Declaration::Kind::ClassDeclaration: { const auto& class_def = cast(*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 d, TypeCheckContext* tops) { alts.push_back({.name = alternative.name(), .value = t}); } auto ct = arena_->New(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; diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index cd403e9eadf2..6db59e849d73 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -338,8 +338,6 @@ auto ValueEqual(Nonnull v1, Nonnull v2, return cast(*v1).value() == cast(*v2).value(); case Value::Kind::BoolValue: return cast(*v1).value() == cast(*v2).value(); - case Value::Kind::PointerValue: - return cast(*v1).value() == cast(*v2).value(); case Value::Kind::FunctionValue: { std::optional> body1 = cast(*v1).declaration().body(); @@ -396,6 +394,9 @@ auto ValueEqual(Nonnull v1, Nonnull 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; } }