[clang] [clang][bytecode] Add `StringPointer` (PR #216736)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 19 00:46:59 PDT 2026


https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/216736

>From 152277d776ea5152c94ce0d6bf9217e332d68b5f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Fri, 14 Aug 2026 16:46:31 +0200
Subject: [PATCH] StringPOinter

---
 clang/lib/AST/ByteCode/Compiler.cpp           |  17 +-
 clang/lib/AST/ByteCode/Context.cpp            |  68 ++++++--
 clang/lib/AST/ByteCode/EvalEmitter.cpp        |   4 -
 clang/lib/AST/ByteCode/Integral.h             |   8 +
 clang/lib/AST/ByteCode/Interp.cpp             |  36 ++---
 clang/lib/AST/ByteCode/Interp.h               |  64 +++++---
 clang/lib/AST/ByteCode/InterpBuiltin.cpp      | 150 +++++++++++-------
 .../lib/AST/ByteCode/InterpBuiltinBitCast.cpp |  94 +++++++++--
 clang/lib/AST/ByteCode/InterpHelpers.h        |  14 +-
 clang/lib/AST/ByteCode/InterpState.h          |   6 +-
 clang/lib/AST/ByteCode/Opcodes.td             |   4 +
 clang/lib/AST/ByteCode/Pointer.cpp            |  47 ++++--
 clang/lib/AST/ByteCode/Pointer.h              | 144 ++++++++++++++++-
 clang/lib/AST/ByteCode/PrimType.h             |  10 ++
 clang/lib/AST/ByteCode/Primitives.h           |   2 +
 clang/lib/AST/ByteCode/Program.cpp            |  51 ------
 clang/lib/AST/ByteCode/Program.h              |   4 -
 clang/test/AST/ByteCode/builtin-functions.cpp |   2 +-
 clang/test/AST/ByteCode/cxx20.cpp             |   4 +-
 clang/test/AST/ByteCode/strings.cpp           |  46 ++++++
 clang/unittests/AST/ByteCode/Pointer.cpp      |  61 +++++++
 21 files changed, 614 insertions(+), 222 deletions(-)
 create mode 100644 clang/test/AST/ByteCode/strings.cpp

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index a8e2cb4a3c076..9627cc65933d2 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3113,10 +3113,8 @@ bool Compiler<Emitter>::VisitStringLiteral(const StringLiteral *E) {
   if (DiscardResult)
     return true;
 
-  if (!Initializing) {
-    unsigned StringIndex = P.createGlobalString(E);
-    return this->emitGetPtrGlobal(StringIndex, E);
-  }
+  if (!Initializing)
+    return this->emitGetStringPtr(E, E);
 
   // We are initializing an array on the stack.
   const ConstantArrayType *CAT =
@@ -3202,9 +3200,7 @@ bool Compiler<Emitter>::VisitSYCLUniqueStableNameExpr(
   StringLiteral *SL =
       StringLiteral::Create(A, ResultStr, StringLiteralKind::Ordinary,
                             /*Pascal=*/false, ArrayTy, E->getLocation());
-
-  unsigned StringIndex = P.createGlobalString(SL);
-  return this->emitGetPtrGlobal(StringIndex, E);
+  return this->emitGetStringPtr(SL, E);
 }
 
 template <class Emitter>
@@ -3734,11 +3730,8 @@ bool Compiler<Emitter>::VisitPredefinedExpr(const PredefinedExpr *E) {
   if (DiscardResult)
     return true;
 
-  if (!Initializing) {
-    unsigned StringIndex = P.createGlobalString(E->getFunctionName(), E);
-    return this->emitGetPtrGlobal(StringIndex, E);
-  }
-
+  if (!Initializing)
+    return this->emitGetStringPtr(E, E);
   return this->delegate(E->getFunctionName());
 }
 
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index ce7a95ed49c06..de537aad6779e 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -209,17 +209,23 @@ bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr,
     }
 
     if (!Ptr.isLive() || !Ptr.isInitialized() || Ptr.isUnknownSizeArray() ||
-        !Ptr.getFieldDesc()->isPrimitiveArray())
+        !Ptr.inArray())
       return false;
 
     // Must be char.
-    if (Ptr.getFieldDesc()->getElemDataSize() != 1 /*bytes*/)
+    if (Ptr.isBlockPointer() &&
+        Ptr.getFieldDesc()->getElemDataSize() != 1 /*bytes*/)
+      return false;
+    if (Ptr.isStringPointer() &&
+        !Ptr.asStringPointer().getLiteral()->isOrdinary())
       return false;
 
+    bool Limited = false;
     if (Size > Ptr.getNumElems()) {
       S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_past_end)
           << AK_Read;
       Size = Ptr.getNumElems();
+      Limited = true;
     }
 
     if constexpr (std::is_same_v<ResultT, APValue>) {
@@ -236,7 +242,13 @@ bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr,
       assert((std::is_same_v<ResultT, std::string>));
       if (Size < Result.max_size())
         Result.resize(Size);
-      Result.assign(reinterpret_cast<const char *>(Ptr.getRawAddress()), Size);
+
+      const char *Addr = reinterpret_cast<const char *>(Ptr.getRawAddress());
+
+      if (Ptr.isStringPointer())
+        Result.assign(Addr, Size - static_cast<unsigned>(Limited));
+      else
+        Result.assign(Addr, Size);
     }
 
     return true;
@@ -274,11 +286,7 @@ bool Context::evaluateString(State &Parent, const Expr *E,
 
   auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, CodePtr OpPC,
                                             const Pointer &Ptr) {
-    if (!Ptr.isBlockPointer())
-      return false;
-
-    const Descriptor *FieldDesc = Ptr.getFieldDesc();
-    if (!FieldDesc->isPrimitiveArray())
+    if (!Ptr.isReadablePointerType())
       return false;
 
     if (!Ptr.isConst())
@@ -288,6 +296,10 @@ bool Context::evaluateString(State &Parent, const Expr *E,
 
     if (Ptr.elemSize() == 1 /* bytes */) {
       const char *Chars = reinterpret_cast<const char *>(Ptr.getRawAddress());
+      if (Ptr.isStringPointer()) {
+        Result.assign(Chars, N - 1);
+        return true;
+      }
       unsigned Length = strnlen(Chars, N);
       // Wasn't null terminated.
       if (N == Length)
@@ -296,10 +308,22 @@ bool Context::evaluateString(State &Parent, const Expr *E,
       return true;
     }
 
-    PrimType ElemT = FieldDesc->getPrimType();
+    PrimType ElemT;
+    if (Ptr.isBlockPointer()) {
+      ElemT = Ptr.getFieldDesc()->getPrimType();
+    } else {
+      // It may happen here that the string literal has not been decayed or
+      // indexed, so check the element type in that case.
+      assert(Ptr.isStringPointer());
+      if (!Ptr.asStringPointer().Decayed)
+        ElemT =
+            *classify(Ptr.getType()->getAsArrayTypeUnsafe()->getElementType());
+      else
+        ElemT = *classify(Ptr.getType());
+    }
     for (unsigned I = Ptr.getIndex(); I != N; ++I) {
       INT_TYPE_SWITCH(ElemT, {
-        auto Elem = Ptr.elem<T>(I);
+        auto Elem = Ptr.loadElem<T>(I);
         if (Elem.isZero())
           return true;
         Result.push_back(static_cast<char>(Elem));
@@ -324,14 +348,34 @@ std::optional<uint64_t> Context::evaluateStrlen(State &Parent, const Expr *E) {
   std::optional<uint64_t> Result;
   auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, CodePtr OpPC,
                                             const Pointer &Ptr) {
-    if (!Ptr.isBlockPointer())
+    if (!Ptr.isReadablePointerType())
+      return false;
+
+    if (Ptr.isPastEnd())
       return false;
 
+    if (Ptr.isStringPointer()) {
+
+      const auto *Lit = Ptr.asStringPointer().getLiteral();
+      int64_t Off = Ptr.getByteOffset();
+      if (Off < 0)
+        return false;
+
+      unsigned Length = 0;
+      for (uint64_t I = Off; I != Lit->getLength(); ++I) {
+        if (Lit->getCodeUnit(I) == 0)
+          break;
+        ++Length;
+      }
+      Result = Length;
+      return true;
+    }
+
     const Descriptor *FieldDesc = Ptr.getFieldDesc();
     if (!FieldDesc->isPrimitiveArray())
       return false;
 
-    if (Ptr.isDummy() || Ptr.isUnknownSizeArray() || Ptr.isPastEnd())
+    if (Ptr.isDummy() || Ptr.isUnknownSizeArray())
       return false;
 
     PrimType ElemT = FieldDesc->getPrimType();
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index a54b6d2c18fa9..124366194d6bb 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -243,7 +243,6 @@ template <PrimType OpType> bool EvalEmitter::emitRet(SourceInfo Info) {
 }
 
 template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) {
-  // llvm::errs()<< __PRETTY_FUNCTION__ << "Ret\n";
   if (!isActive())
     return true;
 
@@ -268,9 +267,6 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) {
     if (Ptr.isPastEnd())
       return false;
 
-    if (Ptr.pointsToStringLiteral() && Ptr.isArrayRoot())
-      return false;
-
     if (!Ptr.isZero() && !CheckFinalLoad(S, CodePtr(), Ptr))
       return false;
 
diff --git a/clang/lib/AST/ByteCode/Integral.h b/clang/lib/AST/ByteCode/Integral.h
index 170bc8f8f12dd..543d7f7fd43a9 100644
--- a/clang/lib/AST/ByteCode/Integral.h
+++ b/clang/lib/AST/ByteCode/Integral.h
@@ -205,6 +205,10 @@ template <unsigned Bits, bool Signed> class Integral final {
                      CharUnits::fromQuantity(Ptr.Offset),
                      APValue::NoLValuePath{});
     }
+    case IntegralKind::ExprAddress: {
+      return APValue((const Expr *)Ptr.P, CharUnits::fromQuantity(Ptr.Offset),
+                     APValue::NoLValuePath{});
+    }
     case IntegralKind::LabelAddress: {
       return APValue((const Expr *)Ptr.P, CharUnits::Zero(),
                      APValue::NoLValuePath{});
@@ -300,6 +304,9 @@ template <unsigned Bits, bool Signed> class Integral final {
     case IntegralKind::Address:
       OS << Ptr.P << " + " << Ptr.Offset << " (Address)";
       break;
+    case IntegralKind::ExprAddress:
+      OS << Ptr.P << " + " << Ptr.Offset << " (ExprAddress)";
+      break;
     case IntegralKind::BlockAddress:
       OS << Ptr.P << " + " << Ptr.Offset << " (BlockAddress)";
       break;
@@ -333,6 +340,7 @@ template <unsigned Bits, bool Signed> class Integral final {
     case IntegralKind::AddrLabelDiff:
       return Integral(V.getLabel1(), V.getLabel2());
     case IntegralKind::Address:
+    case IntegralKind::ExprAddress:
     case IntegralKind::BlockAddress:
     case IntegralKind::LabelAddress:
     case IntegralKind::FunctionAddress:
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 43020f5ad84c1..41b062e239f12 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -23,6 +23,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/Basic/DiagnosticSema.h"
 #include "clang/Basic/TargetInfo.h"
+#include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringExtras.h"
 
 using namespace clang;
@@ -541,17 +542,6 @@ bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
   return false;
 }
 
-bool CheckRange(InterpState &S, CodePtr OpPC, PtrView Ptr, AccessKinds AK) {
-  if (!Ptr.isOnePastEnd() && !Ptr.isZeroSizeArray())
-    return true;
-  if (S.getLangOpts().CPlusPlus) {
-    const SourceInfo &Loc = S.Current->getSource(OpPC);
-    S.FFDiag(Loc, diag::note_constexpr_access_past_end)
-        << AK << S.Current->getRange(OpPC);
-  }
-  return false;
-}
-
 bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
                 CheckSubobjectKind CSK) {
   if (!Ptr.isElementPastEnd() && !Ptr.isZeroSizeArray())
@@ -897,11 +887,11 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
       S.FFDiag(Src, diag::note_constexpr_access_null) << AK;
     return false;
   }
-  // Block pointers are the only ones we can actually read from.
-  if (!Ptr.isBlockPointer())
+  // Block and string pointers are the only ones we can actually read from.
+  if (!Ptr.isReadablePointerType())
     return false;
 
-  if (!Ptr.block()->isAccessible()) {
+  if (Ptr.isBlockPointer() && !Ptr.block()->isAccessible()) {
     if (!CheckLive(S, OpPC, Ptr, AK))
       return false;
     if (!CheckExtern(S, OpPC, Ptr))
@@ -921,7 +911,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
     return diagnoseUninitialized(S, OpPC, Ptr, AK);
   if (!CheckLifetime(S, OpPC, Ptr, AK))
     return false;
-  if (!CheckTemporary(S, OpPC, Ptr.block(), AK))
+  if (Ptr.isBlockPointer() && !CheckTemporary(S, OpPC, Ptr.block(), AK))
     return false;
 
   if (!CheckMutable(S, OpPC, Ptr))
@@ -931,7 +921,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
   if (isConstexprUnknown(Ptr))
     return false;
 
-  if (!Ptr.isArrayRoot()) {
+  if (Ptr.isBlockPointer() && !Ptr.isArrayRoot()) {
     // According to GCC info page:
     //
     // 6.28 Compound Literals
@@ -965,10 +955,10 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
 /// EvalEmitter to do the final lvalue-to-rvalue conversion.
 bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
   assert(!Ptr.isZero());
-  if (!Ptr.isBlockPointer())
+  if (!Ptr.isReadablePointerType())
     return false;
 
-  if (!Ptr.block()->isAccessible()) {
+  if (Ptr.isBlockPointer() && !Ptr.block()->isAccessible()) {
     if (!CheckLive(S, OpPC, Ptr, AK_Read))
       return false;
     if (!CheckExtern(S, OpPC, Ptr))
@@ -987,7 +977,7 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
     return false;
   if (!Ptr.isInitialized())
     return diagnoseUninitialized(S, OpPC, Ptr, AK_Read);
-  if (!CheckTemporary(S, OpPC, Ptr.block(), AK_Read))
+  if (Ptr.isBlockPointer() && !CheckTemporary(S, OpPC, Ptr.block(), AK_Read))
     return false;
   if (!CheckMutable(S, OpPC, Ptr))
     return false;
@@ -2851,8 +2841,8 @@ bool DiagTypeid(InterpState &S, CodePtr OpPC) {
 
 bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
                                              const Pointer &RHS) {
-  if (!LHS.pointsToStringLiteral() || !RHS.pointsToStringLiteral())
-    return false;
+  assert(LHS.isStringPointer());
+  assert(RHS.isStringPointer());
 
   unsigned LHSOffset = LHS.isOnePastEnd() ? LHS.getNumElems() : LHS.getIndex();
   unsigned RHSOffset = RHS.isOnePastEnd() ? RHS.getNumElems() : RHS.getIndex();
@@ -2879,11 +2869,11 @@ bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
   StringRef Shorter;
   StringRef Longer;
   if (LHSLength < RHSLength) {
-    ShorterCharWidth = LHS.getFieldDesc()->getElemDataSize();
+    ShorterCharWidth = LHSLit->getCharByteWidth();
     Shorter = LHSStr;
     Longer = RHSStr;
   } else {
-    ShorterCharWidth = RHS.getFieldDesc()->getElemDataSize();
+    ShorterCharWidth = RHSLit->getCharByteWidth();
     Shorter = RHSStr;
     Longer = LHSStr;
   }
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 9c90321da1c36..74d45b4f54917 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -34,7 +34,6 @@
 #include "clang/AST/Expr.h"
 #include "llvm/ADT/APFloat.h"
 #include "llvm/ADT/APSInt.h"
-#include "llvm/ADT/ScopeExit.h"
 #include "llvm/Support/Compiler.h"
 #include <type_traits>
 
@@ -1359,18 +1358,6 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     return true;
   }
 
-  // FIXME: The source check here isn't entirely correct.
-  if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() &&
-      LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) {
-    if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
-      const SourceInfo &Loc = S.Current->getSource(OpPC);
-      S.FFDiag(Loc, diag::note_constexpr_literal_comparison)
-          << LHS.toDiagnosticString(S.getASTContext())
-          << RHS.toDiagnosticString(S.getASTContext());
-      return false;
-    }
-  }
-
   if (Pointer::hasSameBase(LHS, RHS)) {
     std::optional<size_t> A = LHS.computeOffsetForComparison(S.getASTContext());
     std::optional<size_t> B = RHS.computeOffsetForComparison(S.getASTContext());
@@ -1380,17 +1367,24 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     S.Stk.push<BoolT>(BoolT::from(Fn(Compare(*A, *B))));
     return true;
   }
-
   // Otherwise we need to do a bunch of extra checks before returning Unordered.
-  if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() &&
-      RHS.getOffset() == 0) {
+
+  if (LHS.isStringPointer() && RHS.isStringPointer() &&
+      arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
+    S.FFDiag(S.Current->getSource(OpPC),
+             diag::note_constexpr_literal_comparison)
+        << LHS.toDiagnosticString(S.getASTContext())
+        << RHS.toDiagnosticString(S.getASTContext());
+    return false;
+  }
+
+  if (LHS.isOnePastEnd() && !RHS.isOnePastEnd()) {
     const SourceInfo &Loc = S.Current->getSource(OpPC);
     S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_past_end)
         << LHS.toDiagnosticString(S.getASTContext());
     return false;
   }
-  if (RHS.isOnePastEnd() && !LHS.isOnePastEnd() && LHS.isBlockPointer() &&
-      LHS.getOffset() == 0) {
+  if (RHS.isOnePastEnd() && !LHS.isOnePastEnd()) {
     const SourceInfo &Loc = S.Current->getSource(OpPC);
     S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_past_end)
         << RHS.toDiagnosticString(S.getASTContext());
@@ -2241,11 +2235,11 @@ bool Load(InterpState &S, CodePtr OpPC) {
   const Pointer &Ptr = S.Stk.peek<Pointer>();
   if (!CheckLoad(S, OpPC, Ptr))
     return false;
-  if (!Ptr.isBlockPointer())
+  if (!Ptr.isReadablePointerType())
     return false;
   if (!Ptr.canDeref(Name))
     return false;
-  S.Stk.push<T>(Ptr.deref<T>());
+  S.Stk.push<T>(Ptr.load<T>());
   return true;
 }
 
@@ -2254,11 +2248,11 @@ bool LoadPop(InterpState &S, CodePtr OpPC) {
   const Pointer &Ptr = S.Stk.pop<Pointer>();
   if (!CheckLoad(S, OpPC, Ptr))
     return false;
-  if (!Ptr.isBlockPointer())
+  if (!Ptr.isReadablePointerType())
     return false;
   if (!Ptr.canDeref(Name))
     return false;
-  S.Stk.push<T>(Ptr.deref<T>());
+  S.Stk.push<T>(Ptr.load<T>());
   return true;
 }
 
@@ -2582,6 +2576,20 @@ std::optional<Pointer> OffsetHelper(InterpState &S, CodePtr OpPC,
       S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
           << N << /*non-array*/ true << 0;
     return Pointer(Ptr.asFunctionPointer().Func, N);
+  } else if (Ptr.isStringPointer()) {
+    int64_t NewOffset;
+    if constexpr (Op == ArithOp::Add)
+      NewOffset = Ptr.getRawOffset() + static_cast<int64_t>(Offset);
+    else
+      NewOffset = Ptr.getRawOffset() - static_cast<int64_t>(Offset);
+    if (NewOffset < 0 ||
+        NewOffset > (Ptr.asStringPointer().getLiteral()->getLength() + 1)) {
+      S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+          << NewOffset << /*non-array*/ false
+          << (Ptr.asStringPointer().getLiteral()->getLength() + 1);
+      return std::nullopt;
+    }
+    return Pointer(Ptr.asStringPointer(), NewOffset);
   } else if (!Ptr.isBlockPointer()) {
     return std::nullopt;
   }
@@ -3020,6 +3028,9 @@ bool CastPointerIntegral(InterpState &S, CodePtr OpPC) {
     } else if (Ptr.isFunctionPointer()) {
       const void *FuncDecl = Ptr.asFunctionPointer().Func->getDecl();
       S.Stk.push<T>(IntegralKind::FunctionAddress, FuncDecl, /*Offset=*/0);
+    } else if (Ptr.isStringPointer()) {
+      S.Stk.push<T>(IntegralKind::ExprAddress,
+                    (const void *)Ptr.asStringPointer().getLiteral(), 0);
     } else {
       S.Stk.push<T>(T::from(Ptr.getIntegerRepresentation()));
     }
@@ -3571,6 +3582,10 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
   }
 
   if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) {
+    if (Ptr.isStringPointer()) {
+      S.Stk.push<Pointer>(Ptr.asStringPointer().decay());
+      return true;
+    }
     S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
     return true;
   }
@@ -3622,6 +3637,11 @@ inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Type *Ty) {
   return true;
 }
 
+inline bool GetStringPtr(InterpState &S, const Expr *Base) {
+  S.Stk.push<Pointer>(Base, S.newStringID());
+  return true;
+}
+
 bool GetMemberPtr(InterpState &S, const ValueDecl *D);
 bool GetMemberPtrBase(InterpState &S);
 bool GetMemberPtrDecl(InterpState &S);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 9386a12ac13c2..12bacaeee5f2a 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -81,7 +81,7 @@ static bool popToAPSInt(InterpState &S, QualType T, APSInt &Out) {
 static bool isReadable(const Pointer &P) {
   if (P.isDummy())
     return false;
-  if (!P.isBlockPointer())
+  if (!P.isReadablePointerType())
     return false;
   if (!P.isLive())
     return false;
@@ -157,6 +157,14 @@ static void assignIntegral(InterpState &S, const Pointer &Dest, PrimType ValueT,
 }
 
 static QualType getElemType(const Pointer &P) {
+  if (P.isStringPointer()) {
+    return P.asStringPointer()
+        .getLiteral()
+        ->getType()
+        ->getAsArrayTypeUnsafe()
+        ->getElementType();
+  }
+
   const Descriptor *Desc = P.getFieldDesc();
   QualType T = Desc->getType();
   if (Desc->isPrimitive())
@@ -296,25 +304,21 @@ static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC,
   if (!CheckLive(S, OpPC, A, AK_Read) || !CheckLive(S, OpPC, B, AK_Read))
     return false;
 
-  if (A.isDummy() || B.isDummy())
-    return false;
-  if (!A.isBlockPointer() || !B.isBlockPointer())
+  if (!A.isReadablePointerType() || !B.isReadablePointerType())
     return false;
-  if (!A.getFieldDesc()->isPrimitiveArray() ||
-      !B.getFieldDesc()->isPrimitiveArray())
+  if (A.isDummy() || B.isDummy())
     return false;
 
   bool IsWide = ID == Builtin::BIwcscmp || ID == Builtin::BIwcsncmp ||
                 ID == Builtin::BI__builtin_wcscmp ||
                 ID == Builtin::BI__builtin_wcsncmp;
-  assert(A.getFieldDesc()->isPrimitiveArray());
-  assert(B.getFieldDesc()->isPrimitiveArray());
 
+  QualType ElemTy = getElemType(A);
   // Different element types shouldn't happen, but with casts they can.
-  if (!S.getASTContext().hasSameUnqualifiedType(getElemType(A), getElemType(B)))
+  if (!S.getASTContext().hasSameUnqualifiedType(ElemTy, getElemType(B)))
     return false;
 
-  PrimType ElemT = *S.getContext().classify(getElemType(A));
+  PrimType ElemT = *S.getContext().classify(ElemTy);
 
   auto returnResult = [&](int V) -> bool {
     pushInteger(S, V, Call->getType());
@@ -323,22 +327,25 @@ static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC,
 
   unsigned IndexA = A.getIndex();
   unsigned IndexB = B.getIndex();
+  unsigned NumElemsA = A.getNumElems();
+  unsigned NumElemsB = B.getNumElems();
   uint64_t Steps = 0;
   for (;; ++IndexA, ++IndexB, ++Steps) {
 
     if (Steps >= Limit)
       break;
-    PtrView PA = A.view().atIndex(IndexA);
-    PtrView PB = B.view().atIndex(IndexB);
-    if (!CheckRange(S, OpPC, PA, AK_Read) ||
-        !CheckRange(S, OpPC, PB, AK_Read)) {
+
+    // Diagnose this as a read of one-past-the-end.
+    if (IndexA >= NumElemsA || IndexB >= NumElemsB) {
+      S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_past_end)
+          << AK_Read << S.Current->getRange(OpPC);
       return false;
     }
 
     if (IsWide) {
       INT_TYPE_SWITCH(ElemT, {
-        T CA = PA.deref<T>();
-        T CB = PB.deref<T>();
+        T CA = A.loadElem<T>(IndexA);
+        T CB = B.loadElem<T>(IndexB);
         if (CA > CB)
           return returnResult(1);
         if (CA < CB)
@@ -349,8 +356,8 @@ static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC,
       continue;
     }
 
-    uint8_t CA = PA.deref<uint8_t>();
-    uint8_t CB = PB.deref<uint8_t>();
+    uint8_t CA = A.loadElem<uint8_t>(IndexA);
+    uint8_t CB = B.loadElem<uint8_t>(IndexB);
 
     if (CA > CB)
       return returnResult(1);
@@ -380,6 +387,27 @@ static bool interp__builtin_strlen(InterpState &S, CodePtr OpPC,
   if (!CheckLive(S, OpPC, StrPtr, AK_Read))
     return false;
 
+  // For string literal pointers, this is pretty simple.
+  if (StrPtr.isStringPointer()) {
+    if (StrPtr.isOnePastEnd())
+      return CheckRange(S, OpPC, StrPtr, AK_Read);
+
+    const auto *Lit = StrPtr.asStringPointer().getLiteral();
+    int64_t Off = StrPtr.getByteOffset();
+    if (Off < 0)
+      return false;
+
+    unsigned Length = 0;
+    for (uint64_t I = Off; I != Lit->getLength(); ++I) {
+      if (Lit->getCodeUnit(I) == 0)
+        break;
+      ++Length;
+    }
+
+    pushInteger(S, Length, Call->getType());
+    return true;
+  }
+
   if (!StrPtr.isBlockPointer())
     return false;
 
@@ -429,34 +457,47 @@ static bool interp__builtin_nan(InterpState &S, CodePtr OpPC,
   if (!CheckLoad(S, OpPC, Arg))
     return false;
 
-  if (!Arg.getFieldDesc()->isPrimitiveArray())
-    return Invalid(S, OpPC);
-
   // Convert the given string to an integer using StringRef's API.
   llvm::APInt Fill;
-  std::string Str;
-  unsigned ArgLength = Arg.getNumElems();
-  bool FoundZero = false;
-  for (unsigned I = 0; I != ArgLength; ++I) {
-    if (!Arg.isElementInitialized(I))
-      return false;
+  if (Arg.isBlockPointer()) {
+    if (!Arg.getFieldDesc()->isPrimitiveArray())
+      return Invalid(S, OpPC);
+
+    std::string Str;
+    unsigned ArgLength = Arg.getNumElems();
+    bool FoundZero = false;
+    for (unsigned I = 0; I != ArgLength; ++I) {
+      if (!Arg.isElementInitialized(I))
+        return false;
 
-    if (Arg.elem<int8_t>(I) == 0) {
-      FoundZero = true;
-      break;
+      if (Arg.loadElem<int8_t>(I) == 0) {
+        FoundZero = true;
+        break;
+      }
+      Str += Arg.elem<char>(I);
     }
-    Str += Arg.elem<char>(I);
-  }
 
-  // If we didn't find a NUL byte, diagnose as a one-past-the-end read.
-  if (!FoundZero)
-    return CheckRange(S, OpPC, Arg.atIndex(ArgLength), AK_Read);
+    // If we didn't find a NUL byte, diagnose as a one-past-the-end read.
+    if (!FoundZero)
+      return CheckRange(S, OpPC, Arg.atIndex(ArgLength), AK_Read);
 
-  // Treat empty strings as if they were zero.
-  if (Str.empty())
-    Fill = llvm::APInt(32, 0);
-  else if (StringRef(Str).getAsInteger(0, Fill))
+    // Treat empty strings as if they were zero.
+    if (Str.empty())
+      Fill = llvm::APInt(32, 0);
+    else if (StringRef(Str).getAsInteger(0, Fill))
+      return false;
+  } else if (Arg.isStringPointer()) {
+    if (!Arg.asStringPointer().getLiteral()->isOrdinary())
+      return false;
+    StringRef Str = Arg.asStringPointer().getLiteral()->getString();
+    // Treat empty strings as if they were zero.
+    if (Str.empty())
+      Fill = llvm::APInt(32, 0);
+    else if (StringRef(Str).getAsInteger(0, Fill))
+      return false;
+  } else {
     return false;
+  }
 
   const llvm::fltSemantics &TargetSemantics =
       S.getASTContext().getFloatTypeSemantics(
@@ -1279,8 +1320,11 @@ static bool interp__builtin_is_aligned_up_down(InterpState &S, CodePtr OpPC,
   }
   assert(FirstArgT == PT_Ptr);
   const Pointer &Ptr = S.Stk.pop<Pointer>();
-  if (!Ptr.isBlockPointer())
+  if (!Ptr.isBlockPointer()) {
+    S.FFDiag(Call->getArg(0), diag::note_constexpr_alignment_compute)
+        << Alignment;
     return false;
+  }
 
   const ValueDecl *PtrDecl = Ptr.getDeclDesc()->asValueDecl();
   // We need a pointer for a declaration here.
@@ -1469,13 +1513,11 @@ interp__builtin_ptrauth_string_discriminator(InterpState &S, CodePtr OpPC,
                                              const InterpFrame *Frame,
                                              const CallExpr *Call) {
   const auto &Ptr = S.Stk.pop<Pointer>();
-  assert(Ptr.getFieldDesc()->isPrimitiveArray());
+  if (!Ptr.isStringPointer())
+    return false;
 
-  // This should be created for a StringLiteral, so always holds at least
-  // one array element.
-  assert(Ptr.getFieldDesc()->getNumElems() >= 1);
   uint64_t Result = getPointerAuthStableSipHash(
-      cast<StringLiteral>(Ptr.getFieldDesc()->asExpr())->getString());
+      cast<StringLiteral>(Ptr.getRootExpr())->getString());
   pushInteger(S, Result, Call->getType());
   return true;
 }
@@ -1973,7 +2015,7 @@ static bool interp__builtin_memcpy(InterpState &S, CodePtr OpPC,
   }
 
   size_t RemainingDestElems;
-  if (DestPtr.getFieldDesc()->isArray()) {
+  if (DestPtr.inArray()) {
     RemainingDestElems = DestPtr.isUnknownSizeArray()
                              ? 0
                              : (DestPtr.getNumElems() - DestPtr.getIndex());
@@ -1997,7 +2039,7 @@ static bool interp__builtin_memcpy(InterpState &S, CodePtr OpPC,
 
   QualType SrcElemType = getElemType(SrcPtr);
   size_t RemainingSrcElems;
-  if (SrcPtr.getFieldDesc()->isArray()) {
+  if (SrcPtr.inArray()) {
     RemainingSrcElems = SrcPtr.isUnknownSizeArray()
                             ? 0
                             : (SrcPtr.getNumElems() - SrcPtr.getIndex());
@@ -2080,7 +2122,7 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
     return true;
   }
 
-  if (!PtrA.isBlockPointer() || !PtrB.isBlockPointer())
+  if (!PtrA.isReadablePointerType() || !PtrB.isReadablePointerType())
     return false;
 
   bool IsWide =
@@ -2106,7 +2148,7 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
   // Now, read both pointers to a buffer and compare those.
   BitcastBuffer BufferA(
       Bits(ASTCtx.getTypeSize(ElemTypeA) * PtrA.getNumElems()));
-  readPointerToBuffer(S.getContext(), PtrA, BufferA, false);
+  readPointerToBuffer(S.getContext(), PtrA, BufferA, /*ReturnOnUninit=*/false);
 
   // FIXME: The swapping here is UNDOING something we do when reading the
   // data into the buffer.
@@ -2115,7 +2157,7 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
 
   BitcastBuffer BufferB(
       Bits(ASTCtx.getTypeSize(ElemTypeB) * PtrB.getNumElems()));
-  readPointerToBuffer(S.getContext(), PtrB, BufferB, false);
+  readPointerToBuffer(S.getContext(), PtrB, BufferB, /*ReturnOnUninit=*/false);
   // FIXME: The swapping here is UNDOING something we do when reading the
   // data into the buffer.
   if (ASTCtx.getTargetInfo().isBigEndian())
@@ -2219,12 +2261,10 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
     return false;
   }
 
-  if (!Ptr.isBlockPointer())
+  if (!Ptr.isReadablePointerType())
     return false;
 
-  QualType ElemTy = Ptr.getFieldDesc()->isArray()
-                        ? Ptr.getFieldDesc()->getElemQualType()
-                        : Ptr.getFieldDesc()->getType();
+  QualType ElemTy = getElemType(Ptr);
   bool IsRawByte = ID == Builtin::BImemchr || ID == Builtin::BI__builtin_memchr;
 
   // Give up on byte-oriented matching against multibyte elements.
@@ -2281,7 +2321,7 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
 
     uint64_t V;
     INT_TYPE_SWITCH_NO_BOOL(
-        ElemT, { V = static_cast<uint64_t>(ElemPtr.deref<T>().toUnsigned()); });
+        ElemT, { V = static_cast<uint64_t>(ElemPtr.load<T>().toUnsigned()); });
 
     if (V == DesiredVal) {
       S.Stk.push<Pointer>(ElemPtr);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
index 2529bfc6c1cbd..6444deef9fc92 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
@@ -78,6 +78,53 @@ using DataFunc =
     }                                                                          \
   } while (0)
 
+// FIXME: It is unfortunate that we have this function at all, but we can read
+// from a StringPointer. In the later callback-based reading and writing paths,
+// we do assume a BlockPointer though.
+static std::pair<Block *, std::unique_ptr<Descriptor>>
+convertToBlockPointer(const Context &Ctx, const StringPointer &SP) {
+  const StringLiteral *S = SP.getLiteral();
+  const size_t CharWidth = S->getCharByteWidth();
+  const size_t BitWidth = CharWidth * Ctx.getCharBit();
+  unsigned StringLength = S->getLength();
+
+  OptPrimType CharType =
+      Ctx.classify(S->getType()->castAsArrayTypeUnsafe()->getElementType());
+  assert(CharType);
+
+  // Create a descriptor for the string.
+  std::unique_ptr<Descriptor> Desc =
+      std::make_unique<Descriptor>(S, S->getType().getTypePtr(), *CharType,
+                                   Descriptor::GlobalMD, StringLength + 1,
+                                   /*IsConst=*/true,
+                                   /*isTemporary=*/false,
+                                   /*isMutable=*/false,
+                                   /*IsVolatile=*/false);
+
+  // Allocate storage for the string.
+  // The byte length does not include the null terminator.
+  // unsigned GlobalIndex = Globals.size();
+  auto *Memory = new std::byte[sizeof(Block) + Desc->getAllocSize()];
+  auto *B = new (Memory) Block(Ctx.getEvalID(), Desc.get(), true, false);
+  B->invokeCtor();
+
+  new (B->rawData()) GlobalInlineDescriptor{GlobalInitState::Initialized};
+
+  Pointer Ptr(B);
+  if (CharWidth == 1) {
+    std::memcpy(&Ptr.elem<char>(0), S->getString().data(), StringLength);
+  } else {
+    // Construct the string in storage.
+    for (unsigned I = 0; I <= StringLength; ++I) {
+      uint32_t CodePoint = I == StringLength ? 0 : S->getCodeUnit(I);
+      INT_TYPE_SWITCH_NO_BOOL(*CharType,
+                              Ptr.elem<T>(I) = T::from(CodePoint, BitWidth););
+    }
+  }
+  Ptr.initializeAllElements();
+  return std::make_pair(std::move(B), std::move(Desc));
+}
+
 /// We use this to recursively iterate over all fields and elements of a pointer
 /// and extract relevant data for a bitcast.
 static Result enumerateData(PtrView P, const Context &Ctx, Bits Offset,
@@ -182,6 +229,17 @@ static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
                                    Bits BitsToRead, DataFunc F,
                                    bool Initialize) {
 
+  if (P.isStringPointer()) {
+    auto [B, Desc] = convertToBlockPointer(Ctx, P.asStringPointer());
+
+    if (enumerateData(Pointer(B).atIndex(P.getIndex()).view(), Ctx,
+                      Bits::zero(), BitsToRead, F,
+                      Initialize) == Result::Failure)
+      return false;
+    delete[] B;
+    return true;
+  }
+
   return enumerateData(P.view(), Ctx, Bits::zero(), BitsToRead, F,
                        Initialize) != Result::Failure;
 }
@@ -525,18 +583,36 @@ using PrimTypeVariant =
 bool clang::interp::DoMemcpy(InterpState &S, CodePtr OpPC,
                              const Pointer &SrcPtr, const Pointer &DestPtr,
                              Bits Size) {
-  assert(SrcPtr.isBlockPointer());
+  assert(SrcPtr.isReadablePointerType());
   assert(DestPtr.isBlockPointer());
 
   llvm::SmallVector<PrimTypeVariant> Values;
-  enumeratePointerFields(
-      SrcPtr, S.getContext(), Size,
-      [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
-          bool PackedBools) -> Result {
-        TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
-        return Result::Success;
-      },
-      false);
+
+  if (SrcPtr.isStringPointer()) {
+    const auto &SP = SrcPtr.asStringPointer();
+
+    auto [B, Desc] = convertToBlockPointer(S.getContext(), SP);
+    enumeratePointerFields(
+        Pointer(B).atIndex(SrcPtr.getIndex()), S.getContext(), Size,
+        [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
+            bool PackedBools) -> Result {
+          TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
+          return Result::Success;
+        },
+        false);
+
+    delete[] B;
+  } else {
+
+    enumeratePointerFields(
+        SrcPtr, S.getContext(), Size,
+        [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
+            bool PackedBools) -> Result {
+          TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
+          return Result::Success;
+        },
+        false);
+  }
 
   unsigned ValueIndex = 0;
   enumeratePointerFields(
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index 1df570ac971c4..61fc864ba62b2 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -43,14 +43,16 @@ bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
 bool CheckDummy(InterpState &S, CodePtr OpPC, const Block *B, AccessKinds AK);
 
 /// Checks if a pointer is in range.
-bool CheckRange(InterpState &S, CodePtr OpPC, PtrView Ptr, AccessKinds AK);
-inline bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
-                       AccessKinds AK) {
-  if (!Ptr.isBlockPointer()) {
-    assert(!Ptr.isOnePastEnd());
+template <typename T>
+bool CheckRange(InterpState &S, CodePtr OpPC, T Ptr, AccessKinds AK) {
+  if (!Ptr.isOnePastEnd() && !Ptr.isZeroSizeArray())
     return true;
+  if (S.getLangOpts().CPlusPlus) {
+    const SourceInfo &Loc = S.Current->getSource(OpPC);
+    S.FFDiag(Loc, diag::note_constexpr_access_past_end)
+        << AK << S.Current->getRange(OpPC);
   }
-  return CheckRange(S, OpPC, Ptr.view(), AK);
+  return false;
 }
 
 /// Checks if a field from which a pointer is going to be derived is valid.
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index 36302889a499a..8b3c2a0e7dd5a 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -157,12 +157,14 @@ class InterpState final : public State {
   /// Return if we're checking if a global variable has a constant destructor
   /// and the given pointer is pointing to the variable we're checking that for.
   bool checkingConstantDestruction(const Pointer &Ptr) const {
-    return checkingConstantDestruction(Ptr.getDeclDesc()->asVarDecl());
+    return checkingConstantDestruction(Ptr.getRootVarDecl());
   }
   bool checkingConstantDestruction(const VarDecl *VD) const {
     return EvalKind == EvaluationKind::Dtor && VD == EvaluatingDecl;
   }
 
+  unsigned newStringID() { return StringID++; }
+
 private:
   friend class EvaluationResult;
   friend class InterpStateCCOverride;
@@ -199,6 +201,8 @@ class InterpState final : public State {
   /// ID identifying this evaluation.
   const unsigned EvalID;
 
+  unsigned StringID = 0;
+
   EvaluationKind EvalKind = EvaluationKind::None;
 
   /// Things needed to do speculative execution.
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 74d3cc84e06ab..6c7deafa4c4c3 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -1043,3 +1043,7 @@ def PushCC : SuccessOpcode {
 def PopCC : SuccessOpcode;
 def PushMSVCCE : SuccessOpcode;
 def PopMSVCCE : SuccessOpcode;
+
+def GetStringPtr : SuccessOpcode {
+  let Args = [ArgExpr];
+}
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index 55903c4cff70c..ce5c9cc0a3f98 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -59,6 +59,9 @@ Pointer::Pointer(const Pointer &P)
   case Storage::Typeid:
     Typeid = P.Typeid;
     break;
+  case Storage::String:
+    Str = P.Str;
+    break;
   }
 }
 
@@ -78,6 +81,8 @@ Pointer::Pointer(Pointer &&P) : Offset(P.Offset), StorageKind(P.StorageKind) {
   case Storage::Typeid:
     Typeid = P.Typeid;
     break;
+  case Storage::String:
+    Str = P.Str;
   }
 }
 
@@ -127,6 +132,9 @@ Pointer &Pointer::operator=(const Pointer &P) {
     break;
   case Storage::Typeid:
     Typeid = P.Typeid;
+    break;
+  case Storage::String:
+    Str = P.Str;
   }
   return *this;
 }
@@ -166,6 +174,9 @@ Pointer &Pointer::operator=(Pointer &&P) {
     break;
   case Storage::Typeid:
     Typeid = P.Typeid;
+    break;
+  case Storage::String:
+    Str = P.Str;
   }
   return *this;
 }
@@ -206,6 +217,13 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
                    CharUnits::Zero(), {},
                    /*OnePastTheEnd=*/false, /*IsNull=*/false);
   } break;
+  case Storage::String:
+    if (Offset != 0 || Str.Decayed)
+      Path.push_back(APValue::LValuePathEntry::ArrayIndex(Offset));
+
+    return APValue(APValue::LValueBase(Str.Base),
+                   CharUnits::fromQuantity(Offset * elemSize()), Path,
+                   /*OnePastTheEnd=*/false, /*IsNull=*/false);
   }
 
   assert(isBlockPointer());
@@ -366,6 +384,11 @@ void Pointer::print(llvm::raw_ostream &OS) const {
     OS << "(Typeid) { " << (const void *)asTypeidPointer().TypePtr << ", "
        << (const void *)asTypeidPointer().TypeInfoType << " + " << Offset
        << "}";
+    break;
+  case Storage::String:
+    OS << "(String) { " << (const void *)Str.getLiteral() << ' ';
+    Str.getLiteral()->outputString(OS);
+    OS << ". ID: " << Str.ID << " + " << Offset << "}";
   }
 }
 
@@ -390,6 +413,8 @@ Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const {
     return getIntegerRepresentation();
   case Storage::Typeid:
     return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+  case Storage::String:
+    return reinterpret_cast<uintptr_t>(Str.getLiteral()) + Offset;
   }
 
   auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
@@ -468,6 +493,8 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
     return getIntegerRepresentation();
   case Storage::Typeid:
     return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+  case Storage::String:
+    return Offset * Str.getLiteral()->getCharByteWidth();
   }
 
   auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
@@ -821,6 +848,8 @@ bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
     return true;
   if (A.isTypeidPointer() && B.isTypeidPointer())
     return A.asTypeidPointer().TypePtr == B.asTypeidPointer().TypePtr;
+  if (A.isStringPointer() && B.isStringPointer())
+    return A.Str.ID == B.Str.ID && A.Str.getLiteral() == B.Str.getLiteral();
 
   if (A.StorageKind != B.StorageKind)
     return false;
@@ -886,17 +915,6 @@ bool Pointer::pointsToLiteral() const {
   return E && !isa<MaterializeTemporaryExpr, StringLiteral>(E);
 }
 
-bool Pointer::pointsToStringLiteral() const {
-  if (isZero() || !isBlockPointer())
-    return false;
-
-  if (block()->isDynamic())
-    return false;
-
-  const Expr *E = block()->getDescriptor()->asExpr();
-  return isa_and_nonnull<StringLiteral>(E);
-}
-
 bool Pointer::pointsToLabel() const {
   if (isZero() || !isBlockPointer())
     return false;
@@ -1142,9 +1160,12 @@ std::optional<APValue> Pointer::toRValue(const Context &Ctx,
   if (OptPrimType T = Ctx.classify(ResultType)) {
     if (!canDeref(*T))
       return std::nullopt;
-    TYPE_SWITCH(*T, return this->deref<T>().toAPValue(ASTCtx));
+    TYPE_SWITCH(*T, return this->load<T>().toAPValue(ASTCtx));
   }
 
+  if (!isBlockPointer())
+    return std::nullopt;
+
   // Return the composite type.
   APValue Result;
   if (!Composite(ResultType, view(), Result))
@@ -1161,6 +1182,8 @@ const VarDecl *Pointer::getRootVarDecl() const {
 const Expr *Pointer::getRootExpr() const {
   if (isBlockPointer())
     return getDeclDesc()->asExpr();
+  if (isStringPointer())
+    return Str.getLiteral();
   return nullptr;
 }
 
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index fa16c28712ac5..d6e1177d3d249 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -370,7 +370,20 @@ struct TypeidPointer {
   const Type *TypeInfoType;
 };
 
-enum class Storage { Int, Block, Fn, Typeid };
+struct StringPointer {
+  const Expr *Base = nullptr;
+  unsigned ID = 0;
+  bool Decayed = false;
+
+  StringPointer decay() const { return StringPointer{Base, ID, true}; }
+  const StringLiteral *getLiteral() const {
+    if (const auto *PE = dyn_cast<PredefinedExpr>(Base))
+      return PE->getFunctionName();
+    return cast<StringLiteral>(Base);
+  }
+};
+
+enum class Storage { Int, Block, Fn, Typeid, String };
 
 /// A pointer to a memory block, live or dead.
 ///
@@ -420,6 +433,10 @@ class Pointer {
     Typeid.TypePtr = TypePtr;
     Typeid.TypeInfoType = TypeInfoType;
   }
+  Pointer(const Expr *Base, unsigned Id)
+      : Offset(0), StorageKind(Storage::String), Str{Base, Id} {}
+  Pointer(StringPointer Str, uint64_t Offset = 0)
+      : Offset(Offset), StorageKind(Storage::String), Str(Str) {}
 
   Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
   explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
@@ -438,6 +455,8 @@ class Pointer {
 
     if (isFunctionPointer())
       return P.Fn.Func == Fn.Func && P.Offset == Offset;
+    if (isStringPointer())
+      return Str.Base == P.Str.Base && Offset == P.Offset;
 
     return P.view() == view();
   }
@@ -469,12 +488,18 @@ class Pointer {
 
   /// Offsets a pointer inside an array.
   [[nodiscard]] Pointer atIndex(uint64_t Idx) const {
-    if (isIntegralPointer())
+    switch (StorageKind) {
+    case Storage::Int:
       return Pointer(Int.Value, Int.Ty, Idx);
-    if (isFunctionPointer())
+    case Storage::Block:
+      return Pointer(view().atIndex(Idx));
+    case Storage::Fn:
       return Pointer(Fn.Func, Idx);
-
-    return Pointer(view().atIndex(Idx));
+    case Storage::String:
+      return Pointer(Str, Idx);
+    default:
+      llvm_unreachable("Unexpected pointer type in atIndex()");
+    }
   }
 
   /// Creates a pointer to a field.
@@ -514,6 +539,7 @@ class Pointer {
     case Storage::Fn:
       return !Fn.Func;
     case Storage::Typeid:
+    case Storage::String:
       return false;
     }
     llvm_unreachable("Unknown clang::interp::Storage enum");
@@ -581,6 +607,13 @@ class Pointer {
       return Fn.Func->getDecl()->getType();
     case Storage::Typeid:
       return QualType(Typeid.TypeInfoType, 0);
+    case Storage::String:
+      if (Str.Decayed)
+        return Str.getLiteral()
+            ->getType()
+            ->getAsArrayTypeUnsafe()
+            ->getElementType();
+      return Str.getLiteral()->getType();
     }
     llvm_unreachable("Unhandled StorageKind");
   }
@@ -596,6 +629,8 @@ class Pointer {
       // FIXME: Remove this and handle int ptrs specially?
       return 1;
     }
+    if (isStringPointer())
+      return Str.getLiteral()->getCharByteWidth();
 
     return view().elemSize();
   }
@@ -619,6 +654,8 @@ class Pointer {
   bool inArray() const {
     if (isBlockPointer())
       return view().inArray();
+    if (isStringPointer())
+      return true;
     return false;
   }
   bool inUnion() const {
@@ -676,11 +713,16 @@ class Pointer {
     assert(isTypeidPointer());
     return Typeid;
   }
+  [[nodiscard]] const StringPointer &asStringPointer() const {
+    assert(isStringPointer());
+    return Str;
+  }
 
   bool isBlockPointer() const { return StorageKind == Storage::Block; }
   bool isIntegralPointer() const { return StorageKind == Storage::Int; }
   bool isFunctionPointer() const { return StorageKind == Storage::Fn; }
   bool isTypeidPointer() const { return StorageKind == Storage::Typeid; }
+  bool isStringPointer() const { return StorageKind == Storage::String; }
 
   /// Returns the record descriptor of a class.
   const Record *getRecord() const {
@@ -770,6 +812,8 @@ class Pointer {
   bool isConst() const {
     if (isIntegralPointer())
       return true;
+    if (isStringPointer())
+      return true;
     return view().isConst();
   }
   bool isConstInMutable() const {
@@ -805,8 +849,12 @@ class Pointer {
     return Offset;
   }
 
+  uint64_t getRawOffset() const { return Offset; }
+
   /// Returns the number of elements.
   unsigned getNumElems() const {
+    if (isStringPointer())
+      return Str.getLiteral()->getLength() + 1;
     if (!isBlockPointer())
       return ~0u;
     return view().getNumElems();
@@ -814,15 +862,20 @@ class Pointer {
 
   const Block *block() const { return BS.Pointee; }
 
-  /// If backed by actual data (i.e. a block pointer), return
+  /// If backed by actual data (i.e. a block or string pointer), return
   /// an address to that data.
   const std::byte *getRawAddress() const {
+    if (isStringPointer())
+      return reinterpret_cast<const std::byte *>(
+          Str.getLiteral()->getBytes().data());
     assert(isBlockPointer());
     return BS.Pointee->rawData() + Offset;
   }
 
   /// Returns the index into an array.
   int64_t getIndex() const {
+    if (isStringPointer())
+      return Offset;
     if (!isBlockPointer())
       return getIntegerRepresentation();
 
@@ -831,6 +884,8 @@ class Pointer {
 
   /// Checks if the index is one past end.
   bool isOnePastEnd() const {
+    if (isStringPointer())
+      return Offset == (Str.getLiteral()->getLength() + 1);
     if (!isBlockPointer())
       return false;
 
@@ -847,6 +902,8 @@ class Pointer {
   bool isPastEnd() const {
     if (isIntegralPointer())
       return false;
+    if (isStringPointer())
+      return Offset >= Str.getLiteral()->getLength();
 
     return !isZero() && Offset > BS.Pointee->getSize();
   }
@@ -865,6 +922,20 @@ class Pointer {
 
   /// Checks whether the pointer can be dereferenced to the given PrimType.
   bool canDeref(PrimType T) const {
+    if (isStringPointer()) {
+      switch (Str.getLiteral()->getCharByteWidth()) {
+      case 1:
+        return T == PT_Sint8 || T == PT_Uint8;
+      case 2:
+        return T == PT_Sint16 || T == PT_Uint16;
+      case 4:
+        return T == PT_Sint32 || T == PT_Uint32;
+      }
+
+      return false;
+    }
+
+    assert(isBlockPointer());
     if (const Descriptor *FieldDesc = getFieldDesc()) {
       return (FieldDesc->isPrimitive() || FieldDesc->isPrimitiveArray()) &&
              FieldDesc->getPrimType() == T;
@@ -879,10 +950,36 @@ class Pointer {
     assert(BS.Pointee);
     assert(isDereferencable());
     assert(Offset + sizeof(T) <= BS.Pointee->getDescriptor()->getAllocSize());
-
     return view().deref<T>();
   }
 
+  template <typename T> T load() const {
+    assert(isLive() && "Invalid pointer");
+    if (isBlockPointer()) {
+      assert(BS.Pointee);
+      assert(isDereferencable());
+      assert(Offset + sizeof(T) <= BS.Pointee->getDescriptor()->getAllocSize());
+      return view().deref<T>();
+    }
+
+    if (isStringPointer()) {
+      const StringLiteral *Lit = Str.getLiteral();
+
+      if constexpr (isFixedSizeIntegralType<T>()) {
+        // The literal does not include the nul byte.
+        if (Offset >= Lit->getLength())
+          return T::from('\0');
+        return T::from(Lit->getCodeUnit(Offset));
+      } else if constexpr (std::is_integral_v<T>) {
+        if (Offset >= Lit->getLength())
+          return '\0';
+        return Lit->getCodeUnit(Offset);
+      }
+    }
+
+    llvm_unreachable("Unexpected pointer type in load()");
+  }
+
   /// Dereferences the element at index \p I.
   /// This is equivalent to atIndex(I).deref<T>().
   template <typename T> T &elem(unsigned I) const {
@@ -896,6 +993,33 @@ class Pointer {
     return view().elem<T>(I);
   }
 
+  template <typename T> T loadElem(unsigned I) const {
+    assert(isLive() && "Invalid pointer");
+    if (isBlockPointer()) {
+      assert(BS.Pointee);
+      assert(isDereferencable());
+      assert(getFieldDesc()->isPrimitiveArray());
+      assert(I < getFieldDesc()->getNumElems());
+
+      return view().elem<T>(I);
+    }
+
+    assert(isStringPointer());
+    const StringLiteral *Lit = Str.getLiteral();
+    unsigned Index = Offset + I;
+    if constexpr (isFixedSizeIntegralType<T>()) {
+      // The literal does not include the nul byte.
+      if (Index >= Lit->getLength())
+        return T::from('\0');
+      return T::from(Lit->getCodeUnit(Index));
+    } else if constexpr (std::is_integral_v<T>) {
+      if (Index >= Lit->getLength())
+        return '\0';
+      return Lit->getCodeUnit(Index);
+    }
+    llvm_unreachable("Unexpected pointer type in loadElem()");
+  }
+
   bool isConstexprUnknown() const {
     if (!isBlockPointer())
       return false;
@@ -917,6 +1041,10 @@ class Pointer {
     return true;
   }
 
+  bool isReadablePointerType() const {
+    return StorageKind == Storage::Block || StorageKind == Storage::String;
+  }
+
   /// Initializes a field.
   void initialize() const {
     if (!isBlockPointer())
@@ -1010,7 +1138,6 @@ class Pointer {
   /// Whether this points to a block that's been created for a "literal lvalue",
   /// i.e. a non-MaterializeTemporaryExpr Expr.
   bool pointsToLiteral() const;
-  bool pointsToStringLiteral() const;
   /// Whether this points to a block created for an AddrLabelExpr.
   bool pointsToLabel() const;
   /// Returns the AddrLabelExpr the Pointer points to, if any.
@@ -1073,6 +1200,7 @@ class Pointer {
     BlockPointer BS;
     FunctionPointer Fn;
     TypeidPointer Typeid;
+    StringPointer Str;
   };
 };
 
diff --git a/clang/lib/AST/ByteCode/PrimType.h b/clang/lib/AST/ByteCode/PrimType.h
index 8f725942fedb9..9423b2bcb6308 100644
--- a/clang/lib/AST/ByteCode/PrimType.h
+++ b/clang/lib/AST/ByteCode/PrimType.h
@@ -146,6 +146,16 @@ template <typename T> constexpr bool isIntegralOrPointer() {
          std::is_same_v<T, Integral<64, true>>;
 }
 
+template <typename T> constexpr bool isFixedSizeIntegralType() {
+  return std::is_same_v<T, Char<false>> || std::is_same_v<T, Char<true>> ||
+         std::is_same_v<T, Integral<16, false>> ||
+         std::is_same_v<T, Integral<16, true>> ||
+         std::is_same_v<T, Integral<32, false>> ||
+         std::is_same_v<T, Integral<32, true>> ||
+         std::is_same_v<T, Integral<64, false>> ||
+         std::is_same_v<T, Integral<64, true>>;
+}
+
 /// Mapping from primitive types to their representation.
 template <PrimType T> struct PrimConv;
 template <> struct PrimConv<PT_Sint8> {
diff --git a/clang/lib/AST/ByteCode/Primitives.h b/clang/lib/AST/ByteCode/Primitives.h
index e2d3dacea5726..8b109cb5af0a0 100644
--- a/clang/lib/AST/ByteCode/Primitives.h
+++ b/clang/lib/AST/ByteCode/Primitives.h
@@ -26,6 +26,8 @@ enum class IntegralKind : uint8_t {
   Number = 0,
   /// A pointer to a ValueDecl.
   Address,
+  /// A pointer to an Expr.
+  ExprAddress,
   /// A pointer to an interp::Block.
   BlockAddress,
   /// A pointer to a AddrLabelExpr.
diff --git a/clang/lib/AST/ByteCode/Program.cpp b/clang/lib/AST/ByteCode/Program.cpp
index 564d2d8fc422d..975cace8b2ee7 100644
--- a/clang/lib/AST/ByteCode/Program.cpp
+++ b/clang/lib/AST/ByteCode/Program.cpp
@@ -7,10 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "Program.h"
-#include "Char.h"
 #include "Context.h"
 #include "Function.h"
-#include "Integral.h"
 #include "PrimType.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
@@ -32,55 +30,6 @@ const void *Program::getNativePointer(unsigned Idx) const {
   return NativePointers[Idx];
 }
 
-unsigned Program::createGlobalString(const StringLiteral *S, const Expr *Base) {
-  const size_t CharWidth = S->getCharByteWidth();
-  const size_t BitWidth = CharWidth * Ctx.getCharBit();
-  unsigned StringLength = S->getLength();
-
-  OptPrimType CharType =
-      Ctx.classify(S->getType()->castAsArrayTypeUnsafe()->getElementType());
-  assert(CharType);
-
-  if (!Base)
-    Base = S;
-
-  // Create a descriptor for the string.
-  Descriptor *Desc =
-      allocateDescriptor(Base, S->getType().getTypePtr(), *CharType,
-                         Descriptor::GlobalMD, StringLength + 1,
-                         /*IsConst=*/true,
-                         /*isTemporary=*/false,
-                         /*isMutable=*/false,
-                         /*IsVolatile=*/false);
-
-  // Allocate storage for the string.
-  // The byte length does not include the null terminator.
-  unsigned GlobalIndex = Globals.size();
-  unsigned Sz = Desc->getAllocSize();
-  auto *G = new (Allocator, Sz) Global(Ctx.getEvalID(), Desc, /*IsStatic=*/true,
-                                       /*IsExtern=*/false);
-  G->block()->invokeCtor();
-
-  new (G->block()->rawData())
-      GlobalInlineDescriptor{GlobalInitState::Initialized};
-  Globals.push_back(G);
-
-  const Pointer Ptr(G->block());
-  if (CharWidth == 1) {
-    std::memcpy(&Ptr.elem<char>(0), S->getString().data(), StringLength);
-  } else {
-    // Construct the string in storage.
-    for (unsigned I = 0; I <= StringLength; ++I) {
-      uint32_t CodePoint = I == StringLength ? 0 : S->getCodeUnit(I);
-      INT_TYPE_SWITCH_NO_BOOL(*CharType,
-                              Ptr.elem<T>(I) = T::from(CodePoint, BitWidth););
-    }
-  }
-  Ptr.initializeAllElements();
-
-  return GlobalIndex;
-}
-
 Pointer Program::getPtrGlobal(unsigned Idx) const {
   assert(Idx < Globals.size());
   return Pointer(Globals[Idx]->block());
diff --git a/clang/lib/AST/ByteCode/Program.h b/clang/lib/AST/ByteCode/Program.h
index c2299a1e10191..c879b5e82dda7 100644
--- a/clang/lib/AST/ByteCode/Program.h
+++ b/clang/lib/AST/ByteCode/Program.h
@@ -63,10 +63,6 @@ class Program final {
   /// Returns the value of a marshalled native pointer.
   const void *getNativePointer(unsigned Idx) const;
 
-  /// Emits a string literal among global data.
-  unsigned createGlobalString(const StringLiteral *S,
-                              const Expr *Base = nullptr);
-
   /// Returns a pointer to a global.
   Pointer getPtrGlobal(unsigned Idx) const;
 
diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp b/clang/test/AST/ByteCode/builtin-functions.cpp
index 87ffb1cc5b609..55ef1a2128c14 100644
--- a/clang/test/AST/ByteCode/builtin-functions.cpp
+++ b/clang/test/AST/ByteCode/builtin-functions.cpp
@@ -180,7 +180,7 @@ namespace WcsCmp {
 
 /// Copied from constant-expression-cxx11.cpp
 namespace strlen {
-constexpr const char *a = "foo\0quux";
+  constexpr const char *a = "foo\0quux";
   constexpr char b[] = "foo\0quux";
   constexpr int f() { return 'u'; }
   constexpr char c[] = { 'f', 'o', 'o', 0, 'q', f(), 'u', 'x', 0 };
diff --git a/clang/test/AST/ByteCode/cxx20.cpp b/clang/test/AST/ByteCode/cxx20.cpp
index 70ff1045a74da..b06a3ed9149cb 100644
--- a/clang/test/AST/ByteCode/cxx20.cpp
+++ b/clang/test/AST/ByteCode/cxx20.cpp
@@ -119,8 +119,8 @@ static_assert(!b2);
 constexpr auto name1() { return "name1"; }
 constexpr auto name2() { return "name2"; }
 
-constexpr auto b3 = name1() == name1(); // ref-error {{must be initialized by a constant expression}} \
-                                        // ref-note {{comparison of addresses of potentially overlapping literals}}
+constexpr auto b3 = name1() == name1(); // both-error {{must be initialized by a constant expression}} \
+                                        // both-note {{comparison of addresses of potentially overlapping literals}}
 constexpr auto b4 = name1() == name2();
 static_assert(!b4);
 
diff --git a/clang/test/AST/ByteCode/strings.cpp b/clang/test/AST/ByteCode/strings.cpp
new file mode 100644
index 0000000000000..33fca213f529a
--- /dev/null
+++ b/clang/test/AST/ByteCode/strings.cpp
@@ -0,0 +1,46 @@
+// RUN: %clang_cc1 -triple x86_64-linux -verify=both,expected %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -triple x86_64-linux -verify=both,ref      %s
+
+
+
+static_assert("foo"[0] == 'f');
+static_assert("foo"[1] == 'o');
+static_assert("foo"[2] == 'o');
+static_assert("foo"[3] == '\0');
+
+static_assert(+"foo" == +"foo"); // both-error {{not an integral constant expression}} \
+                                 // both-note {{comparison of addresses of potentially overlapping literals}}
+
+static_assert("foo"[4] == '\0'); // both-error {{not an integral constant expression}} \
+                                 // both-note {{read of dereferenced one-past-the-end pointer}}
+
+static_assert("foo"[5] == '\0'); // both-error {{not an integral constant expression}} \
+                                 // both-note {{cannot refer to element 5 of array of 4 elements}}
+
+constexpr const wchar_t *wide = L"bar";
+static_assert(wide[0] == L'b', "");
+
+constexpr const char32_t *u32 = U"abc";
+static_assert(u32[1] == U'b', "");
+
+constexpr int testMemcpy() {
+  char s[5] = {0, 0, 0, 0, 0};
+  __builtin_memcpy(s, "abcd", 5);
+  return s[0] == 'a';
+}
+static_assert(testMemcpy() == 1, "");
+
+constexpr const auto *wp = L"abc";
+static_assert(&wp[2] - &wp[0] == 2);
+
+
+constexpr int checkMemcpy() {
+  char a[3] = {};
+
+  __builtin_memcpy(a, &"abcdef"[3], 3);
+  return __builtin_strncmp(a, "def", 3) == 0;
+}
+static_assert(checkMemcpy());
+
+
+
diff --git a/clang/unittests/AST/ByteCode/Pointer.cpp b/clang/unittests/AST/ByteCode/Pointer.cpp
index 0b93c40aaa202..8661903df53c0 100644
--- a/clang/unittests/AST/ByteCode/Pointer.cpp
+++ b/clang/unittests/AST/ByteCode/Pointer.cpp
@@ -272,3 +272,64 @@ TEST(Pointer, TypesPrimitive) {
     ASSERT_EQ(GlobalPtr, GlobalPtr.atIndex(2).narrow().expand().getArray());
   }
 }
+
+TEST(Pointer, Strings) {
+  constexpr char Code[] = "constexpr const char *str1 = \"foobar\";\n"
+                          "constexpr const auto *str2 = L\"foobar\";\n"
+                          "constexpr const auto *c = &L\"foobar\"[5];\n";
+  auto AST = tooling::buildASTFromCodeWithArgs(
+      Code, {"-fexperimental-new-constant-interpreter"});
+  ASTContext &ASTCtx = AST->getASTContext();
+  const VarDecl *D =
+      match(varDecl(hasGlobalStorage(), hasName("str1")).bind("str1"),
+            ASTCtx)[0]
+          .getNodeAs<VarDecl>("str1");
+  ASSERT_NE(D, nullptr);
+
+  const auto &Ctx = AST->getASTContext().getInterpContext();
+  Program &Prog = Ctx.getProgram();
+  ASSERT_TRUE(Prog.getGlobal(D));
+
+  Pointer GlobalPtr = Prog.getPtrGlobal(*Prog.getGlobal(D));
+  ASSERT_TRUE(GlobalPtr.isBlockPointer());
+  ASSERT_TRUE(GlobalPtr.getFieldDesc()->getPrimType() == PT_Ptr);
+
+  Pointer Pointee = GlobalPtr.load<Pointer>();
+  ASSERT_TRUE(Pointee.isStringPointer());
+  ASSERT_EQ(Pointee.getNumElems(), 7u);
+  ASSERT_EQ(Pointee.elemSize(), 1u);
+
+  D = match(varDecl(hasGlobalStorage(), hasName("str2")).bind("str2"),
+            ASTCtx)[0]
+          .getNodeAs<VarDecl>("str2");
+  ASSERT_NE(D, nullptr);
+  GlobalPtr = Prog.getPtrGlobal(*Prog.getGlobal(D));
+  ASSERT_TRUE(GlobalPtr.isBlockPointer());
+  ASSERT_TRUE(GlobalPtr.getFieldDesc()->getPrimType() == PT_Ptr);
+
+  Pointee = GlobalPtr.load<Pointer>();
+  ASSERT_TRUE(Pointee.isStringPointer());
+  ASSERT_EQ(Pointee.getNumElems(), 7u);
+  ASSERT_EQ(Pointee.elemSize(), __WCHAR_WIDTH__ / 8u);
+
+  D = match(varDecl(hasGlobalStorage(), hasName("c")).bind("c"), ASTCtx)[0]
+          .getNodeAs<VarDecl>("c");
+  ASSERT_NE(D, nullptr);
+  GlobalPtr = Prog.getPtrGlobal(*Prog.getGlobal(D));
+  ASSERT_TRUE(GlobalPtr.isBlockPointer());
+  ASSERT_TRUE(GlobalPtr.getFieldDesc()->getPrimType() == PT_Ptr);
+
+  Pointee = GlobalPtr.load<Pointer>();
+  ASSERT_TRUE(Pointee.isStringPointer());
+  ASSERT_EQ(Pointee.getNumElems(), 7u);
+  ASSERT_EQ(Pointee.elemSize(), __WCHAR_WIDTH__ / 8u);
+  ASSERT_EQ(Pointee.getIndex(), 5u);
+  APValue APV = Pointee.toAPValue(ASTCtx);
+  ASSERT_TRUE(APV.isLValue());
+  ASSERT_FALSE(APV.isLValueOnePastTheEnd());
+  ASSERT_EQ(APV.getLValueOffset().getQuantity(), 5u * (__WCHAR_WIDTH__ / 8u));
+  ASSERT_TRUE(APV.hasLValuePath());
+  const auto &Path = APV.getLValuePath();
+  ASSERT_EQ(Path.size(), 1u);
+  ASSERT_EQ(Path[0].getAsArrayIndex(), 5u);
+}



More information about the cfe-commits mailing list