[clang][Interp] Support inc/dec operators for pointers
authorTimm Bäder <tbaeder@redhat.com>
Wed, 2 Nov 2022 08:09:23 +0000 (09:09 +0100)
committerTimm Bäder <tbaeder@redhat.com>
Wed, 25 Jan 2023 10:57:05 +0000 (11:57 +0100)
Differential Revision: https://reviews.llvm.org/D137232

clang/lib/AST/Interp/ByteCodeExprGen.cpp
clang/lib/AST/Interp/Interp.h
clang/lib/AST/Interp/Opcodes.td
clang/test/AST/Interp/arrays.cpp

index fa2c74c..dd7d88a 100644 (file)
@@ -1333,24 +1333,44 @@ bool ByteCodeExprGen<Emitter>::VisitUnaryOperator(const UnaryOperator *E) {
   const Expr *SubExpr = E->getSubExpr();
   std::optional<PrimType> T = classify(SubExpr->getType());
 
-  // TODO: Support pointers for inc/dec operators.
   switch (E->getOpcode()) {
   case UO_PostInc: { // x++
     if (!this->visit(SubExpr))
       return false;
 
+    if (T == PT_Ptr) {
+      if (!this->emitIncPtr(E))
+        return false;
+
+      return DiscardResult ? this->emitPopPtr(E) : true;
+    }
+
     return DiscardResult ? this->emitIncPop(*T, E) : this->emitInc(*T, E);
   }
   case UO_PostDec: { // x--
     if (!this->visit(SubExpr))
       return false;
 
+    if (T == PT_Ptr) {
+      if (!this->emitDecPtr(E))
+        return false;
+
+      return DiscardResult ? this->emitPopPtr(E) : true;
+    }
+
     return DiscardResult ? this->emitDecPop(*T, E) : this->emitDec(*T, E);
   }
   case UO_PreInc: { // ++x
     if (!this->visit(SubExpr))
       return false;
 
+    if (T == PT_Ptr) {
+      this->emitLoadPtr(E);
+      this->emitConstUint8(1, E);
+      this->emitAddOffsetUint8(E);
+      return DiscardResult ? this->emitStorePopPtr(E) : this->emitStorePtr(E);
+    }
+
     // Post-inc and pre-inc are the same if the value is to be discarded.
     if (DiscardResult)
       return this->emitIncPop(*T, E);
@@ -1364,6 +1384,13 @@ bool ByteCodeExprGen<Emitter>::VisitUnaryOperator(const UnaryOperator *E) {
     if (!this->visit(SubExpr))
       return false;
 
+    if (T == PT_Ptr) {
+      this->emitLoadPtr(E);
+      this->emitConstUint8(1, E);
+      this->emitSubOffsetUint8(E);
+      return DiscardResult ? this->emitStorePopPtr(E) : this->emitStorePtr(E);
+    }
+
     // Post-dec and pre-dec are the same if the value is to be discarded.
     if (DiscardResult)
       return this->emitDecPop(*T, E);
index ed3accd..9433652 100644 (file)
@@ -140,6 +140,8 @@ bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) {
 /// Interpreter entry point.
 bool Interpret(InterpState &S, APValue &Result);
 
+enum class ArithOp { Add, Sub };
+
 //===----------------------------------------------------------------------===//
 // Add, Sub, Mul
 //===----------------------------------------------------------------------===//
@@ -1112,6 +1114,34 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
   return OffsetHelper<T, false>(S, OpPC);
 }
 
+template <ArithOp Op>
+static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC) {
+  using OneT = Integral<8, false>;
+  const Pointer &Ptr = S.Stk.pop<Pointer>();
+
+  // Get the current value on the stack.
+  S.Stk.push<Pointer>(Ptr.deref<Pointer>());
+
+  // Now the current Ptr again and a constant 1.
+  // FIXME: We shouldn't have to push these two on the stack.
+  S.Stk.push<Pointer>(Ptr.deref<Pointer>());
+  S.Stk.push<OneT>(OneT::from(1));
+  if (!OffsetHelper<OneT, Op == ArithOp::Add>(S, OpPC))
+    return false;
+
+  // Store the new value.
+  Ptr.deref<Pointer>() = S.Stk.pop<Pointer>();
+  return true;
+}
+
+static inline bool IncPtr(InterpState &S, CodePtr OpPC) {
+  return IncDecPtrHelper<ArithOp::Add>(S, OpPC);
+}
+
+static inline bool DecPtr(InterpState &S, CodePtr OpPC) {
+  return IncDecPtrHelper<ArithOp::Sub>(S, OpPC);
+}
+
 /// 1) Pops a Pointer from the stack.
 /// 2) Pops another Pointer from the stack.
 /// 3) Pushes the different of the indices of the two pointers on the stack.
index 058475b..07facb6 100644 (file)
@@ -393,12 +393,21 @@ def AddOffset : AluOpcode;
 // [Pointer, Integral] -> [Pointer]
 def SubOffset : AluOpcode;
 
-// Pointer, Pointer] - [Integral]
+// [Pointer, Pointer] -> [Integral]
 def SubPtr : Opcode {
   let Types = [IntegerTypeClass];
   let HasGroup = 1;
 }
 
+// [Pointer] -> [Pointer]
+def IncPtr : Opcode {
+  let HasGroup = 0;
+}
+// [Pointer] -> [Pointer]
+def DecPtr : Opcode {
+  let HasGroup = 0;
+}
+
 //===----------------------------------------------------------------------===//
 // Binary operators.
 //===----------------------------------------------------------------------===//
index 3c0eaef..accef60 100644 (file)
@@ -235,9 +235,6 @@ constexpr BU bu; // expected-error {{must be initialized by a constant expressio
                  // ref-note {{in call to 'BU()'}}
 
 namespace IncDec {
-  // FIXME: Pointer arithmethic needs to be supported in inc/dec
-  //   unary operators
-#if 0
   constexpr int getNextElem(const int *A, int I) {
     const int *B = (A + I);
     ++B;
@@ -245,6 +242,91 @@ namespace IncDec {
   }
   constexpr int E[] = {1,2,3,4};
 
-  static_assert(getNextElem(E, 1) == 3);
-#endif
+  static_assert(getNextElem(E, 1) == 3, "");
+
+  constexpr int getFirst() {
+    const int *e = E;
+    return *(e++);
+  }
+  static_assert(getFirst() == 1, "");
+
+  constexpr int getFirst2() {
+    const int *e = E;
+    e++;
+    return *e;
+  }
+  static_assert(getFirst2() == 2, "");
+
+  constexpr int getSecond() {
+    const int *e = E;
+    return *(++e);
+  }
+  static_assert(getSecond() == 2, "");
+
+  constexpr int getSecond2() {
+    const int *e = E;
+    ++e;
+    return *e;
+  }
+  static_assert(getSecond2() == 2, "");
+
+  constexpr int getLast() {
+    const int *e = E + 3;
+    return *(e--);
+  }
+  static_assert(getLast() == 4, "");
+
+  constexpr int getLast2() {
+    const int *e = E + 3;
+    e--;
+    return *e;
+  }
+  static_assert(getLast2() == 3, "");
+
+  constexpr int getSecondToLast() {
+    const int *e = E + 3;
+    return *(--e);
+  }
+  static_assert(getSecondToLast() == 3, "");
+
+  constexpr int getSecondToLast2() {
+    const int *e = E + 3;
+    --e;
+    return *e;
+  }
+  static_assert(getSecondToLast2() == 3, "");
+
+  constexpr int bad1() { // ref-error {{never produces a constant expression}}
+    const int *e =  E + 3;
+    e++; // This is fine because it's a one-past-the-end pointer
+    return *e; // expected-note {{read of dereferenced one-past-the-end pointer}} \
+               // ref-note 2{{read of dereferenced one-past-the-end pointer}}
+  }
+  static_assert(bad1() == 0, ""); // expected-error {{not an integral constant expression}} \
+                                  // expected-note {{in call to}} \
+                                  // ref-error {{not an integral constant expression}} \
+                                  // ref-note {{in call to}}
+
+  constexpr int bad2() { // ref-error {{never produces a constant expression}}
+    const int *e = E + 4;
+    e++; // expected-note {{cannot refer to element 5 of array of 4 elements}} \
+         // ref-note 2{{cannot refer to element 5 of array of 4 elements}}
+    return *e; // This is UB as well
+  }
+  static_assert(bad2() == 0, ""); // expected-error {{not an integral constant expression}} \
+                                  // expected-note {{in call to}} \
+                                  // ref-error {{not an integral constant expression}} \
+                                  // ref-note {{in call to}}
+
+
+  constexpr int bad3() { // ref-error {{never produces a constant expression}}
+    const int *e = E;
+    e--; // expected-note {{cannot refer to element -1 of array of 4 elements}} \
+         // ref-note 2{{cannot refer to element -1 of array of 4 elements}}
+    return *e; // This is UB as well
+  }
+   static_assert(bad3() == 0, ""); // expected-error {{not an integral constant expression}} \
+                                   // expected-note {{in call to}} \
+                                   // ref-error {{not an integral constant expression}} \
+                                  // ref-note {{in call to}}
 };