nand created this revision.
nand added reviewers: rsmith, jfb, Bigcheese, dexonsmith.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Implemented string constants and the following pointer operations:

- AddOffset
- SubOffset
- Narrow
- Expand
- LogicalNot

To allow for sensible tests, short-circuiting logical ops were also implemented.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D70087

Files:
  clang/lib/AST/Interp/ByteCodeExprGen.cpp
  clang/lib/AST/Interp/ByteCodeExprGen.h
  clang/lib/AST/Interp/Interp.h
  clang/lib/AST/Interp/Opcodes.td
  clang/lib/AST/Interp/Opcodes/Logical.h
  clang/lib/AST/Interp/Opcodes/Offset.h
  clang/lib/AST/Interp/Program.cpp
  clang/lib/AST/Interp/Program.h
  clang/test/AST/Interp/string.cpp

Index: clang/test/AST/Interp/string.cpp
===================================================================
--- /dev/null
+++ clang/test/AST/Interp/string.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -fexperimental-new-constant-interpreter %s -verify
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only %s -verify
+// expected-no-diagnostics
+
+constexpr bool streq(const char *a, const char *b) {
+  while (*a && *a == *b) {
+    a = a + 1;
+    b = b + 1;
+  }
+  return *a == *b;
+}
+
+constexpr bool is_equal() {
+  const char *a = "Hello";
+  const char *b = "Hello";
+  return streq(a, b);
+}
+
+static_assert(is_equal());
+
+constexpr bool not_equal() {
+  const char *a = "HA";
+  const char *b = "HB";
+  return streq(a, b);
+}
+
+static_assert(!streq("HA", "HB"));
Index: clang/lib/AST/Interp/Program.h
===================================================================
--- clang/lib/AST/Interp/Program.h
+++ clang/lib/AST/Interp/Program.h
@@ -63,6 +63,14 @@
 public:
   Program(Context &Ctx) : Ctx(Ctx) {}
 
+  /// Emits a string literal among global data.
+  unsigned createGlobalString(const StringLiteral *S);
+
+  /// Returns a pointer to a global.
+  Pointer getPtrGlobal(GlobalLocation Idx);
+  /// Returns  a pointer to a global by index.
+  Pointer getPtrGlobal(const ValueDecl *VD);
+
   /// Creates a new function from a code range.
   template <typename... Ts>
   Function *createFunction(const FunctionDecl *Def, Ts &&... Args) {
@@ -95,7 +103,11 @@
 
 private:
   friend class DeclScope;
- /// Reference to the VM context.
+
+  llvm::Optional<GlobalLocation> createGlobal(const DeclTy &D, QualType Ty,
+                                              bool IsStatic, bool IsExtern);
+
+  /// Reference to the VM context.
   Context &Ctx;
   /// Mapping from decls to cached bytecode functions.
   llvm::DenseMap<const FunctionDecl *, std::unique_ptr<Function>> Funcs;
@@ -108,9 +120,38 @@
   /// Custom allocator for global storage.
   using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
 
+  /// Descriptor + storage for a global object.
+  ///
+  /// Global objects never go out of scope, thus they do not track pointers.
+  class Global {
+  public:
+    /// Create a global descriptor for string literals.
+    template <typename... Tys>
+    Global(Tys... Args) : B(std::forward<Tys>(Args)...) {}
+
+    /// Allocates the global in the pool, reserving storate for data.
+    void *operator new(size_t Meta, PoolAllocTy &Alloc, size_t Data) {
+      return Alloc.Allocate(Meta + Data, alignof(void *));
+    }
+
+    /// Return a pointer to the data.
+    char *data() { return B.data(); }
+    /// Return a pointer to the block.
+    Block *block() { return &B; }
+
+  private:
+    /// Required metadata - does not actually track pointers.
+    Block B;
+  };
+
   /// Allocator for globals.
   PoolAllocTy Allocator;
 
+  /// Global objects.
+  std::vector<Global *> Globals;
+  /// Cached global indices.
+  llvm::DenseMap<const void *, unsigned> GlobalIndices;
+
   /// Creates a new descriptor.
   template <typename... Ts> Descriptor *allocateDescriptor(Ts &&... Args) {
     return new (Allocator) Descriptor(std::forward<Ts>(Args)...);
Index: clang/lib/AST/Interp/Program.cpp
===================================================================
--- clang/lib/AST/Interp/Program.cpp
+++ clang/lib/AST/Interp/Program.cpp
@@ -18,6 +18,78 @@
 using namespace clang;
 using namespace clang::interp;
 
+unsigned Program::createGlobalString(const StringLiteral *S) {
+  const size_t CharWidth = S->getCharByteWidth();
+  const size_t BitWidth = CharWidth * Ctx.getCharBit();
+
+  PrimType CharType;
+  switch (CharWidth) {
+  case 1:
+    CharType = PT_Sint8;
+    break;
+  case 2:
+    CharType = PT_Uint16;
+    break;
+  case 4:
+    CharType = PT_Uint32;
+    break;
+  default:
+    llvm_unreachable("unsupported character width");
+  }
+
+  // Create a descriptor for the string.
+  Descriptor *Desc = allocateDescriptor(S, CharType, S->getLength() + 1,
+                                        /*isConst=*/true,
+                                        /*isTemporary=*/false,
+                                        /*isMutable=*/false);
+
+  // Allocate storage for the string.
+  // The byte length does not include the null terminator.
+  unsigned I = Globals.size();
+  unsigned Sz = Desc->getAllocSize();
+  auto *G = new (Allocator, Sz) Global(Desc, /*isStatic=*/true,
+                                       /*isExtern=*/false);
+  Globals.push_back(G);
+
+  // Construct the string in storage.
+  const Pointer Ptr(G->block());
+  for (unsigned I = 0, N = S->getLength(); I <= N; ++I) {
+    Pointer Field = Ptr.atIndex(I).narrow();
+    const uint32_t CodePoint = I == N ? 0 : S->getCodeUnit(I);
+    switch (CharType) {
+    case PT_Sint8: {
+      using T = PrimConv<PT_Sint8>::T;
+      Field.deref<T>() = T::from(CodePoint, BitWidth);
+      break;
+    }
+    case PT_Uint16: {
+      using T = PrimConv<PT_Uint16>::T;
+      Field.deref<T>() = T::from(CodePoint, BitWidth);
+      break;
+    }
+    case PT_Uint32: {
+      using T = PrimConv<PT_Uint32>::T;
+      Field.deref<T>() = T::from(CodePoint, BitWidth);
+      break;
+    }
+    default:
+      llvm_unreachable("unsupported character type");
+    }
+  }
+  return I;
+}
+
+Pointer Program::getPtrGlobal(GlobalLocation G) {
+  assert(G.Index < Globals.size());
+  return Pointer(Globals[G.Index]->block());
+}
+
+Pointer Program::getPtrGlobal(const ValueDecl *VD) {
+  auto It = GlobalIndices.find(VD);
+  assert(It != GlobalIndices.end() && "missing global");
+  return Pointer(Globals[It->second]->block());
+}
+
 Function *Program::getFunction(const FunctionDecl *F) {
   F = F->getDefinition();
   auto It = Funcs.find(F);
@@ -36,4 +108,3 @@
   // A relocation which traps if not resolved.
   return nullptr;
 }
-
Index: clang/lib/AST/Interp/Opcodes/Offset.h
===================================================================
--- /dev/null
+++ clang/lib/AST/Interp/Opcodes/Offset.h
@@ -0,0 +1,86 @@
+//===--- PoitnerArithmetic.h - Pointer arithmetic instructions --*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implementation of the pointer offset opcodes: AddOffset, SubOffset
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_INTERP_OPCODES_OFFSET_H
+#define LLVM_CLANG_AST_INTERP_OPCODES_OFFSET_H
+
+namespace detail {
+
+template <class T, bool Add>
+bool OffsetHelper(InterpState &S, CodePtr OpPC) {
+  // Fetch the pointer and the offset.
+  const T &Offset = S.Stk.pop<T>();
+  const Pointer &Ptr = S.Stk.pop<Pointer>();
+  if (!S.CheckNull(OpPC, Ptr, CSK_ArrayIndex))
+    return false;
+  if (!S.CheckRange(OpPC, Ptr, CSK_ArrayToPointer))
+    return false;
+
+  // Get a version of the index comparable to the type.
+  T Index = T::from(Ptr.getIndex(), Offset.bitWidth());
+  // A zero offset does not change the pointer, but in the case of an array
+  // it has to be adjusted to point to the first element instead of the array.
+  if (Offset.isZero()) {
+    S.Stk.push<Pointer>(Index.isZero() ? Ptr.atIndex(0) : Ptr);
+    return true;
+  }
+  // Arrays of unknown bounds cannot have pointers into them.
+  if (!S.CheckArray(OpPC, Ptr))
+    return false;
+
+  // Compute the largest index into the array.
+  unsigned MaxIndex = Ptr.getNumElems();
+
+  // Helper to report an invalid offset, computed as APSInt.
+  auto InvalidOffset = [&]() {
+    const unsigned Bits = Offset.bitWidth();
+    APSInt APOffset(Offset.toAPSInt().extend(Bits + 2), false);
+    APSInt APIndex(Index.toAPSInt().extend(Bits + 2), false);
+    APSInt NewIndex = Add ? (APIndex + APOffset) : (APIndex - APOffset);
+    S.CCEDiag(S.getSource(OpPC), diag::note_constexpr_array_index)
+        << NewIndex << /*array*/ static_cast<int>(!Ptr.inArray())
+        << static_cast<unsigned>(MaxIndex);
+    return false;
+  };
+
+  // If the new offset would be negative, bail out.
+  if (Add && Offset.isNegative() && (Offset.isMin() || -Offset > Index))
+    return InvalidOffset();
+  if (!Add && Offset.isPositive() && Index < Offset)
+    return InvalidOffset();
+
+  // If the new offset would be out of bounds, bail out.
+  unsigned MaxOffset = MaxIndex - Ptr.getIndex();
+  if (Add && Offset.isPositive() && Offset > MaxOffset)
+    return InvalidOffset();
+  if (!Add && Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset))
+    return InvalidOffset();
+
+  // Offset is valid - compute it on unsigned.
+  int64_t WideIndex = static_cast<int64_t>(Index);
+  int64_t WideOffset = static_cast<int64_t>(Offset);
+  int64_t Result = Add ? (WideIndex + WideOffset) : (WideIndex - WideOffset);
+  S.Stk.push<Pointer>(Ptr.atIndex(static_cast<unsigned>(Result)));
+  return true;
+}
+
+} // namespace detail
+
+template <PrimType Name> bool AddOffset(InterpState &S, CodePtr OpPC) {
+  return detail::OffsetHelper<typename PrimConv<Name>::T, true>(S, OpPC);
+}
+
+template <PrimType Name> bool SubOffset(InterpState &S, CodePtr OpPC) {
+  return detail::OffsetHelper<typename PrimConv<Name>::T, false>(S, OpPC);
+}
+
+#endif
Index: clang/lib/AST/Interp/Opcodes/Logical.h
===================================================================
--- /dev/null
+++ clang/lib/AST/Interp/Opcodes/Logical.h
@@ -0,0 +1,24 @@
+//===--- Logical.h - Logical instructions -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implementation of logical instructions: LNot
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_INTERP_OPCODES_LOGICAL_H
+#define LLVM_CLANG_AST_INTERP_OPCODES_LOGICAL_H
+
+
+template <PrimType Name>
+bool LogicalNot(InterpState &S, CodePtr OpPC) {
+  S.Stk.push<bool>(S.Stk.pop<typename PrimConv<Name>::T>().isZero());
+  return true;
+}
+
+
+#endif
Index: clang/lib/AST/Interp/Opcodes.td
===================================================================
--- clang/lib/AST/Interp/Opcodes.td
+++ clang/lib/AST/Interp/Opcodes.td
@@ -239,6 +239,20 @@
   // Offset of parameter.
   let Args = [ArgUint32];
 }
+// [] -> [Pointer]
+def GetPtrGlobal : Opcode {
+  // Index of global.
+  let Args = [ArgUint32];
+}
+
+//===----------------------------------------------------------------------===//
+// Pointer manipulation
+//===----------------------------------------------------------------------===//
+
+// [Pointer] -> [Pointer]
+def NarrowPtr : Opcode;
+// [Pointer] -> [Pointer]
+def ExpandPtr : Opcode;
 
 //===----------------------------------------------------------------------===//
 // Direct field accessors
@@ -296,6 +310,15 @@
 // [Pointer, Value] -> []
 def StorePop : StoreOpcode {}
 
+//===----------------------------------------------------------------------===//
+// Pointer arithmetic.
+//===----------------------------------------------------------------------===//
+
+// [Pointer, Integral] -> [Pointer]
+def AddOffset : AluOpcode;
+// [Pointer, Integral] -> [Pointer]
+def SubOffset : AluOpcode;
+
 //===----------------------------------------------------------------------===//
 // Binary operators.
 //===----------------------------------------------------------------------===//
@@ -316,6 +339,11 @@
   let Types = [AllTypeClass];
   let HasGroup = 1;
 }
+// [Value] -> [Bool]
+def LogicalNot : Opcode {
+  let Types = [AllTypeClass];
+  let HasGroup = 1;
+}
 
 //===----------------------------------------------------------------------===//
 // Comparison opcodes.
Index: clang/lib/AST/Interp/Interp.h
===================================================================
--- clang/lib/AST/Interp/Interp.h
+++ clang/lib/AST/Interp/Interp.h
@@ -199,6 +199,11 @@
   return true;
 }
 
+inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
+  S.Stk.push<Pointer>(S.P.getPtrGlobal(I));
+  return true;
+}
+
 //===----------------------------------------------------------------------===//
 // Load, Store, Init
 //===----------------------------------------------------------------------===//
@@ -287,6 +292,22 @@
   return false;
 }
 
+//===----------------------------------------------------------------------===//
+// NarrowPtr, ExpandPtr
+//===----------------------------------------------------------------------===//
+
+inline bool NarrowPtr(InterpState &S, CodePtr OpPC) {
+  const Pointer &Ptr = S.Stk.pop<Pointer>();
+  S.Stk.push<Pointer>(Ptr.narrow());
+  return true;
+}
+
+inline bool ExpandPtr(InterpState &S, CodePtr OpPC) {
+  const Pointer &Ptr = S.Stk.pop<Pointer>();
+  S.Stk.push<Pointer>(Ptr.expand());
+  return true;
+}
+
 //===----------------------------------------------------------------------===//
 // Trap
 //===----------------------------------------------------------------------===//
@@ -298,6 +319,8 @@
 }
 
 #include "Opcodes/Comparison.h"
+#include "Opcodes/Logical.h"
+#include "Opcodes/Offset.h"
 
 } // namespace interp
 } // namespace clang
Index: clang/lib/AST/Interp/ByteCodeExprGen.h
===================================================================
--- clang/lib/AST/Interp/ByteCodeExprGen.h
+++ clang/lib/AST/Interp/ByteCodeExprGen.h
@@ -71,9 +71,13 @@
   bool VisitCastExpr(const CastExpr *E);
   bool VisitConstantExpr(const ConstantExpr *E);
   bool VisitIntegerLiteral(const IntegerLiteral *E);
+  bool VisitStringLiteral(const StringLiteral *E);
   bool VisitParenExpr(const ParenExpr *E);
   bool VisitBinaryOperator(const BinaryOperator *E);
   bool VisitUnaryMinus(const UnaryOperator *E);
+  bool VisitUnaryDeref(const UnaryOperator *E);
+  bool VisitUnaryAddrOf(const UnaryOperator *E);
+  bool VisitUnaryLNot(const UnaryOperator *E);
   bool VisitCallExpr(const CallExpr *E);
 
   // Fallback methods for nodes which are not yet implemented.
@@ -150,6 +154,11 @@
   bool emitFunctionCall(const FunctionDecl *Callee, llvm::Optional<PrimType> T,
                         const Expr *Call);
 
+  /// Visits a short-circuit logical operator: && or ||.
+  bool visitShortCircuit(const BinaryOperator *E);
+  /// Compiles an offset calculation.
+  bool visitOffset(const Expr *Ptr, const Expr *Off, const Expr *E, UnaryFn Op);
+  /// Compiles an assignment.
   bool visitAssign(PrimType T, const BinaryOperator *BO);
 
   enum class DerefKind {
Index: clang/lib/AST/Interp/ByteCodeExprGen.cpp
===================================================================
--- clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -229,6 +229,15 @@
   return false;
 }
 
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::VisitStringLiteral(const StringLiteral *E) {
+  if (InitFn)
+    return this->bail(E);
+  if (DiscardResult)
+    return true;
+  return this->emitGetPtrGlobal(P.createGlobalString(E), E);
+}
+
 template <class Emitter>
 bool ByteCodeExprGen<Emitter>::VisitParenExpr(const ParenExpr *PE) {
   return this->Visit(PE->getSubExpr());
@@ -267,22 +276,22 @@
     switch (BO->getOpcode()) {
     case BO_Add: {
       if (isPointer(*LT) && !isPointer(*RT))
-        return this->bail(BO);
-      if (isPointer(*RT) && !isPointer(*LT))
-        return this->bail(BO);
+        return visitOffset(LHS, RHS, BO, &ByteCodeExprGen::emitAddOffset);
+      if (!isPointer(*RT) && isPointer(*LT))
+        return visitOffset(RHS, LHS, BO, &ByteCodeExprGen::emitAddOffset);
       break;
     }
     case BO_Sub: {
       if (isPointer(*LT) && isPointer(*RT))
         return this->bail(BO);
-      if (isPointer(*LT) && isPointer(*RT))
-        return this->bail(BO);
+      if (isPointer(*LT) && !isPointer(*RT))
+        return visitOffset(LHS, RHS, BO, &ByteCodeExprGen::emitSubOffset);
       break;
     }
 
     case BO_LOr:
     case BO_LAnd:
-      return this->bail(BO);
+      return visitShortCircuit(BO);
 
     default:
       break;
@@ -333,6 +342,36 @@
   return this->bail(UM);
 }
 
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::VisitUnaryDeref(const UnaryOperator *E) {
+  if (!this->Visit(E->getSubExpr()))
+    return false;
+  if (DiscardResult)
+    return true;
+  if (needsAdjust(E->getType()))
+    return this->emitNarrowPtr(E);
+  return true;
+}
+
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::VisitUnaryAddrOf(const UnaryOperator *E) {
+  if (!this->Visit(E->getSubExpr()))
+    return false;
+  if (DiscardResult)
+    return true;
+  if (needsAdjust(E->getType()))
+    return this->emitExpandPtr(E);
+  return true;
+}
+
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::VisitUnaryLNot(const UnaryOperator *UO) {
+  if (!this->Visit(UO->getSubExpr()))
+    return false;
+  PrimType T = *classify(UO->getType());
+  return DiscardResult ? true : this->emitLogicalNot(T, UO);
+}
+
 template <class Emitter>
 bool ByteCodeExprGen<Emitter>::VisitCallExpr(const CallExpr *CE) {
   // Emit the pointer to build the return value into.
@@ -415,6 +454,45 @@
   }
 }
 
+
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::visitOffset(const Expr *Ptr, const Expr *Off,
+                                           const Expr *E, UnaryFn Op) {
+  if (!visit(Ptr))
+    return false;
+  if (!visit(Off))
+    return false;
+  if (!(this->*Op)(*classify(Off->getType()), E))
+    return false;
+  return DiscardResult ? this->emitPopPtr(E) : true;
+}
+
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::visitShortCircuit(const BinaryOperator *E) {
+  if (!visitBool(E->getLHS()))
+    return false;
+
+  LabelTy Short = this->getLabel();
+  const bool IsTrue = E->getOpcode() == BO_LAnd;
+  if (DiscardResult) {
+    if (!(IsTrue ? this->jumpFalse(Short) : this->jumpTrue(Short)))
+      return false;
+    if (!this->discard(E->getRHS()))
+      return false;
+  } else {
+    if (!this->emitDupBool(E))
+      return false;
+    if (!(IsTrue ? this->jumpFalse(Short) : this->jumpTrue(Short)))
+      return false;
+    if (!this->emitPopBool(E))
+      return false;
+    if (!visitBool(E->getRHS()))
+      return false;
+  }
+  this->fallthrough(Short);
+  return true;
+}
+
 template <class Emitter>
 bool ByteCodeExprGen<Emitter>::visitAssign(PrimType T,
                                            const BinaryOperator *BO) {
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to