Просмотр исходного кода

Pointers (#1060)

* Naive pointer implementation

* Bug fixes and better pointer tests

* Remove debug print statement

* Implement changes suggested by @geoffromer

Also add a failing test case that tests applying the address-of operator
to an rvalue.
Darshal Shetty 4 лет назад
Родитель
Сommit
4479c55305

+ 2 - 0
executable_semantics/ast/expression.cpp

@@ -54,6 +54,8 @@ auto ToString(Operator op) -> std::string_view {
   switch (op) {
     case Operator::Add:
       return "+";
+    case Operator::AddressOf:
+      return "&";
     case Operator::Neg:
     case Operator::Sub:
       return "-";

+ 1 - 0
executable_semantics/ast/expression.h

@@ -96,6 +96,7 @@ class FieldInitializer {
 
 enum class Operator {
   Add,
+  AddressOf,
   And,
   Deref,
   Eq,

+ 23 - 3
executable_semantics/interpreter/interpreter.cpp

@@ -154,7 +154,9 @@ auto Interpreter::EvalPrim(Operator op,
     case Operator::Ptr:
       return arena_->New<PointerType>(args[0]);
     case Operator::Deref:
-      FATAL() << "dereference not implemented yet";
+      return heap_.Read(cast<PointerValue>(*args[0]).address(), source_loc);
+    case Operator::AddressOf:
+      return arena_->New<PointerValue>(cast<LValue>(*args[0]).address());
   }
 }
 
@@ -313,13 +315,26 @@ void Interpreter::StepLvalue() {
         return todo_.FinishAction(arena_->New<LValue>(field));
       }
     }
+    case ExpressionKind::PrimitiveOperatorExpression: {
+      const PrimitiveOperatorExpression& op = cast<PrimitiveOperatorExpression>(exp);
+      if (op.op() != Operator::Deref) {
+        FATAL() << "Can't treat primitive operator expression as lvalue: " << exp;
+      }
+      if (act.pos() == 0) {
+        return todo_.Spawn(
+            std::make_unique<ExpressionAction>(op.arguments()[0]));
+      } else {
+        const PointerValue& res = cast<PointerValue>(*act.results()[0]);
+        return todo_.FinishAction(arena_->New<LValue>(res.address()));
+      }
+      break;
+    }
     case ExpressionKind::TupleLiteral:
     case ExpressionKind::StructLiteral:
     case ExpressionKind::StructTypeLiteral:
     case ExpressionKind::IntLiteral:
     case ExpressionKind::BoolLiteral:
     case ExpressionKind::CallExpression:
-    case ExpressionKind::PrimitiveOperatorExpression:
     case ExpressionKind::IntTypeLiteral:
     case ExpressionKind::BoolTypeLiteral:
     case ExpressionKind::TypeTypeLiteral:
@@ -340,6 +355,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
   switch (value->kind()) {
     case Value::Kind::IntValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::BoolValue:
     case Value::Kind::NominalClassValue:
@@ -509,7 +525,11 @@ void Interpreter::StepExp() {
         //    { {v :: op(vs,[],e,es) :: C, E, F} :: S, H}
         // -> { {e :: op(vs,v,[],es) :: C, E, F} :: S, H}
         Nonnull<const Expression*> arg = op.arguments()[act.pos()];
-        return todo_.Spawn(std::make_unique<ExpressionAction>(arg));
+        if (op.op() == Operator::AddressOf) {
+          return todo_.Spawn(std::make_unique<LValAction>(arg));
+        } else {
+          return todo_.Spawn(std::make_unique<ExpressionAction>(arg));
+        }
       } else {
         //    { {v :: op(vs,[]) :: C, E, F} :: S, H}
         // -> { {eval_prim(op, (vs,v)) :: C, E, F} :: S, H}

+ 11 - 0
executable_semantics/interpreter/type_checker.cpp

@@ -74,6 +74,7 @@ static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
   switch (value->kind()) {
     case Value::Kind::IntValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::BoolValue:
     case Value::Kind::StructValue:
@@ -313,6 +314,7 @@ void TypeChecker::ArgumentDeduction(
     case Value::Kind::IntValue:
     case Value::Kind::BoolValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::StructValue:
     case Value::Kind::NominalClassValue:
@@ -378,6 +380,7 @@ auto TypeChecker::Substitute(
     case Value::Kind::IntValue:
     case Value::Kind::BoolValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::StructValue:
     case Value::Kind::NominalClassValue:
@@ -618,6 +621,14 @@ void TypeChecker::TypeCheckExp(Nonnull<Expression*> e) {
           SetStaticType(&op, arena_->New<TypeType>());
           op.set_value_category(ValueCategory::Let);
           return;
+        case Operator::AddressOf:
+          if (op.arguments()[0]->value_category() != ValueCategory::Var) {
+            FATAL_COMPILATION_ERROR(op.arguments()[0]->source_loc()) <<
+                "Argument to " << ToString(op.op()) << " should be an lvalue.";
+          }
+          SetStaticType(&op, arena_->New<PointerType>(ts[0]));
+          op.set_value_category(ValueCategory::Let);
+          return;
       }
       break;
     }

+ 5 - 1
executable_semantics/interpreter/value.cpp

@@ -179,8 +179,11 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::FunctionValue:
       out << "fun<" << cast<FunctionValue>(*this).declaration().name() << ">";
       break;
+    case Value::Kind::PointerValue:
+      out << "ptr<" << cast<PointerValue>(*this).address() << ">";
+      break;
     case Value::Kind::LValue:
-      out << "ptr<" << cast<LValue>(*this).address() << ">";
+      out << "lval<" << cast<LValue>(*this).address() << ">";
       break;
     case Value::Kind::BoolType:
       out << "Bool";
@@ -434,6 +437,7 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
     case Value::Kind::BindingPlaceholderValue:
     case Value::Kind::AlternativeConstructorValue:
     case Value::Kind::ContinuationValue:
+    case Value::Kind::PointerValue:
     case Value::Kind::LValue:
       // TODO: support pointer comparisons once we have a clearer distinction
       // between pointers and lvalues.

+ 17 - 0
executable_semantics/interpreter/value.h

@@ -36,6 +36,7 @@ class Value {
   enum class Kind {
     IntValue,
     FunctionValue,
+    PointerValue,
     LValue,
     BoolValue,
     StructValue,
@@ -150,6 +151,22 @@ class LValue : public Value {
   Address value_;
 };
 
+// A pointer value
+class PointerValue : public Value {
+ public:
+  explicit PointerValue(Address value)
+      : Value(Kind::PointerValue), value_(std::move(value)) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::PointerValue;
+  }
+
+  auto address() const -> const Address& { return value_; }
+
+ private:
+  Address value_;
+};
+
 // A bool value.
 class BoolValue : public Value {
  public:

+ 2 - 0
executable_semantics/syntax/lexer.lpp

@@ -38,6 +38,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 %s AFTER_OPERAND
 
 /* table-begin */
+AMPERSAND            "&"
 AND                  "and"
 API                  "api"
 ARROW                "->"
@@ -134,6 +135,7 @@ string_literal        \"([^\\\"\n\v\f\r]|\\.)*\"
 %}
 
  /* table-begin */
+{AMPERSAND}           { return SIMPLE_TOKEN(AMPERSAND);           }
 {AND}                 { return SIMPLE_TOKEN(AND);                 }
 {API}                 { return SIMPLE_TOKEN(API);                 }
 {ARROW}               { return SIMPLE_TOKEN(ARROW);               }

+ 8 - 1
executable_semantics/syntax/parser.ypp

@@ -141,6 +141,7 @@
 %token
   // Most tokens have their spelling defined in lexer.lpp.
   // table-begin
+  AMPERSAND
   AND
   API
   ARROW
@@ -214,7 +215,7 @@
 %nonassoc EQUAL_EQUAL
 %left PLUS MINUS
 %left BINARY_STAR
-%precedence NOT UNARY_MINUS PREFIX_STAR
+%precedence NOT UNARY_MINUS PREFIX_STAR AMPERSAND
 // We need to give the `UNARY_STAR` token a precedence, rather than overriding
 // the precedence of the `expression UNARY_STAR` rule below, because bison
 // compares the precedence of the final token (for a shift) to the precedence
@@ -367,6 +368,12 @@ expression:
           context.source_loc(), Operator::Deref,
           std::vector<Nonnull<Expression*>>({$2}));
     }
+| AMPERSAND expression
+    {
+      $$ = arena->New<PrimitiveOperatorExpression>(
+          context.source_loc(), Operator::AddressOf,
+          std::vector<Nonnull<Expression*>>({$2}));
+    }
 | expression tuple
     { $$ = arena->New<CallExpression>(context.source_loc(), $1, $2); }
 | expression POSTFIX_STAR

+ 24 - 0
executable_semantics/testdata/pointer/basic.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: result: 0
+
+package ExecutableSemanticsTest api;
+
+fn Main() -> i32 {
+  var x: i32 = 5;
+  x = 10; // changes x to 10
+  var p: i32* = &x;
+  *p = 7; // changes x to 7
+  var q: i32* = &*p;
+  *q = 0; // changes x to 0
+  var y: i32 = *p;
+
+  return y;
+}

+ 18 - 0
executable_semantics/testdata/pointer/fail_rvalue_addressof.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/pointer/fail_rvalue_addressof.carbon:16: Argument to & should be an lvalue.
+
+package ExecutableSemanticsTest api;
+
+fn Main() -> i32 {
+  var x: i32 = 5;
+  var p: i32* = &5;
+  return 0;
+}

+ 20 - 0
executable_semantics/testdata/pointer/struct_pointer.carbon

@@ -0,0 +1,20 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: result: 0
+
+package ExecutableSemanticsTest api;
+
+fn Main() -> i32 {
+  var x: auto = {.x = 10, .y = 1};
+  var p: i32* = &x.x;
+  *p = 0;
+
+  return x.x;
+}