https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/216736
>From 0adffa475f78d0db3ce1f62a422aed586b4d80cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> 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 | 25 +-- clang/lib/AST/ByteCode/Interp.h | 64 +++++--- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 150 +++++++++++------- .../lib/AST/ByteCode/InterpBuiltinBitCast.cpp | 91 +++++++++-- clang/lib/AST/ByteCode/InterpHelpers.h | 9 +- 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, 609 insertions(+), 208 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..37d3483777f9c 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; @@ -897,11 +898,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 +922,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 +932,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 +966,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 +988,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 +2852,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 +2880,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..2ee3912a1ab23 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp @@ -78,6 +78,50 @@ using DataFunc = } \ } while (0) +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 +226,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 +580,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..e6c58ffb143b4 100644 --- a/clang/lib/AST/ByteCode/InterpHelpers.h +++ b/clang/lib/AST/ByteCode/InterpHelpers.h @@ -46,11 +46,14 @@ bool CheckDummy(InterpState &S, CodePtr OpPC, const Block *B, AccessKinds AK); 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()); + 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..76c242dbb3a6f 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(), 4u); + + 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(), 4u); + ASSERT_EQ(Pointee.getIndex(), 5u); + APValue APV = Pointee.toAPValue(ASTCtx); + ASSERT_TRUE(APV.isLValue()); + ASSERT_FALSE(APV.isLValueOnePastTheEnd()); + ASSERT_EQ(APV.getLValueOffset().getQuantity(), 20u); + ASSERT_TRUE(APV.hasLValuePath()); + const auto &Path = APV.getLValuePath(); + ASSERT_EQ(Path.size(), 1u); + ASSERT_EQ(Path[0].getAsArrayIndex(), 5u); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
