فهرست منبع

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`.
Geoff Romer 4 سال پیش
والد
کامیت
0df9e8666c

+ 1 - 1
executable_semantics/interpreter/action.h

@@ -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
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_;
 };
 

+ 19 - 18
executable_semantics/interpreter/heap.cpp

@@ -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
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 <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_

+ 12 - 12
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<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(

+ 1 - 1
executable_semantics/interpreter/interpreter.h

@@ -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);
   }
 

+ 4 - 4
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<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
executable_semantics/interpreter/value.cpp

@@ -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;
   }
 }