[clang] [clang][Interp] Integral pointers (PR #84159)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 9 22:58:02 PDT 2024
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/84159
>From 3d6a09d1324dbd354668b0341644fb70fe09a47c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 6 Mar 2024 08:36:52 +0100
Subject: [PATCH] [clang][Interp] Integral pointers
---
clang/lib/AST/Interp/ByteCodeExprGen.cpp | 81 ++++-
clang/lib/AST/Interp/ByteCodeStmtGen.cpp | 2 +-
clang/lib/AST/Interp/Descriptor.h | 1 +
clang/lib/AST/Interp/FunctionPointer.h | 10 +-
clang/lib/AST/Interp/Interp.cpp | 5 +
clang/lib/AST/Interp/Interp.h | 85 +++--
clang/lib/AST/Interp/InterpBlock.cpp | 4 +-
clang/lib/AST/Interp/Opcodes.td | 12 +
clang/lib/AST/Interp/Pointer.cpp | 168 +++++++---
clang/lib/AST/Interp/Pointer.h | 383 +++++++++++++++++------
clang/lib/AST/Interp/PrimType.h | 4 +
clang/test/AST/Interp/c.c | 18 ++
clang/test/AST/Interp/const-eval.c | 192 ++++++++++++
clang/test/AST/Interp/functions.cpp | 15 +
14 files changed, 798 insertions(+), 182 deletions(-)
create mode 100644 clang/test/AST/Interp/const-eval.c
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index a1ce6575148325..4e650d49d180bb 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -173,10 +173,18 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
return this->emitCastFloatingIntegral(*ToT, CE);
}
- case CK_NullToPointer:
+ case CK_NullToPointer: {
if (DiscardResult)
return true;
- return this->emitNull(classifyPrim(CE->getType()), CE);
+
+ const Descriptor *Desc = nullptr;
+ const QualType PointeeType = CE->getType()->getPointeeType();
+ if (!PointeeType.isNull()) {
+ if (std::optional<PrimType> T = classify(PointeeType))
+ Desc = P.createDescriptor(SubExpr, *T);
+ }
+ return this->emitNull(classifyPrim(CE->getType()), Desc, CE);
+ }
case CK_PointerToIntegral: {
if (DiscardResult)
@@ -199,6 +207,41 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
return true;
}
+ case CK_IntegralToPointer: {
+ QualType IntType = SubExpr->getType();
+ assert(IntType->isIntegralOrEnumerationType());
+ if (!this->visit(SubExpr))
+ return false;
+ // FIXME: I think the discard is wrong since the int->ptr cast might cause a
+ // diagnostic.
+ PrimType T = classifyPrim(IntType);
+ if (DiscardResult)
+ return this->emitPop(T, CE);
+
+ QualType PtrType = CE->getType();
+ assert(PtrType->isPointerType());
+
+ const Descriptor *Desc;
+ if (std::optional<PrimType> T = classify(PtrType->getPointeeType()))
+ Desc = P.createDescriptor(SubExpr, *T);
+ else if (PtrType->getPointeeType()->isVoidType())
+ Desc = nullptr;
+ else
+ Desc = P.createDescriptor(CE, PtrType->getPointeeType().getTypePtr(),
+ Descriptor::InlineDescMD, true, false,
+ /*IsMutable=*/false, nullptr);
+
+ if (!this->emitGetIntPtr(T, Desc, CE))
+ return false;
+
+ PrimType DestPtrT = classifyPrim(PtrType);
+ if (DestPtrT == PT_Ptr)
+ return true;
+
+ // In case we're converting the integer to a non-Pointer.
+ return this->emitDecayPtr(PT_Ptr, DestPtrT, CE);
+ }
+
case CK_AtomicToNonAtomic:
case CK_ConstructorConversion:
case CK_FunctionToPointerDecay:
@@ -207,13 +250,31 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
case CK_UserDefinedConversion:
return this->delegate(SubExpr);
- case CK_BitCast:
+ case CK_BitCast: {
+ // Reject bitcasts to atomic types.
if (CE->getType()->isAtomicType()) {
if (!this->discard(SubExpr))
return false;
return this->emitInvalidCast(CastKind::Reinterpret, CE);
}
- return this->delegate(SubExpr);
+
+ if (DiscardResult)
+ return this->discard(SubExpr);
+
+ std::optional<PrimType> FromT = classify(SubExpr->getType());
+ std::optional<PrimType> ToT = classifyPrim(CE->getType());
+ if (!FromT || !ToT)
+ return false;
+
+ assert(isPtrType(*FromT));
+ assert(isPtrType(*ToT));
+ if (FromT == ToT)
+ return this->delegate(SubExpr);
+
+ if (!this->visit(SubExpr))
+ return false;
+ return this->emitDecayPtr(*FromT, *ToT, CE);
+ }
case CK_IntegralToBoolean:
case CK_IntegralCast: {
@@ -245,7 +306,7 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
if (!this->visit(SubExpr))
return false;
- if (!this->emitNull(PtrT, CE))
+ if (!this->emitNull(PtrT, nullptr, CE))
return false;
return this->emitNE(PtrT, CE);
@@ -455,7 +516,7 @@ bool ByteCodeExprGen<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) {
// Pointer arithmetic special case.
if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) {
- if (T == PT_Ptr || (LT == PT_Ptr && RT == PT_Ptr))
+ if (isPtrType(*T) || (isPtrType(*LT) && isPtrType(*RT)))
return this->VisitPointerArithBinOp(BO);
}
@@ -2326,7 +2387,7 @@ bool ByteCodeExprGen<Emitter>::visitBool(const Expr *E) {
// Convert pointers to bool.
if (T == PT_Ptr || T == PT_FnPtr) {
- if (!this->emitNull(*T, E))
+ if (!this->emitNull(*T, nullptr, E))
return false;
return this->emitNE(*T, E);
}
@@ -2366,9 +2427,9 @@ bool ByteCodeExprGen<Emitter>::visitZeroInitializer(PrimType T, QualType QT,
case PT_IntAPS:
return this->emitZeroIntAPS(Ctx.getBitWidth(QT), E);
case PT_Ptr:
- return this->emitNullPtr(E);
+ return this->emitNullPtr(nullptr, E);
case PT_FnPtr:
- return this->emitNullFnPtr(E);
+ return this->emitNullFnPtr(nullptr, E);
case PT_Float: {
return this->emitConstFloat(APFloat::getZero(Ctx.getFloatSemantics(QT)), E);
}
@@ -2952,7 +3013,7 @@ bool ByteCodeExprGen<Emitter>::VisitCXXNullPtrLiteralExpr(
if (DiscardResult)
return true;
- return this->emitNullPtr(E);
+ return this->emitNullPtr(nullptr, E);
}
template <class Emitter>
diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
index 675063e7489886..55a06f37a0c3de 100644
--- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
@@ -110,7 +110,7 @@ bool ByteCodeStmtGen<Emitter>::emitLambdaStaticInvokerBody(
// one here, and we don't need one either because the lambda cannot have
// any captures, as verified above. Emit a null pointer. This is then
// special-cased when interpreting to not emit any misleading diagnostics.
- if (!this->emitNullPtr(MD))
+ if (!this->emitNullPtr(nullptr, MD))
return false;
// Forward all arguments from the static invoker to the lambda call operator.
diff --git a/clang/lib/AST/Interp/Descriptor.h b/clang/lib/AST/Interp/Descriptor.h
index 4e257361ad146b..c386fc8ac7b09d 100644
--- a/clang/lib/AST/Interp/Descriptor.h
+++ b/clang/lib/AST/Interp/Descriptor.h
@@ -168,6 +168,7 @@ struct Descriptor final {
const Decl *asDecl() const { return Source.dyn_cast<const Decl *>(); }
const Expr *asExpr() const { return Source.dyn_cast<const Expr *>(); }
+ const DeclTy &getSource() const { return Source; }
const ValueDecl *asValueDecl() const {
return dyn_cast_if_present<ValueDecl>(asDecl());
diff --git a/clang/lib/AST/Interp/FunctionPointer.h b/clang/lib/AST/Interp/FunctionPointer.h
index 2ff691b1cd3e18..e7fad8161fd9cb 100644
--- a/clang/lib/AST/Interp/FunctionPointer.h
+++ b/clang/lib/AST/Interp/FunctionPointer.h
@@ -22,7 +22,11 @@ class FunctionPointer final {
const Function *Func;
public:
- FunctionPointer() : Func(nullptr) {}
+ // FIXME: We might want to track the fact that the Function pointer
+ // has been created from an integer and is most likely garbage anyway.
+ FunctionPointer(int IntVal = 0, const Descriptor *Desc = nullptr)
+ : Func(reinterpret_cast<const Function *>(IntVal)) {}
+
FunctionPointer(const Function *Func) : Func(Func) { assert(Func); }
const Function *getFunction() const { return Func; }
@@ -53,6 +57,10 @@ class FunctionPointer final {
return toAPValue().getAsString(Ctx, Func->getDecl()->getType());
}
+ uint64_t getIntegerRepresentation() const {
+ return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(Func));
+ }
+
ComparisonCategoryResult compare(const FunctionPointer &RHS) const {
if (Func == RHS.Func)
return ComparisonCategoryResult::Equal;
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
index 0ce64a572c263f..e5e2c932f500b8 100644
--- a/clang/lib/AST/Interp/Interp.cpp
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -282,6 +282,8 @@ bool CheckConstant(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
}
static bool CheckConstant(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+ if (Ptr.isIntegralPointer())
+ return true;
return CheckConstant(S, OpPC, Ptr.getDeclDesc());
}
@@ -335,6 +337,9 @@ bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
return true;
}
+ if (!Ptr.isBlockPointer())
+ return false;
+
const QualType Ty = Ptr.getType();
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_modify_const_type) << Ty;
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index 2c733c90f5f2b6..c7012aa4ec680b 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -804,8 +804,7 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
for (const auto &P : {LHS, RHS}) {
if (P.isZero())
continue;
- if (const ValueDecl *VD = P.getDeclDesc()->asValueDecl();
- VD && VD->isWeak()) {
+ if (P.isWeak()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_pointer_weak_comparison)
<< P.toDiagnosticString(S.getCtx());
@@ -824,9 +823,9 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
// element in the same array are NOT equal. They have the same Base value,
// but a different Offset. This is a pretty rare case, so we fix this here
// by comparing pointers to the first elements.
- if (!LHS.isDummy() && LHS.isArrayRoot())
+ if (!LHS.isZero() && !LHS.isDummy() && LHS.isArrayRoot())
VL = LHS.atIndex(0).getByteOffset();
- if (!RHS.isDummy() && RHS.isArrayRoot())
+ if (!RHS.isZero() && !RHS.isDummy() && RHS.isArrayRoot())
VR = RHS.atIndex(0).getByteOffset();
S.Stk.push<BoolT>(BoolT::from(Fn(Compare(VL, VR))));
@@ -1387,6 +1386,8 @@ bool Load(InterpState &S, CodePtr OpPC) {
const Pointer &Ptr = S.Stk.peek<Pointer>();
if (!CheckLoad(S, OpPC, Ptr))
return false;
+ if (!Ptr.isBlockPointer())
+ return false;
S.Stk.push<T>(Ptr.deref<T>());
return true;
}
@@ -1396,6 +1397,8 @@ bool LoadPop(InterpState &S, CodePtr OpPC) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
if (!CheckLoad(S, OpPC, Ptr))
return false;
+ if (!Ptr.isBlockPointer())
+ return false;
S.Stk.push<T>(Ptr.deref<T>());
return true;
}
@@ -1534,8 +1537,12 @@ bool OffsetHelper(InterpState &S, CodePtr OpPC, const T &Offset,
return true;
}
- if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex))
- return false;
+ if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex)) {
+ // The CheckNull will have emitted a note already, but we only
+ // abort in C++, since this is fine in C.
+ if (S.getLangOpts().CPlusPlus)
+ return false;
+ }
// Arrays of unknown bounds cannot have pointers into them.
if (!CheckArray(S, OpPC, Ptr))
@@ -1561,23 +1568,25 @@ bool OffsetHelper(InterpState &S, CodePtr OpPC, const T &Offset,
Invalid = true;
};
- T MaxOffset = T::from(MaxIndex - Index, Offset.bitWidth());
- if constexpr (Op == ArithOp::Add) {
- // If the new offset would be negative, bail out.
- if (Offset.isNegative() && (Offset.isMin() || -Offset > Index))
- DiagInvalidOffset();
-
- // If the new offset would be out of bounds, bail out.
- if (Offset.isPositive() && Offset > MaxOffset)
- DiagInvalidOffset();
- } else {
- // If the new offset would be negative, bail out.
- if (Offset.isPositive() && Index < Offset)
- DiagInvalidOffset();
-
- // If the new offset would be out of bounds, bail out.
- if (Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset))
- DiagInvalidOffset();
+ if (Ptr.isBlockPointer()) {
+ T MaxOffset = T::from(MaxIndex - Index, Offset.bitWidth());
+ if constexpr (Op == ArithOp::Add) {
+ // If the new offset would be negative, bail out.
+ if (Offset.isNegative() && (Offset.isMin() || -Offset > Index))
+ DiagInvalidOffset();
+
+ // If the new offset would be out of bounds, bail out.
+ if (Offset.isPositive() && Offset > MaxOffset)
+ DiagInvalidOffset();
+ } else {
+ // If the new offset would be negative, bail out.
+ if (Offset.isPositive() && Index < Offset)
+ DiagInvalidOffset();
+
+ // If the new offset would be out of bounds, bail out.
+ if (Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset))
+ DiagInvalidOffset();
+ }
}
if (Invalid && !Ptr.isDummy() && S.getLangOpts().CPlusPlus)
@@ -1661,6 +1670,11 @@ inline bool SubPtr(InterpState &S, CodePtr OpPC) {
const Pointer &LHS = S.Stk.pop<Pointer>();
const Pointer &RHS = S.Stk.pop<Pointer>();
+ if (RHS.isZero()) {
+ S.Stk.push<T>(T::from(LHS.getIndex()));
+ return true;
+ }
+
if (!Pointer::hasSameBase(LHS, RHS) && S.getLangOpts().CPlusPlus) {
// TODO: Diagnose.
return false;
@@ -1839,8 +1853,9 @@ static inline bool ZeroIntAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
}
template <PrimType Name, class T = typename PrimConv<Name>::T>
-inline bool Null(InterpState &S, CodePtr OpPC) {
- S.Stk.push<T>();
+inline bool Null(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
+ // Note: Desc can be null.
+ S.Stk.push<T>(0, Desc);
return true;
}
@@ -2244,6 +2259,14 @@ inline bool GetFnPtr(InterpState &S, CodePtr OpPC, const Function *Func) {
return true;
}
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
+ const T &IntVal = S.Stk.pop<T>();
+
+ S.Stk.push<Pointer>(static_cast<uint64_t>(IntVal), Desc);
+ return true;
+}
+
/// Just emit a diagnostic. The expression that caused emission of this
/// op is not valid in a constant context.
inline bool Invalid(InterpState &S, CodePtr OpPC) {
@@ -2300,6 +2323,18 @@ inline bool CheckNonNullArg(InterpState &S, CodePtr OpPC) {
return false;
}
+/// OldPtr -> Integer -> NewPtr.
+template <PrimType TIn, PrimType TOut>
+inline bool DecayPtr(InterpState &S, CodePtr OpPC) {
+ static_assert(isPtrType(TIn) && isPtrType(TOut));
+ using FromT = typename PrimConv<TIn>::T;
+ using ToT = typename PrimConv<TOut>::T;
+
+ const FromT &OldPtr = S.Stk.pop<FromT>();
+ S.Stk.push<ToT>(ToT(OldPtr.getIntegerRepresentation(), nullptr));
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// Read opcode arguments
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Interp/InterpBlock.cpp b/clang/lib/AST/Interp/InterpBlock.cpp
index a62128d9cfaedd..9b33d1b778fb2c 100644
--- a/clang/lib/AST/Interp/InterpBlock.cpp
+++ b/clang/lib/AST/Interp/InterpBlock.cpp
@@ -73,7 +73,7 @@ void Block::replacePointer(Pointer *Old, Pointer *New) {
removePointer(Old);
addPointer(New);
- Old->Pointee = nullptr;
+ Old->PointeeStorage.BS.Pointee = nullptr;
#ifndef NDEBUG
assert(!hasPointer(Old));
@@ -104,7 +104,7 @@ DeadBlock::DeadBlock(DeadBlock *&Root, Block *Blk)
// Transfer pointers.
B.Pointers = Blk->Pointers;
for (Pointer *P = Blk->Pointers; P; P = P->Next)
- P->Pointee = &B;
+ P->PointeeStorage.BS.Pointee = &B;
}
void DeadBlock::free() {
diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td
index 6e79a03203d276..e17be3afd25729 100644
--- a/clang/lib/AST/Interp/Opcodes.td
+++ b/clang/lib/AST/Interp/Opcodes.td
@@ -59,6 +59,7 @@ def ArgCastKind : ArgType { let Name = "CastKind"; }
def ArgCallExpr : ArgType { let Name = "const CallExpr *"; }
def ArgOffsetOfExpr : ArgType { let Name = "const OffsetOfExpr *"; }
def ArgDeclRef : ArgType { let Name = "const DeclRefExpr *"; }
+def ArgDesc : ArgType { let Name = "const Descriptor *"; }
def ArgCCI : ArgType { let Name = "const ComparisonCategoryInfo *"; }
//===----------------------------------------------------------------------===//
@@ -272,6 +273,7 @@ def ZeroIntAPS : Opcode {
// [] -> [Pointer]
def Null : Opcode {
let Types = [PtrTypeClass];
+ let Args = [ArgDesc];
let HasGroup = 1;
}
@@ -530,6 +532,11 @@ def GetFnPtr : Opcode {
let Args = [ArgFunction];
}
+def GetIntPtr : Opcode {
+ let Types = [AluTypeClass];
+ let Args = [ArgDesc];
+ let HasGroup = 1;
+}
//===----------------------------------------------------------------------===//
// Binary operators.
@@ -662,6 +669,11 @@ def CastPointerIntegral : Opcode {
let HasGroup = 1;
}
+def DecayPtr : Opcode {
+ let Types = [PtrTypeClass, PtrTypeClass];
+ let HasGroup = 1;
+}
+
//===----------------------------------------------------------------------===//
// Comparison opcodes.
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Interp/Pointer.cpp b/clang/lib/AST/Interp/Pointer.cpp
index 53998cc3233c94..24906cbf1b53ea 100644
--- a/clang/lib/AST/Interp/Pointer.cpp
+++ b/clang/lib/AST/Interp/Pointer.cpp
@@ -26,60 +26,95 @@ Pointer::Pointer(Block *Pointee)
Pointer::Pointer(Block *Pointee, unsigned BaseAndOffset)
: Pointer(Pointee, BaseAndOffset, BaseAndOffset) {}
-Pointer::Pointer(const Pointer &P) : Pointer(P.Pointee, P.Base, P.Offset) {}
+Pointer::Pointer(const Pointer &P)
+ : Offset(P.Offset), PointeeStorage(P.PointeeStorage),
+ StorageKind(P.StorageKind) {
-Pointer::Pointer(Pointer &&P)
- : Pointee(P.Pointee), Base(P.Base), Offset(P.Offset) {
- if (Pointee)
- Pointee->replacePointer(&P, this);
+ if (isBlockPointer() && PointeeStorage.BS.Pointee)
+ PointeeStorage.BS.Pointee->addPointer(this);
}
Pointer::Pointer(Block *Pointee, unsigned Base, unsigned Offset)
- : Pointee(Pointee), Base(Base), Offset(Offset) {
+ : Offset(Offset), StorageKind(Storage::Block) {
assert((Base == RootPtrMark || Base % alignof(void *) == 0) && "wrong base");
+
+ PointeeStorage.BS = {Pointee, Base};
+
if (Pointee)
Pointee->addPointer(this);
}
+Pointer::Pointer(Pointer &&P)
+ : Offset(P.Offset), PointeeStorage(P.PointeeStorage),
+ StorageKind(P.StorageKind) {
+
+ if (StorageKind == Storage::Block && PointeeStorage.BS.Pointee)
+ PointeeStorage.BS.Pointee->replacePointer(&P, this);
+}
+
Pointer::~Pointer() {
- if (Pointee) {
- Pointee->removePointer(this);
- Pointee->cleanup();
+ if (isIntegralPointer())
+ return;
+
+ if (PointeeStorage.BS.Pointee) {
+ PointeeStorage.BS.Pointee->removePointer(this);
+ PointeeStorage.BS.Pointee->cleanup();
}
}
void Pointer::operator=(const Pointer &P) {
- Block *Old = Pointee;
- if (Pointee)
- Pointee->removePointer(this);
+ if (!this->isIntegralPointer() || !P.isBlockPointer())
+ assert(P.StorageKind == StorageKind);
- Offset = P.Offset;
- Base = P.Base;
+ bool WasBlockPointer = isBlockPointer();
+ StorageKind = P.StorageKind;
+ if (StorageKind == Storage::Block) {
+ Block *Old = PointeeStorage.BS.Pointee;
+ if (WasBlockPointer && PointeeStorage.BS.Pointee)
+ PointeeStorage.BS.Pointee->removePointer(this);
- Pointee = P.Pointee;
- if (Pointee)
- Pointee->addPointer(this);
+ Offset = P.Offset;
+ PointeeStorage.BS = P.PointeeStorage.BS;
+
+ if (PointeeStorage.BS.Pointee)
+ PointeeStorage.BS.Pointee->addPointer(this);
- if (Old)
- Old->cleanup();
+ if (WasBlockPointer && Old)
+ Old->cleanup();
+
+ } else if (StorageKind == Storage::Int) {
+ PointeeStorage.Int = P.PointeeStorage.Int;
+ } else {
+ assert(false && "Unhandled storage kind");
+ }
}
void Pointer::operator=(Pointer &&P) {
- Block *Old = Pointee;
+ if (!this->isIntegralPointer() || !P.isBlockPointer())
+ assert(P.StorageKind == StorageKind);
- if (Pointee)
- Pointee->removePointer(this);
+ bool WasBlockPointer = isBlockPointer();
+ StorageKind = P.StorageKind;
+ if (StorageKind == Storage::Block) {
+ Block *Old = PointeeStorage.BS.Pointee;
+ if (WasBlockPointer && PointeeStorage.BS.Pointee)
+ PointeeStorage.BS.Pointee->removePointer(this);
- Offset = P.Offset;
- Base = P.Base;
+ Offset = P.Offset;
+ PointeeStorage.BS = P.PointeeStorage.BS;
- Pointee = P.Pointee;
- if (Pointee)
- Pointee->replacePointer(&P, this);
+ if (PointeeStorage.BS.Pointee)
+ PointeeStorage.BS.Pointee->addPointer(this);
- if (Old)
- Old->cleanup();
+ if (WasBlockPointer && Old)
+ Old->cleanup();
+
+ } else if (StorageKind == Storage::Int) {
+ PointeeStorage.Int = P.PointeeStorage.Int;
+ } else {
+ assert(false && "Unhandled storage kind");
+ }
}
APValue Pointer::toAPValue() const {
@@ -88,6 +123,11 @@ APValue Pointer::toAPValue() const {
if (isZero())
return APValue(static_cast<const Expr *>(nullptr), CharUnits::Zero(), Path,
/*IsOnePastEnd=*/false, /*IsNullPtr=*/true);
+ if (isIntegralPointer())
+ return APValue(static_cast<const Expr *>(nullptr),
+ CharUnits::fromQuantity(asIntPointer().Value + this->Offset),
+ Path,
+ /*IsOnePastEnd=*/false, /*IsNullPtr=*/false);
// Build the lvalue base from the block.
const Descriptor *Desc = getDeclDesc();
@@ -137,19 +177,52 @@ APValue Pointer::toAPValue() const {
return APValue(Base, Offset, Path, IsOnePastEnd, /*IsNullPtr=*/false);
}
+void Pointer::print(llvm::raw_ostream &OS) const {
+ OS << PointeeStorage.BS.Pointee << " (";
+ if (isBlockPointer()) {
+ OS << "Block) {";
+
+ if (PointeeStorage.BS.Base == RootPtrMark)
+ OS << "rootptr, ";
+ else
+ OS << PointeeStorage.BS.Base << ", ";
+
+ if (Offset == PastEndMark)
+ OS << "pastend, ";
+ else
+ OS << Offset << ", ";
+
+ if (isBlockPointer() && PointeeStorage.BS.Pointee)
+ OS << PointeeStorage.BS.Pointee->getSize();
+ else
+ OS << "nullptr";
+ } else {
+ OS << "Int) {";
+ OS << PointeeStorage.Int.Value << ", " << PointeeStorage.Int.Desc;
+ }
+ OS << "}";
+}
+
std::string Pointer::toDiagnosticString(const ASTContext &Ctx) const {
- if (!Pointee)
+ if (isZero())
return "nullptr";
+ if (isIntegralPointer())
+ return (Twine("&(") + Twine(asIntPointer().Value + Offset) + ")").str();
+
return toAPValue().getAsString(Ctx, getType());
}
bool Pointer::isInitialized() const {
- assert(Pointee && "Cannot check if null pointer was initialized");
+ if (isIntegralPointer())
+ return true;
+
+ assert(PointeeStorage.BS.Pointee &&
+ "Cannot check if null pointer was initialized");
const Descriptor *Desc = getFieldDesc();
assert(Desc);
if (Desc->isPrimitiveArray()) {
- if (isStatic() && Base == 0)
+ if (isStatic() && PointeeStorage.BS.Base == 0)
return true;
InitMapPtr &IM = getInitMap();
@@ -164,17 +237,20 @@ bool Pointer::isInitialized() const {
}
// Field has its bit in an inline descriptor.
- return Base == 0 || getInlineDesc()->IsInitialized;
+ return PointeeStorage.BS.Base == 0 || getInlineDesc()->IsInitialized;
}
void Pointer::initialize() const {
- assert(Pointee && "Cannot initialize null pointer");
+ if (isIntegralPointer())
+ return;
+
+ assert(PointeeStorage.BS.Pointee && "Cannot initialize null pointer");
const Descriptor *Desc = getFieldDesc();
assert(Desc);
if (Desc->isPrimitiveArray()) {
// Primitive global arrays don't have an initmap.
- if (isStatic() && Base == 0)
+ if (isStatic() && PointeeStorage.BS.Base == 0)
return;
// Nothing to do for these.
@@ -200,13 +276,15 @@ void Pointer::initialize() const {
}
// Field has its bit in an inline descriptor.
- assert(Base != 0 && "Only composite fields can be initialised");
+ assert(PointeeStorage.BS.Base != 0 &&
+ "Only composite fields can be initialised");
getInlineDesc()->IsInitialized = true;
}
void Pointer::activate() const {
// Field has its bit in an inline descriptor.
- assert(Base != 0 && "Only composite fields can be initialised");
+ assert(PointeeStorage.BS.Base != 0 &&
+ "Only composite fields can be initialised");
getInlineDesc()->IsActive = true;
}
@@ -215,11 +293,23 @@ void Pointer::deactivate() const {
}
bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
- return A.Pointee == B.Pointee;
+ // Two null pointers always have the same base.
+ if (A.isZero() && B.isZero())
+ return true;
+
+ if (A.isIntegralPointer() && B.isIntegralPointer())
+ return true;
+
+ if (A.isIntegralPointer() || B.isIntegralPointer())
+ return A.getSource() == B.getSource();
+
+ return A.asBlockPointer().Pointee == B.asBlockPointer().Pointee;
}
bool Pointer::hasSameArray(const Pointer &A, const Pointer &B) {
- return hasSameBase(A, B) && A.Base == B.Base && A.getFieldDesc()->IsArray;
+ return hasSameBase(A, B) &&
+ A.PointeeStorage.BS.Base == B.PointeeStorage.BS.Base &&
+ A.getFieldDesc()->IsArray;
}
std::optional<APValue> Pointer::toRValue(const Context &Ctx) const {
diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h
index fffb4aba492fc8..fcd00aac62f93e 100644
--- a/clang/lib/AST/Interp/Pointer.h
+++ b/clang/lib/AST/Interp/Pointer.h
@@ -28,11 +28,26 @@ class Block;
class DeadBlock;
class Pointer;
class Context;
+template <unsigned A, bool B> class Integral;
enum PrimType : unsigned;
class Pointer;
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P);
+struct BlockPointer {
+ /// The block the pointer is pointing to.
+ Block *Pointee;
+ /// Start of the current subfield.
+ unsigned Base;
+};
+
+struct IntPointer {
+ const Descriptor *Desc;
+ uint64_t Value;
+};
+
+enum class Storage { Block, Int };
+
/// A pointer to a memory block, live or dead.
///
/// This object can be allocated into interpreter stack frames. If pointing to
@@ -68,11 +83,20 @@ class Pointer {
static constexpr unsigned RootPtrMark = ~0u;
public:
- Pointer() {}
+ Pointer() {
+ StorageKind = Storage::Int;
+ PointeeStorage.Int.Value = 0;
+ PointeeStorage.Int.Desc = nullptr;
+ }
Pointer(Block *B);
Pointer(Block *B, unsigned BaseAndOffset);
Pointer(const Pointer &P);
Pointer(Pointer &&P);
+ Pointer(uint64_t Address, const Descriptor *Desc, unsigned Offset = 0)
+ : Offset(Offset), StorageKind(Storage::Int) {
+ PointeeStorage.Int.Value = Address;
+ PointeeStorage.Int.Desc = Desc;
+ }
~Pointer();
void operator=(const Pointer &P);
@@ -80,21 +104,30 @@ class Pointer {
/// Equality operators are just for tests.
bool operator==(const Pointer &P) const {
- return Pointee == P.Pointee && Base == P.Base && Offset == P.Offset;
- }
+ if (P.StorageKind != StorageKind)
+ return false;
+ if (isIntegralPointer())
+ return P.asIntPointer().Value == asIntPointer().Value &&
+ Offset == P.Offset;
- bool operator!=(const Pointer &P) const {
- return Pointee != P.Pointee || Base != P.Base || Offset != P.Offset;
+ assert(isBlockPointer());
+ return P.asBlockPointer().Pointee == asBlockPointer().Pointee &&
+ P.asBlockPointer().Base == asBlockPointer().Base &&
+ Offset == P.Offset;
}
+ bool operator!=(const Pointer &P) const { return !(P == *this); }
+
/// Converts the pointer to an APValue.
APValue toAPValue() const;
/// Converts the pointer to a string usable in diagnostics.
std::string toDiagnosticString(const ASTContext &Ctx) const;
- unsigned getIntegerRepresentation() const {
- return reinterpret_cast<uintptr_t>(Pointee) + Offset;
+ uint64_t getIntegerRepresentation() const {
+ if (isIntegralPointer())
+ return asIntPointer().Value + (Offset * elemSize());
+ return reinterpret_cast<uint64_t>(asBlockPointer().Pointee) + Offset;
}
/// Converts the pointer to an APValue that is an rvalue.
@@ -102,20 +135,27 @@ class Pointer {
/// Offsets a pointer inside an array.
[[nodiscard]] Pointer atIndex(unsigned Idx) const {
- if (Base == RootPtrMark)
- return Pointer(Pointee, RootPtrMark, getDeclDesc()->getSize());
+ if (isIntegralPointer())
+ return Pointer(asIntPointer().Value, asIntPointer().Desc, Idx);
+
+ if (asBlockPointer().Base == RootPtrMark)
+ return Pointer(asBlockPointer().Pointee, RootPtrMark,
+ getDeclDesc()->getSize());
unsigned Off = Idx * elemSize();
if (getFieldDesc()->ElemDesc)
Off += sizeof(InlineDescriptor);
else
Off += sizeof(InitMapPtr);
- return Pointer(Pointee, Base, Base + Off);
+ return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+ asBlockPointer().Base + Off);
}
/// Creates a pointer to a field.
[[nodiscard]] Pointer atField(unsigned Off) const {
unsigned Field = Offset + Off;
- return Pointer(Pointee, Field, Field);
+ if (isIntegralPointer())
+ return Pointer(asIntPointer().Value + Field, asIntPointer().Desc);
+ return Pointer(asBlockPointer().Pointee, Field, Field);
}
/// Subtract the given offset from the current Base and Offset
@@ -123,44 +163,49 @@ class Pointer {
[[nodiscard]] Pointer atFieldSub(unsigned Off) const {
assert(Offset >= Off);
unsigned O = Offset - Off;
- return Pointer(Pointee, O, O);
+ return Pointer(asBlockPointer().Pointee, O, O);
}
/// Restricts the scope of an array element pointer.
[[nodiscard]] Pointer narrow() const {
+ if (!isBlockPointer())
+ return *this;
+ assert(isBlockPointer());
// Null pointers cannot be narrowed.
if (isZero() || isUnknownSizeArray())
return *this;
// Pointer to an array of base types - enter block.
- if (Base == RootPtrMark)
- return Pointer(Pointee, sizeof(InlineDescriptor),
+ if (asBlockPointer().Base == RootPtrMark)
+ return Pointer(asBlockPointer().Pointee, sizeof(InlineDescriptor),
Offset == 0 ? Offset : PastEndMark);
// Pointer is one past end - magic offset marks that.
if (isOnePastEnd())
- return Pointer(Pointee, Base, PastEndMark);
+ return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+ PastEndMark);
// Primitive arrays are a bit special since they do not have inline
// descriptors. If Offset != Base, then the pointer already points to
// an element and there is nothing to do. Otherwise, the pointer is
// adjusted to the first element of the array.
if (inPrimitiveArray()) {
- if (Offset != Base)
+ if (Offset != asBlockPointer().Base)
return *this;
- return Pointer(Pointee, Base, Offset + sizeof(InitMapPtr));
+ return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+ Offset + sizeof(InitMapPtr));
}
// Pointer is to a field or array element - enter it.
- if (Offset != Base)
- return Pointer(Pointee, Offset, Offset);
+ if (Offset != asBlockPointer().Base)
+ return Pointer(asBlockPointer().Pointee, Offset, Offset);
// Enter the first element of an array.
if (!getFieldDesc()->isArray())
return *this;
- const unsigned NewBase = Base + sizeof(InlineDescriptor);
- return Pointer(Pointee, NewBase, NewBase);
+ const unsigned NewBase = asBlockPointer().Base + sizeof(InlineDescriptor);
+ return Pointer(asBlockPointer().Pointee, NewBase, NewBase);
}
/// Expands a pointer to the containing array, undoing narrowing.
@@ -172,72 +217,109 @@ class Pointer {
Adjust = sizeof(InitMapPtr);
else
Adjust = sizeof(InlineDescriptor);
- return Pointer(Pointee, Base, Base + getSize() + Adjust);
+ return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+ asBlockPointer().Base + getSize() + Adjust);
}
// Do not step out of array elements.
- if (Base != Offset)
+ if (asBlockPointer().Base != Offset)
return *this;
// If at base, point to an array of base types.
- if (Base == 0 || Base == sizeof(InlineDescriptor))
- return Pointer(Pointee, RootPtrMark, 0);
+ if (asBlockPointer().Base == 0 ||
+ asBlockPointer().Base == sizeof(InlineDescriptor))
+ return Pointer(asBlockPointer().Pointee, RootPtrMark, 0);
// Step into the containing array, if inside one.
- unsigned Next = Base - getInlineDesc()->Offset;
+ unsigned Next = asBlockPointer().Base - getInlineDesc()->Offset;
const Descriptor *Desc =
Next == 0 ? getDeclDesc() : getDescriptor(Next)->Desc;
if (!Desc->IsArray)
return *this;
- return Pointer(Pointee, Next, Offset);
+ return Pointer(asBlockPointer().Pointee, Next, Offset);
}
/// Checks if the pointer is null.
- bool isZero() const { return Pointee == nullptr; }
+ bool isZero() const {
+ if (Offset != 0)
+ return false;
+
+ if (isBlockPointer())
+ return asBlockPointer().Pointee == nullptr;
+ assert(isIntegralPointer());
+ return asIntPointer().Value == 0;
+ }
/// Checks if the pointer is live.
- bool isLive() const { return Pointee && !Pointee->IsDead; }
+ bool isLive() const {
+ if (isIntegralPointer())
+ return true;
+ return asBlockPointer().Pointee && !asBlockPointer().Pointee->IsDead;
+ }
/// Checks if the item is a field in an object.
bool isField() const {
+ if (isIntegralPointer())
+ return false;
+
+ unsigned Base = asBlockPointer().Base;
return Base != 0 && Base != sizeof(InlineDescriptor) &&
Base != RootPtrMark && getFieldDesc()->asDecl();
}
/// Accessor for information about the declaration site.
const Descriptor *getDeclDesc() const {
- assert(Pointee);
- return Pointee->Desc;
+ if (isIntegralPointer())
+ return asIntPointer().Desc;
+
+ assert(isBlockPointer());
+ assert(asBlockPointer().Pointee);
+ return asBlockPointer().Pointee->Desc;
}
SourceLocation getDeclLoc() const { return getDeclDesc()->getLocation(); }
+ /// Returns the expression or declaration the pointer has been created for.
+ DeclTy getSource() const {
+ if (isBlockPointer())
+ return getDeclDesc()->getSource();
+
+ assert(isIntegralPointer());
+ return asIntPointer().Desc ? asIntPointer().Desc->getSource() : DeclTy();
+ }
+
/// Returns a pointer to the object of which this pointer is a field.
[[nodiscard]] Pointer getBase() const {
- if (Base == RootPtrMark) {
+ if (asBlockPointer().Base == RootPtrMark) {
assert(Offset == PastEndMark && "cannot get base of a block");
- return Pointer(Pointee, Base, 0);
+ return Pointer(asBlockPointer().Pointee, asBlockPointer().Base, 0);
}
- unsigned NewBase = Base - getInlineDesc()->Offset;
- return Pointer(Pointee, NewBase, NewBase);
+ unsigned NewBase = asBlockPointer().Base - getInlineDesc()->Offset;
+ return Pointer(asBlockPointer().Pointee, NewBase, NewBase);
}
/// Returns the parent array.
[[nodiscard]] Pointer getArray() const {
- if (Base == RootPtrMark) {
+ if (asBlockPointer().Base == RootPtrMark) {
assert(Offset != 0 && Offset != PastEndMark && "not an array element");
- return Pointer(Pointee, Base, 0);
+ return Pointer(asBlockPointer().Pointee, asBlockPointer().Base, 0);
}
- assert(Offset != Base && "not an array element");
- return Pointer(Pointee, Base, Base);
+ assert(Offset != asBlockPointer().Base && "not an array element");
+ return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+ asBlockPointer().Base);
}
/// Accessors for information about the innermost field.
const Descriptor *getFieldDesc() const {
- if (Base == 0 || Base == sizeof(InlineDescriptor) || Base == RootPtrMark)
+ if (isIntegralPointer())
+ return asIntPointer().Desc;
+ if (isBlockPointer() &&
+ (asBlockPointer().Base == 0 ||
+ asBlockPointer().Base == sizeof(InlineDescriptor) ||
+ asBlockPointer().Base == RootPtrMark))
return getDeclDesc();
return getInlineDesc()->Desc;
}
/// Returns the type of the innermost field.
QualType getType() const {
- if (inPrimitiveArray() && Offset != Base) {
+ if (inPrimitiveArray() && Offset != asBlockPointer().Base) {
// Unfortunately, complex types are not array types in clang, but they are
// for us.
if (const auto *AT = getFieldDesc()->getType()->getAsArrayTypeUnsafe())
@@ -248,58 +330,104 @@ class Pointer {
return getFieldDesc()->getType();
}
- [[nodiscard]] Pointer getDeclPtr() const { return Pointer(Pointee); }
+ [[nodiscard]] Pointer getDeclPtr() const {
+ return Pointer(asBlockPointer().Pointee);
+ }
/// Returns the element size of the innermost field.
size_t elemSize() const {
- if (Base == RootPtrMark)
+ if (isIntegralPointer()) {
+ if (!asIntPointer().Desc)
+ return 1;
+ return asIntPointer().Desc->getElemSize();
+ }
+
+ if (asBlockPointer().Base == RootPtrMark)
return getDeclDesc()->getSize();
return getFieldDesc()->getElemSize();
}
/// Returns the total size of the innermost field.
- size_t getSize() const { return getFieldDesc()->getSize(); }
+ size_t getSize() const {
+ assert(isBlockPointer());
+ return getFieldDesc()->getSize();
+ }
/// Returns the offset into an array.
unsigned getOffset() const {
assert(Offset != PastEndMark && "invalid offset");
- if (Base == RootPtrMark)
+ if (asBlockPointer().Base == RootPtrMark)
return Offset;
unsigned Adjust = 0;
- if (Offset != Base) {
+ if (Offset != asBlockPointer().Base) {
if (getFieldDesc()->ElemDesc)
Adjust = sizeof(InlineDescriptor);
else
Adjust = sizeof(InitMapPtr);
}
- return Offset - Base - Adjust;
+ return Offset - asBlockPointer().Base - Adjust;
}
/// Whether this array refers to an array, but not
/// to the first element.
- bool isArrayRoot() const { return inArray() && Offset == Base; }
+ bool isArrayRoot() const {
+ return inArray() && Offset == asBlockPointer().Base;
+ }
/// Checks if the innermost field is an array.
- bool inArray() const { return getFieldDesc()->IsArray; }
+ bool inArray() const {
+ if (isBlockPointer())
+ return getFieldDesc()->IsArray;
+ return false;
+ }
/// Checks if the structure is a primitive array.
- bool inPrimitiveArray() const { return getFieldDesc()->isPrimitiveArray(); }
+ bool inPrimitiveArray() const {
+ if (isBlockPointer())
+ return getFieldDesc()->isPrimitiveArray();
+ return false;
+ }
/// Checks if the structure is an array of unknown size.
bool isUnknownSizeArray() const {
+ if (!isBlockPointer())
+ return false;
// If this points inside a dummy block, return true.
// FIXME: This might change in the future. If it does, we need
// to set the proper Ctor/Dtor functions for dummy Descriptors.
- if (Base != 0 && Base != sizeof(InlineDescriptor) && isDummy())
+ if (asBlockPointer().Base != 0 &&
+ asBlockPointer().Base != sizeof(InlineDescriptor) && isDummy())
return true;
return getFieldDesc()->isUnknownSizeArray();
}
/// Checks if the pointer points to an array.
- bool isArrayElement() const { return inArray() && Base != Offset; }
+ bool isArrayElement() const {
+ if (isBlockPointer())
+ return inArray() && asBlockPointer().Base != Offset;
+ return false;
+ }
/// Pointer points directly to a block.
bool isRoot() const {
- return (Base == 0 || Base == RootPtrMark) && Offset == 0;
+ return (asBlockPointer().Base == 0 ||
+ asBlockPointer().Base == RootPtrMark) &&
+ Offset == 0;
}
/// If this pointer has an InlineDescriptor we can use to initialize.
- bool canBeInitialized() const { return Pointee && Base > 0; }
+ bool canBeInitialized() const {
+ if (!isBlockPointer())
+ return false;
+
+ return asBlockPointer().Pointee && asBlockPointer().Base > 0;
+ }
+
+ [[nodiscard]] const BlockPointer &asBlockPointer() const {
+ assert(isBlockPointer());
+ return PointeeStorage.BS;
+ }
+ [[nodiscard]] const IntPointer &asIntPointer() const {
+ assert(isIntegralPointer());
+ return PointeeStorage.Int;
+ }
+ bool isBlockPointer() const { return StorageKind == Storage::Block; }
+ bool isIntegralPointer() const { return StorageKind == Storage::Int; }
/// Returns the record descriptor of a class.
const Record *getRecord() const { return getFieldDesc()->ElemRecord; }
@@ -315,71 +443,119 @@ class Pointer {
bool isUnion() const;
/// Checks if the storage is extern.
- bool isExtern() const { return Pointee && Pointee->isExtern(); }
+ bool isExtern() const {
+ if (isBlockPointer())
+ return asBlockPointer().Pointee && asBlockPointer().Pointee->isExtern();
+ return false;
+ }
/// Checks if the storage is static.
bool isStatic() const {
- assert(Pointee);
- return Pointee->isStatic();
+ if (isIntegralPointer())
+ return true;
+ assert(asBlockPointer().Pointee);
+ return asBlockPointer().Pointee->isStatic();
}
/// Checks if the storage is temporary.
bool isTemporary() const {
- assert(Pointee);
- return Pointee->isTemporary();
+ if (isBlockPointer()) {
+ assert(asBlockPointer().Pointee);
+ return asBlockPointer().Pointee->isTemporary();
+ }
+ return false;
}
/// Checks if the storage is a static temporary.
bool isStaticTemporary() const { return isStatic() && isTemporary(); }
/// Checks if the field is mutable.
bool isMutable() const {
- return Base != 0 && Base != sizeof(InlineDescriptor) &&
+ if (!isBlockPointer())
+ return false;
+ return asBlockPointer().Base != 0 &&
+ asBlockPointer().Base != sizeof(InlineDescriptor) &&
getInlineDesc()->IsFieldMutable;
}
+
+ bool isWeak() const {
+ if (isIntegralPointer())
+ return false;
+
+ assert(isBlockPointer());
+ if (const ValueDecl *VD = getDeclDesc()->asValueDecl())
+ return VD->isWeak();
+ return false;
+ }
/// Checks if an object was initialized.
bool isInitialized() const;
/// Checks if the object is active.
bool isActive() const {
- return Base == 0 || Base == sizeof(InlineDescriptor) ||
+ if (!isBlockPointer())
+ return true;
+ return asBlockPointer().Base == 0 ||
+ asBlockPointer().Base == sizeof(InlineDescriptor) ||
getInlineDesc()->IsActive;
}
/// Checks if a structure is a base class.
bool isBaseClass() const { return isField() && getInlineDesc()->IsBase; }
/// Checks if the pointer points to a dummy value.
bool isDummy() const {
- if (!Pointee)
+ if (!isBlockPointer())
return false;
+
+ if (!asBlockPointer().Pointee)
+ return false;
+
return getDeclDesc()->isDummy();
}
/// Checks if an object or a subfield is mutable.
bool isConst() const {
- return (Base == 0 || Base == sizeof(InlineDescriptor))
+ if (isIntegralPointer())
+ return true;
+ return (asBlockPointer().Base == 0 ||
+ asBlockPointer().Base == sizeof(InlineDescriptor))
? getDeclDesc()->IsConst
: getInlineDesc()->IsConst;
}
/// Returns the declaration ID.
std::optional<unsigned> getDeclID() const {
- assert(Pointee);
- return Pointee->getDeclID();
+ if (isBlockPointer()) {
+ assert(asBlockPointer().Pointee);
+ return asBlockPointer().Pointee->getDeclID();
+ }
+ return std::nullopt;
}
/// Returns the byte offset from the start.
unsigned getByteOffset() const {
+ if (isIntegralPointer())
+ return asIntPointer().Value + Offset;
return Offset;
}
/// Returns the number of elements.
- unsigned getNumElems() const { return getSize() / elemSize(); }
+ unsigned getNumElems() const {
+ if (isIntegralPointer())
+ return ~unsigned(0);
+ return getSize() / elemSize();
+ }
- const Block *block() const { return Pointee; }
+ const Block *block() const { return asBlockPointer().Pointee; }
/// Returns the index into an array.
int64_t getIndex() const {
+ if (!isBlockPointer())
+ return 0;
+
+ if (isZero())
+ return 0;
+
if (isElementPastEnd())
return 1;
// narrow()ed element in a composite array.
- if (Base > sizeof(InlineDescriptor) && Base == Offset)
+ if (asBlockPointer().Base > sizeof(InlineDescriptor) &&
+ asBlockPointer().Base == Offset)
return 0;
if (auto ElemSize = elemSize())
@@ -389,7 +565,10 @@ class Pointer {
/// Checks if the index is one past end.
bool isOnePastEnd() const {
- if (!Pointee)
+ if (isIntegralPointer())
+ return false;
+
+ if (!asBlockPointer().Pointee)
return false;
return isElementPastEnd() || getSize() == getOffset();
}
@@ -400,20 +579,25 @@ class Pointer {
/// Dereferences the pointer, if it's live.
template <typename T> T &deref() const {
assert(isLive() && "Invalid pointer");
- assert(Pointee);
+ assert(isBlockPointer());
+ assert(asBlockPointer().Pointee);
+ assert(Offset + sizeof(T) <=
+ asBlockPointer().Pointee->getDescriptor()->getAllocSize());
+
if (isArrayRoot())
- return *reinterpret_cast<T *>(Pointee->rawData() + Base +
- sizeof(InitMapPtr));
+ return *reinterpret_cast<T *>(asBlockPointer().Pointee->rawData() +
+ asBlockPointer().Base + sizeof(InitMapPtr));
- assert(Offset + sizeof(T) <= Pointee->getDescriptor()->getAllocSize());
- return *reinterpret_cast<T *>(Pointee->rawData() + Offset);
+ return *reinterpret_cast<T *>(asBlockPointer().Pointee->rawData() + Offset);
}
/// Dereferences a primitive element.
template <typename T> T &elem(unsigned I) const {
assert(I < getNumElems());
- assert(Pointee);
- return reinterpret_cast<T *>(Pointee->data() + sizeof(InitMapPtr))[I];
+ assert(isBlockPointer());
+ assert(asBlockPointer().Pointee);
+ return reinterpret_cast<T *>(asBlockPointer().Pointee->data() +
+ sizeof(InitMapPtr))[I];
}
/// Initializes a field.
@@ -442,24 +626,7 @@ class Pointer {
static bool hasSameArray(const Pointer &A, const Pointer &B);
/// Prints the pointer.
- void print(llvm::raw_ostream &OS) const {
- OS << Pointee << " {";
- if (Base == RootPtrMark)
- OS << "rootptr, ";
- else
- OS << Base << ", ";
-
- if (Offset == PastEndMark)
- OS << "pastend, ";
- else
- OS << Offset << ", ";
-
- if (Pointee)
- OS << Pointee->getSize();
- else
- OS << "nullptr";
- OS << "}";
- }
+ void print(llvm::raw_ostream &OS) const;
private:
friend class Block;
@@ -469,33 +636,41 @@ class Pointer {
Pointer(Block *Pointee, unsigned Base, unsigned Offset);
/// Returns the embedded descriptor preceding a field.
- InlineDescriptor *getInlineDesc() const { return getDescriptor(Base); }
+ InlineDescriptor *getInlineDesc() const {
+ return getDescriptor(asBlockPointer().Base);
+ }
/// Returns a descriptor at a given offset.
InlineDescriptor *getDescriptor(unsigned Offset) const {
assert(Offset != 0 && "Not a nested pointer");
- assert(Pointee);
- return reinterpret_cast<InlineDescriptor *>(Pointee->rawData() + Offset) -
+ assert(isBlockPointer());
+ assert(!isZero());
+ return reinterpret_cast<InlineDescriptor *>(
+ asBlockPointer().Pointee->rawData() + Offset) -
1;
}
/// Returns a reference to the InitMapPtr which stores the initialization map.
InitMapPtr &getInitMap() const {
- assert(Pointee);
- return *reinterpret_cast<InitMapPtr *>(Pointee->rawData() + Base);
+ assert(isBlockPointer());
+ assert(!isZero());
+ return *reinterpret_cast<InitMapPtr *>(asBlockPointer().Pointee->rawData() +
+ asBlockPointer().Base);
}
- /// The block the pointer is pointing to.
- Block *Pointee = nullptr;
- /// Start of the current subfield.
- unsigned Base = 0;
- /// Offset into the block.
+ /// Offset into the storage.
unsigned Offset = 0;
/// Previous link in the pointer chain.
Pointer *Prev = nullptr;
/// Next link in the pointer chain.
Pointer *Next = nullptr;
+
+ union {
+ BlockPointer BS;
+ IntPointer Int;
+ } PointeeStorage;
+ Storage StorageKind = Storage::Int;
};
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P) {
diff --git a/clang/lib/AST/Interp/PrimType.h b/clang/lib/AST/Interp/PrimType.h
index 2bc83b334643e3..05a094d0c5b1ff 100644
--- a/clang/lib/AST/Interp/PrimType.h
+++ b/clang/lib/AST/Interp/PrimType.h
@@ -46,6 +46,10 @@ enum PrimType : unsigned {
PT_FnPtr,
};
+inline constexpr bool isPtrType(PrimType T) {
+ return T == PT_Ptr || T == PT_FnPtr;
+}
+
enum class CastKind : uint8_t {
Reinterpret,
Atomic,
diff --git a/clang/test/AST/Interp/c.c b/clang/test/AST/Interp/c.c
index 10e23839f2ba2a..cdecd3e83a9979 100644
--- a/clang/test/AST/Interp/c.c
+++ b/clang/test/AST/Interp/c.c
@@ -209,3 +209,21 @@ const struct StrA * const sb = &sa;
const struct StrA sc = *sb;
_Static_assert(sc.a == 12, ""); // pedantic-ref-warning {{GNU extension}} \
// pedantic-expected-warning {{GNU extension}}
+
+_Static_assert(((void*)0 + 1) != (void*)0, ""); // pedantic-expected-warning {{arithmetic on a pointer to void is a GNU extension}} \
+ // pedantic-expected-warning {{not an integer constant expression}} \
+ // pedantic-expected-note {{cannot perform pointer arithmetic on null pointer}} \
+ // pedantic-ref-warning {{arithmetic on a pointer to void is a GNU extension}} \
+ // pedantic-ref-warning {{not an integer constant expression}} \
+ // pedantic-ref-note {{cannot perform pointer arithmetic on null pointer}}
+
+typedef __INTPTR_TYPE__ intptr_t;
+int array[(intptr_t)(int*)1]; // ref-warning {{variable length array folded to constant array}} \
+ // pedantic-ref-warning {{variable length array folded to constant array}} \
+ // expected-warning {{variable length array folded to constant array}} \
+ // pedantic-expected-warning {{variable length array folded to constant array}}
+
+int castViaInt[*(int*)(unsigned long)"test"]; // ref-error {{variable length array}} \
+ // pedantic-ref-error {{variable length array}} \
+ // expected-error {{variable length array}} \
+ // pedantic-expected-error {{variable length array}}
diff --git a/clang/test/AST/Interp/const-eval.c b/clang/test/AST/Interp/const-eval.c
new file mode 100644
index 00000000000000..72c0833a0f6309
--- /dev/null
+++ b/clang/test/AST/Interp/const-eval.c
@@ -0,0 +1,192 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=both,ref -triple x86_64-linux %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast
+// RUN: %clang_cc1 -fsyntax-only -verify=both,expected -triple x86_64-linux %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=both,ref -triple powerpc64-ibm-aix-xcoff %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast
+// RUN: %clang_cc1 -fsyntax-only -verify=both,expected -triple powerpc64-ibm-aix-xcoff %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast -fexperimental-new-constant-interpreter -DNEW_INTERP
+
+/// This is a version of test/Sema/const-eval.c with the
+/// tests commented out that the new constant expression interpreter does
+/// not support yet. They are all marked with the NEW_INTERP define:
+///
+/// - builtin_constant_p
+/// - unions
+
+
+#define EVAL_EXPR(testno, expr) enum { test##testno = (expr) }; struct check_positive##testno { int a[test##testno]; };
+int x;
+EVAL_EXPR(1, (_Bool)&x)
+EVAL_EXPR(2, (int)(1.0+(double)4))
+EVAL_EXPR(3, (int)(1.0+(float)4.0))
+EVAL_EXPR(4, (_Bool)(1 ? (void*)&x : 0))
+EVAL_EXPR(5, (_Bool)(int[]){0})
+struct y {int x,y;};
+EVAL_EXPR(6, (int)(1+(struct y*)0))
+_Static_assert((long)&((struct y*)0)->y > 0, "");
+EVAL_EXPR(7, (int)&((struct y*)0)->y)
+EVAL_EXPR(8, (_Bool)"asdf")
+EVAL_EXPR(9, !!&x)
+EVAL_EXPR(10, ((void)1, 12))
+void g0(void);
+EVAL_EXPR(11, (g0(), 12)) // both-error {{not an integer constant expression}}
+EVAL_EXPR(12, 1.0&&2.0)
+EVAL_EXPR(13, x || 3.0) // both-error {{not an integer constant expression}}
+
+unsigned int l_19 = 1;
+EVAL_EXPR(14, (1 ^ l_19) && 1); // both-error {{not an integer constant expression}}
+
+void f(void)
+{
+ int a;
+ EVAL_EXPR(15, (_Bool)&a);
+}
+
+_Complex float g16 = (1.0f + 1.0fi);
+
+// ?: in constant expressions.
+int g17[(3?:1) - 2];
+
+EVAL_EXPR(18, ((int)((void*)10 + 10)) == 20 ? 1 : -1);
+
+struct s {
+ int a[(int)-1.0f]; // both-error {{array size is negative}}
+};
+
+EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1));
+
+#ifndef NEW_INTERP
+EVAL_EXPR(20, __builtin_constant_p(*((int*) 10)));
+#endif
+
+EVAL_EXPR(21, (__imag__ 2i) == 2 ? 1 : -1);
+
+EVAL_EXPR(22, (__real__ (2i+3)) == 3 ? 1 : -1);
+
+int g23[(int)(1.0 / 1.0)] = { 1 }; // both-warning {{folded to constant array}}
+int g24[(int)(1.0 / 1.0)] = { 1 , 2 }; // both-warning {{folded to constant array}} \
+ // both-warning {{excess elements in array initializer}}
+int g25[(int)(1.0 + 1.0)], g26 = sizeof(g25); // both-warning {{folded to constant array}}
+
+EVAL_EXPR(26, (_Complex double)0 ? -1 : 1)
+EVAL_EXPR(27, (_Complex int)0 ? -1 : 1)
+EVAL_EXPR(28, (_Complex double)1 ? 1 : -1)
+EVAL_EXPR(29, (_Complex int)1 ? 1 : -1)
+
+// PR4027
+struct a { int x, y; };
+static struct a V2 = (struct a)(struct a){ 1, 2};
+static const struct a V1 = (struct a){ 1, 2};
+
+EVAL_EXPR(30, (int)(_Complex float)((1<<30)-1) == (1<<30) ? 1 : -1)
+EVAL_EXPR(31, (int*)0 == (int*)0 ? 1 : -1)
+EVAL_EXPR(32, (int*)0 != (int*)0 ? -1 : 1)
+EVAL_EXPR(33, (void*)0 - (void*)0 == 0 ? 1 : -1)
+
+void foo(void) {}
+EVAL_EXPR(34, (foo == (void *)0) ? -1 : 1)
+
+// No PR. Mismatched bitwidths lead to a crash on second evaluation.
+const _Bool constbool = 0;
+EVAL_EXPR(35, constbool)
+EVAL_EXPR(36, constbool)
+
+EVAL_EXPR(37, ((void)1,2.0) == 2.0 ? 1 : -1)
+EVAL_EXPR(38, __builtin_expect(1,1) == 1 ? 1 : -1)
+
+// PR7884
+EVAL_EXPR(39, __real__(1.f) == 1 ? 1 : -1)
+EVAL_EXPR(40, __imag__(1.f) == 0 ? 1 : -1)
+
+// From gcc testsuite
+EVAL_EXPR(41, (int)(1+(_Complex unsigned)2))
+
+void rdar8875946(void) {
+ double _Complex P;
+ float _Complex P2 = 3.3f + P;
+}
+
+double d = (d = 0.0); // both-error {{not a compile-time constant}}
+double d2 = ++d; // both-error {{not a compile-time constant}}
+
+int n = 2;
+int intLvalue[*(int*)((long)&n ?: 1)] = { 1, 2 }; // both-error {{variable length array}}
+
+union u { int a; char b[4]; };
+char c = ((union u)(123456)).b[0]; // both-error {{not a compile-time constant}}
+
+#ifndef NEW_INTERP
+extern const int weak_int __attribute__((weak));
+const int weak_int = 42;
+int weak_int_test = weak_int; // both-error {{not a compile-time constant}}
+#endif
+
+int literalVsNull1 = "foo" == 0;
+int literalVsNull2 = 0 == "foo";
+
+// PR11385.
+int castViaInt[*(int*)(unsigned long)"test"]; // both-error {{variable length array}}
+
+// PR11391.
+#ifndef NEW_INTERP
+struct PR11391 { _Complex float f; } pr11391;
+EVAL_EXPR(42, __builtin_constant_p(pr11391.f = 1))
+#endif
+
+// PR12043
+float varfloat;
+const float constfloat = 0;
+EVAL_EXPR(43, varfloat && constfloat) // both-error {{not an integer constant expression}}
+EVAL_EXPR(45, ((char*)-1) + 1 == 0 ? 1 : -1)
+EVAL_EXPR(46, ((char*)-1) + 1 < (char*) -1 ? 1 : -1)
+EVAL_EXPR(47, &x < &x + 1 ? 1 : -1)
+EVAL_EXPR(48, &x != &x - 1 ? 1 : -1)
+EVAL_EXPR(49, &x < &x - 100 ? 1 : -1) // ref-error {{not an integer constant expression}}
+
+/// FIXME: Rejecting this is correct, BUT when converting the innermost pointer
+/// to an integer, we do not preserve the information where it came from. So when we later
+/// create a pointer from it, it also doesn't have that information, which means
+/// hasSameBase() for those two pointers will return false. And in those cases, we emit
+/// the diagnostic:
+/// comparison between '&Test50' and '&(631578)' has unspecified value
+extern struct Test50S Test50;
+EVAL_EXPR(50, &Test50 < (struct Test50S*)((unsigned long)&Test50 + 10)) // both-error {{not an integer constant expression}} \
+ // expected-note {{comparison between}}
+
+EVAL_EXPR(51, 0 != (float)1e99)
+
+// PR21945
+void PR21945(void) { int i = (({}), 0l); }
+
+void PR24622(void);
+struct PR24622 {} pr24622;
+EVAL_EXPR(52, &pr24622 == (void *)&PR24622);
+
+// We evaluate these by providing 2s' complement semantics in constant
+// expressions, like we do for integers.
+void *PR28739a = (__int128)(unsigned long)-1 + &PR28739a; // both-warning {{the pointer incremented by 18446744073709551615 refers past the last possible element for an array in 64-bit address space containing 64-bit (8-byte) elements (max possible 2305843009213693952 elements)}}
+
+void *PR28739b = &PR28739b + (__int128)(unsigned long)-1; // both-warning {{refers past the last possible element}}
+__int128 PR28739c = (&PR28739c + (__int128)(unsigned long)-1) - &PR28739c; // both-warning {{refers past the last possible element}}
+void *PR28739d = &(&PR28739d)[(__int128)(unsigned long)-1]; // both-warning {{refers past the last possible element}}
+
+struct PR35214_X {
+ int k;
+ int arr[];
+};
+int PR35214_x;
+int PR35214_y = ((struct PR35214_X *)&PR35214_x)->arr[1]; // both-error {{not a compile-time constant}}
+#ifndef NEW_INTERP
+int *PR35214_z = &((struct PR35214_X *)&PR35214_x)->arr[1]; // ok, &PR35214_x + 2
+#endif
+
+/// From const-eval-64.c
+EVAL_EXPR(53, ((char*)-1LL) + 1 == 0 ? 1 : -1)
+EVAL_EXPR(54, ((char*)-1LL) + 1 < (char*) -1 ? 1 : -1)
+
+/// === Additions ===
+#if __SIZEOF_INT__ == 4
+typedef __INTPTR_TYPE__ intptr_t;
+const intptr_t A = (intptr_t)(((int*) 0) + 1);
+const intptr_t B = (intptr_t)(((char*)0) + 3);
+_Static_assert(A > B, "");
+#else
+#error :(
+#endif
diff --git a/clang/test/AST/Interp/functions.cpp b/clang/test/AST/Interp/functions.cpp
index 67fd9036d81e76..4fb3c816000ab8 100644
--- a/clang/test/AST/Interp/functions.cpp
+++ b/clang/test/AST/Interp/functions.cpp
@@ -185,6 +185,21 @@ namespace FunctionReturnType {
constexpr int (*invalidFnPtr)() = m;
static_assert(invalidFnPtr() == 5, ""); // both-error {{not an integral constant expression}} \
// both-note {{non-constexpr function 'm'}}
+
+
+namespace ToBool {
+ void mismatched(int x) {}
+ typedef void (*callback_t)(int);
+ void foo() {
+ callback_t callback = (callback_t)mismatched; // warns
+ /// Casts a function pointer to a boolean and then back to a function pointer.
+ /// This is extracted from test/Sema/callingconv-cast.c
+ callback = (callback_t)!mismatched; // both-warning {{address of function 'mismatched' will always evaluate to 'true'}} \
+ // both-note {{prefix with the address-of operator to silence this warning}}
+ }
+}
+
+
}
namespace Comparison {
More information about the cfe-commits
mailing list