[clang] 1709eac - [clang][Interp] Integral pointers (#84159)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 10 03:53:58 PDT 2024
Author: Timm Baeder
Date: 2024-04-10T12:53:54+02:00
New Revision: 1709eac58fee8f559cd70cfce9e7f09192dcb1bc
URL: https://github.com/llvm/llvm-project/commit/1709eac58fee8f559cd70cfce9e7f09192dcb1bc
DIFF: https://github.com/llvm/llvm-project/commit/1709eac58fee8f559cd70cfce9e7f09192dcb1bc.diff
LOG: [clang][Interp] Integral pointers (#84159)
This turns the current `Pointer` class into a discriminated union of
`BlockPointer` and `IntPointer`. The former is what `Pointer` currently
is while the latter is just an integer value and an optional
`Descriptor*`.
The `Pointer` then has type check functions like
`isBlockPointer()`/`isIntegralPointer()`/`asBlockPointer()`/`asIntPointer()`,
which can be used to access its data.
Right now, the `IntPointer` and `BlockPointer` structs do not have any
methods of their own and everything is instead implemented in Pointer
(like it was before) and the functions now just either assert for the
right type or decide what to do based on it.
This also implements bitcasts by decaying the pointer to an integral
pointer.
`test/AST/Interp/const-eval.c` is a new test testing all kinds of stuff
related to this. It still has a few tests `#ifdef`-ed out but that
mostly depends on other unimplemented things like
`__builtin_constant_p`.
Added:
clang/test/AST/Interp/const-eval.c
Modified:
clang/lib/AST/Interp/ByteCodeExprGen.cpp
clang/lib/AST/Interp/ByteCodeStmtGen.cpp
clang/lib/AST/Interp/Descriptor.h
clang/lib/AST/Interp/FunctionPointer.h
clang/lib/AST/Interp/Interp.cpp
clang/lib/AST/Interp/Interp.h
clang/lib/AST/Interp/InterpBlock.cpp
clang/lib/AST/Interp/Opcodes.td
clang/lib/AST/Interp/Pointer.cpp
clang/lib/AST/Interp/Pointer.h
clang/lib/AST/Interp/PrimType.h
clang/test/AST/Interp/c.c
clang/test/AST/Interp/functions.cpp
Removed:
################################################################################
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index acff63cd9dc022..84bacd457c85b5 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);
}
@@ -2354,7 +2415,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);
}
@@ -2394,9 +2455,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);
}
@@ -2980,7 +3041,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
diff erent 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 cddcd6b0151e42..e163e658d462b2 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