Matheus Izvekov 91cdd35008
[clang] Improve nested name specifier AST representation (#147835)
This is a major change on how we represent nested name qualifications in
the AST.

* The nested name specifier itself and how it's stored is changed. The
prefixes for types are handled within the type hierarchy, which makes
canonicalization for them super cheap, no memory allocation required.
Also translating a type into nested name specifier form becomes a no-op.
An identifier is stored as a DependentNameType. The nested name
specifier gains a lightweight handle class, to be used instead of
passing around pointers, which is similar to what is implemented for
TemplateName. There is still one free bit available, and this handle can
be used within a PointerUnion and PointerIntPair, which should keep
bit-packing aficionados happy.
* The ElaboratedType node is removed, all type nodes in which it could
previously apply to can now store the elaborated keyword and name
qualifier, tail allocating when present.
* TagTypes can now point to the exact declaration found when producing
these, as opposed to the previous situation of there only existing one
TagType per entity. This increases the amount of type sugar retained,
and can have several applications, for example in tracking module
ownership, and other tools which care about source file origins, such as
IWYU. These TagTypes are lazily allocated, in order to limit the
increase in AST size.

This patch offers a great performance benefit.

It greatly improves compilation time for
[stdexec](https://github.com/NVIDIA/stdexec). For one datapoint, for
`test_on2.cpp` in that project, which is the slowest compiling test,
this patch improves `-c` compilation time by about 7.2%, with the
`-fsyntax-only` improvement being at ~12%.

This has great results on compile-time-tracker as well:

![image](https://github.com/user-attachments/assets/700dce98-2cab-4aa8-97d1-b038c0bee831)

This patch also further enables other optimziations in the future, and
will reduce the performance impact of template specialization resugaring
when that lands.

It has some other miscelaneous drive-by fixes.

About the review: Yes the patch is huge, sorry about that. Part of the
reason is that I started by the nested name specifier part, before the
ElaboratedType part, but that had a huge performance downside, as
ElaboratedType is a big performance hog. I didn't have the steam to go
back and change the patch after the fact.

There is also a lot of internal API changes, and it made sense to remove
ElaboratedType in one go, versus removing it from one type at a time, as
that would present much more churn to the users. Also, the nested name
specifier having a different API avoids missing changes related to how
prefixes work now, which could make existing code compile but not work.

How to review: The important changes are all in
`clang/include/clang/AST` and `clang/lib/AST`, with also important
changes in `clang/lib/Sema/TreeTransform.h`.

The rest and bulk of the changes are mostly consequences of the changes
in API.

PS: TagType::getDecl is renamed to `getOriginalDecl` in this patch, just
for easier to rebasing. I plan to rename it back after this lands.

Fixes #136624
Fixes https://github.com/llvm/llvm-project/issues/43179
Fixes https://github.com/llvm/llvm-project/issues/68670
Fixes https://github.com/llvm/llvm-project/issues/92757
2025-08-09 05:06:53 -03:00

2248 lines
71 KiB
C++

//===------- Interp.cpp - Interpreter for the constexpr VM ------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Interp.h"
#include "Compiler.h"
#include "Function.h"
#include "InterpFrame.h"
#include "InterpShared.h"
#include "InterpStack.h"
#include "Opcode.h"
#include "PrimType.h"
#include "Program.h"
#include "State.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/StringExtras.h"
using namespace clang;
using namespace clang::interp;
static bool RetValue(InterpState &S, CodePtr &Pt) {
llvm::report_fatal_error("Interpreter cannot return values");
}
//===----------------------------------------------------------------------===//
// Jmp, Jt, Jf
//===----------------------------------------------------------------------===//
static bool Jmp(InterpState &S, CodePtr &PC, int32_t Offset) {
PC += Offset;
return true;
}
static bool Jt(InterpState &S, CodePtr &PC, int32_t Offset) {
if (S.Stk.pop<bool>()) {
PC += Offset;
}
return true;
}
static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) {
if (!S.Stk.pop<bool>()) {
PC += Offset;
}
return true;
}
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", off)
#endif
// FIXME: We have the large switch over all opcodes here again, and in
// Interpret().
static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset, PrimType PT) {
[[maybe_unused]] CodePtr PCBefore = RealPC;
size_t StackSizeBefore = S.Stk.size();
auto SpeculativeInterp = [&S, RealPC]() -> bool {
const InterpFrame *StartFrame = S.Current;
CodePtr PC = RealPC;
for (;;) {
auto Op = PC.read<Opcode>();
if (Op == OP_EndSpeculation)
return true;
CodePtr OpPC = PC;
switch (Op) {
#define GET_INTERP
#include "Opcodes.inc"
#undef GET_INTERP
}
}
llvm_unreachable("We didn't see an EndSpeculation op?");
};
if (SpeculativeInterp()) {
if (PT == PT_Ptr) {
const auto &Ptr = S.Stk.pop<Pointer>();
assert(S.Stk.size() == StackSizeBefore);
S.Stk.push<Integral<32, true>>(
Integral<32, true>::from(CheckBCPResult(S, Ptr)));
} else {
// Pop the result from the stack and return success.
TYPE_SWITCH(PT, S.Stk.pop<T>(););
assert(S.Stk.size() == StackSizeBefore);
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(1));
}
} else {
if (!S.inConstantContext())
return Invalid(S, RealPC);
S.Stk.clearTo(StackSizeBefore);
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(0));
}
// RealPC should not have been modified.
assert(*RealPC == *PCBefore);
// Jump to end label. This is a little tricker than just RealPC += Offset
// because our usual jump instructions don't have any arguments, to the offset
// we get is a little too much and we need to subtract the size of the
// bool and PrimType arguments again.
int32_t ParamSize = align(sizeof(PrimType));
assert(Offset >= ParamSize);
RealPC += Offset - ParamSize;
[[maybe_unused]] CodePtr PCCopy = RealPC;
assert(PCCopy.read<Opcode>() == OP_EndSpeculation);
return true;
}
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", on)
#endif
static void diagnoseMissingInitializer(InterpState &S, CodePtr OpPC,
const ValueDecl *VD) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_var_init_unknown, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at) << VD->getSourceRange();
}
static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC,
const ValueDecl *VD);
static bool diagnoseUnknownDecl(InterpState &S, CodePtr OpPC,
const ValueDecl *D) {
// This function tries pretty hard to produce a good diagnostic. Just skip
// tha if nobody will see it anyway.
if (!S.diagnosing())
return false;
if (isa<ParmVarDecl>(D)) {
if (D->getType()->isReferenceType()) {
if (S.inConstantContext() && S.getLangOpts().CPlusPlus &&
!S.getLangOpts().CPlusPlus11)
diagnoseNonConstVariable(S, OpPC, D);
return false;
}
const SourceInfo &Loc = S.Current->getSource(OpPC);
if (S.getLangOpts().CPlusPlus11) {
S.FFDiag(Loc, diag::note_constexpr_function_param_value_unknown) << D;
S.Note(D->getLocation(), diag::note_declared_at) << D->getSourceRange();
} else {
S.FFDiag(Loc);
}
return false;
}
if (!D->getType().isConstQualified()) {
diagnoseNonConstVariable(S, OpPC, D);
} else if (const auto *VD = dyn_cast<VarDecl>(D)) {
if (!VD->getAnyInitializer()) {
diagnoseMissingInitializer(S, OpPC, VD);
} else {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at);
}
}
return false;
}
static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC,
const ValueDecl *VD) {
if (!S.diagnosing())
return;
const SourceInfo &Loc = S.Current->getSource(OpPC);
if (!S.getLangOpts().CPlusPlus) {
S.FFDiag(Loc);
return;
}
if (const auto *VarD = dyn_cast<VarDecl>(VD);
VarD && VarD->getType().isConstQualified() &&
!VarD->getAnyInitializer()) {
diagnoseMissingInitializer(S, OpPC, VD);
return;
}
// Rather random, but this is to match the diagnostic output of the current
// interpreter.
if (isa<ObjCIvarDecl>(VD))
return;
if (VD->getType()->isIntegralOrEnumerationType()) {
S.FFDiag(Loc, diag::note_constexpr_ltor_non_const_int, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at);
return;
}
S.FFDiag(Loc,
S.getLangOpts().CPlusPlus11 ? diag::note_constexpr_ltor_non_constexpr
: diag::note_constexpr_ltor_non_integral,
1)
<< VD << VD->getType();
S.Note(VD->getLocation(), diag::note_declared_at);
}
static bool CheckTemporary(InterpState &S, CodePtr OpPC, const Block *B,
AccessKinds AK) {
if (B->getDeclID()) {
if (!(B->isStatic() && B->isTemporary()))
return true;
const auto *MTE = dyn_cast_if_present<MaterializeTemporaryExpr>(
B->getDescriptor()->asExpr());
if (!MTE)
return true;
// FIXME(perf): Since we do this check on every Load from a static
// temporary, it might make sense to cache the value of the
// isUsableInConstantExpressions call.
if (B->getEvalID() != S.Ctx.getEvalID() &&
!MTE->isUsableInConstantExpressions(S.getASTContext())) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
S.Note(B->getDescriptor()->getLocation(),
diag::note_constexpr_temporary_here);
return false;
}
}
return true;
}
static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (auto ID = Ptr.getDeclID()) {
if (!Ptr.isStatic())
return true;
if (S.P.getCurrentDecl() == ID)
return true;
S.FFDiag(S.Current->getLocation(OpPC), diag::note_constexpr_modify_global);
return false;
}
return true;
}
namespace clang {
namespace interp {
static void popArg(InterpState &S, const Expr *Arg) {
PrimType Ty = S.getContext().classify(Arg).value_or(PT_Ptr);
TYPE_SWITCH(Ty, S.Stk.discard<T>());
}
void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC,
const Function *Func) {
assert(S.Current);
assert(Func);
if (S.Current->Caller && Func->isVariadic()) {
// CallExpr we're look for is at the return PC of the current function, i.e.
// in the caller.
// This code path should be executed very rarely.
unsigned NumVarArgs;
const Expr *const *Args = nullptr;
unsigned NumArgs = 0;
const Expr *CallSite = S.Current->Caller->getExpr(S.Current->getRetPC());
if (const auto *CE = dyn_cast<CallExpr>(CallSite)) {
Args = CE->getArgs();
NumArgs = CE->getNumArgs();
} else if (const auto *CE = dyn_cast<CXXConstructExpr>(CallSite)) {
Args = CE->getArgs();
NumArgs = CE->getNumArgs();
} else
assert(false && "Can't get arguments from that expression type");
assert(NumArgs >= Func->getNumWrittenParams());
NumVarArgs = NumArgs - (Func->getNumWrittenParams() +
isa<CXXOperatorCallExpr>(CallSite));
for (unsigned I = 0; I != NumVarArgs; ++I) {
const Expr *A = Args[NumArgs - 1 - I];
popArg(S, A);
}
}
// And in any case, remove the fixed parameters (the non-variadic ones)
// at the end.
for (PrimType Ty : Func->args_reverse())
TYPE_SWITCH(Ty, S.Stk.discard<T>());
}
bool isConstexprUnknown(const Pointer &P) {
if (!P.isBlockPointer())
return false;
if (P.isDummy())
return isa_and_nonnull<ParmVarDecl>(P.getDeclDesc()->asValueDecl());
return P.getDeclDesc()->IsConstexprUnknown;
}
bool CheckBCPResult(InterpState &S, const Pointer &Ptr) {
if (Ptr.isDummy())
return false;
if (Ptr.isZero())
return true;
if (Ptr.isFunctionPointer())
return false;
if (Ptr.isIntegralPointer())
return true;
if (Ptr.isTypeidPointer())
return true;
if (Ptr.getType()->isAnyComplexType())
return true;
if (const Expr *Base = Ptr.getDeclDesc()->asExpr())
return isa<StringLiteral>(Base) && Ptr.getIndex() == 0;
return false;
}
bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
AccessKinds AK) {
if (Ptr.isActive())
return true;
assert(Ptr.inUnion());
Pointer U = Ptr.getBase();
Pointer C = Ptr;
while (!U.isRoot() && !U.isActive()) {
// A little arbitrary, but this is what the current interpreter does.
// See the AnonymousUnion test in test/AST/ByteCode/unions.cpp.
// GCC's output is more similar to what we would get without
// this condition.
if (U.getRecord() && U.getRecord()->isAnonymousUnion())
break;
C = U;
U = U.getBase();
}
assert(C.isField());
// Consider:
// union U {
// struct {
// int x;
// int y;
// } a;
// }
//
// When activating x, we will also activate a. If we now try to read
// from y, we will get to CheckActive, because y is not active. In that
// case, our U will be a (not a union). We return here and let later code
// handle this.
if (!U.getFieldDesc()->isUnion())
return true;
// Get the inactive field descriptor.
assert(!C.isActive());
const FieldDecl *InactiveField = C.getField();
assert(InactiveField);
// Find the active field of the union.
const Record *R = U.getRecord();
assert(R && R->isUnion() && "Not a union");
const FieldDecl *ActiveField = nullptr;
for (const Record::Field &F : R->fields()) {
const Pointer &Field = U.atField(F.Offset);
if (Field.isActive()) {
ActiveField = Field.getField();
break;
}
}
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_access_inactive_union_member)
<< AK << InactiveField << !ActiveField << ActiveField;
return false;
}
bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!Ptr.isExtern())
return true;
if (Ptr.isInitialized() ||
(Ptr.getDeclDesc()->asVarDecl() == S.EvaluatingDecl))
return true;
if (S.checkingPotentialConstantExpression() && S.getLangOpts().CPlusPlus &&
Ptr.isConst())
return false;
const auto *VD = Ptr.getDeclDesc()->asValueDecl();
diagnoseNonConstVariable(S, OpPC, VD);
return false;
}
bool CheckArray(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!Ptr.isUnknownSizeArray())
return true;
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_unsized_array_indexed);
return false;
}
bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
AccessKinds AK) {
if (Ptr.isZero()) {
const auto &Src = S.Current->getSource(OpPC);
if (Ptr.isField())
S.FFDiag(Src, diag::note_constexpr_null_subobject) << CSK_Field;
else
S.FFDiag(Src, diag::note_constexpr_access_null) << AK;
return false;
}
if (!Ptr.isLive()) {
const auto &Src = S.Current->getSource(OpPC);
if (Ptr.isDynamic()) {
S.FFDiag(Src, diag::note_constexpr_access_deleted_object) << AK;
} else if (!S.checkingPotentialConstantExpression()) {
bool IsTemp = Ptr.isTemporary();
S.FFDiag(Src, diag::note_constexpr_lifetime_ended, 1) << AK << !IsTemp;
if (IsTemp)
S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here);
else
S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
}
return false;
}
return true;
}
bool CheckConstant(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
assert(Desc);
const auto *D = Desc->asVarDecl();
if (!D || D == S.EvaluatingDecl || D->isConstexpr())
return true;
// If we're evaluating the initializer for a constexpr variable in C23, we may
// only read other contexpr variables. Abort here since this one isn't
// constexpr.
if (const auto *VD = dyn_cast_if_present<VarDecl>(S.EvaluatingDecl);
VD && VD->isConstexpr() && S.getLangOpts().C23)
return Invalid(S, OpPC);
QualType T = D->getType();
bool IsConstant = T.isConstant(S.getASTContext());
if (T->isIntegralOrEnumerationType()) {
if (!IsConstant) {
diagnoseNonConstVariable(S, OpPC, D);
return false;
}
return true;
}
if (IsConstant) {
if (S.getLangOpts().CPlusPlus) {
S.CCEDiag(S.Current->getLocation(OpPC),
S.getLangOpts().CPlusPlus11
? diag::note_constexpr_ltor_non_constexpr
: diag::note_constexpr_ltor_non_integral,
1)
<< D << T;
S.Note(D->getLocation(), diag::note_declared_at);
} else {
S.CCEDiag(S.Current->getLocation(OpPC));
}
return true;
}
if (T->isPointerOrReferenceType()) {
if (!T->getPointeeType().isConstant(S.getASTContext()) ||
!S.getLangOpts().CPlusPlus11) {
diagnoseNonConstVariable(S, OpPC, D);
return false;
}
return true;
}
diagnoseNonConstVariable(S, OpPC, D);
return false;
}
static bool CheckConstant(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!Ptr.isStatic() || !Ptr.isBlockPointer())
return true;
if (!Ptr.getDeclID())
return true;
return CheckConstant(S, OpPC, Ptr.getDeclDesc());
}
bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
CheckSubobjectKind CSK) {
if (!Ptr.isZero())
return true;
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_null_subobject)
<< CSK << S.Current->getRange(OpPC);
return false;
}
bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
AccessKinds AK) {
if (!Ptr.isOnePastEnd() && !Ptr.isZeroSizeArray())
return true;
if (S.getLangOpts().CPlusPlus) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_access_past_end)
<< AK << S.Current->getRange(OpPC);
}
return false;
}
bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
CheckSubobjectKind CSK) {
if (!Ptr.isElementPastEnd())
return true;
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_past_end_subobject)
<< CSK << S.Current->getRange(OpPC);
return false;
}
bool CheckSubobject(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
CheckSubobjectKind CSK) {
if (!Ptr.isOnePastEnd())
return true;
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_past_end_subobject)
<< CSK << S.Current->getRange(OpPC);
return false;
}
bool CheckDowncast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
uint32_t Offset) {
uint32_t MinOffset = Ptr.getDeclDesc()->getMetadataSize();
uint32_t PtrOffset = Ptr.getByteOffset();
// We subtract Offset from PtrOffset. The result must be at least
// MinOffset.
if (Offset < PtrOffset && (PtrOffset - Offset) >= MinOffset)
return true;
const auto *E = cast<CastExpr>(S.Current->getExpr(OpPC));
QualType TargetQT = E->getType()->getPointeeType();
QualType MostDerivedQT = Ptr.getDeclPtr().getType();
S.CCEDiag(E, diag::note_constexpr_invalid_downcast)
<< MostDerivedQT << TargetQT;
return false;
}
bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
assert(Ptr.isLive() && "Pointer is not live");
if (!Ptr.isConst())
return true;
if (Ptr.isMutable() && !Ptr.isConstInMutable())
return true;
if (!Ptr.isBlockPointer())
return false;
// The This pointer is writable in constructors and destructors,
// even if isConst() returns true.
if (llvm::is_contained(S.InitializingBlocks, Ptr.block()))
return true;
const QualType Ty = Ptr.getType();
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_modify_const_type) << Ty;
return false;
}
bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
assert(Ptr.isLive() && "Pointer is not live");
if (!Ptr.isMutable())
return true;
// In C++14 onwards, it is permitted to read a mutable member whose
// lifetime began within the evaluation.
if (S.getLangOpts().CPlusPlus14 &&
Ptr.block()->getEvalID() == S.Ctx.getEvalID()) {
// FIXME: This check is necessary because (of the way) we revisit
// variables in Compiler.cpp:visitDeclRef. Revisiting a so far
// unknown variable will get the same EvalID and we end up allowing
// reads from mutable members of it.
if (!S.inConstantContext() && isConstexprUnknown(Ptr))
return false;
return true;
}
const SourceInfo &Loc = S.Current->getSource(OpPC);
const FieldDecl *Field = Ptr.getField();
S.FFDiag(Loc, diag::note_constexpr_access_mutable, 1) << AK_Read << Field;
S.Note(Field->getLocation(), diag::note_declared_at);
return false;
}
static bool CheckVolatile(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
AccessKinds AK) {
assert(Ptr.isLive());
if (!Ptr.isVolatile())
return true;
if (!S.getLangOpts().CPlusPlus)
return Invalid(S, OpPC);
// The reason why Ptr is volatile might be further up the hierarchy.
// Find that pointer.
Pointer P = Ptr;
while (!P.isRoot()) {
if (P.getType().isVolatileQualified())
break;
P = P.getBase();
}
const NamedDecl *ND = nullptr;
int DiagKind;
SourceLocation Loc;
if (const auto *F = P.getField()) {
DiagKind = 2;
Loc = F->getLocation();
ND = F;
} else if (auto *VD = P.getFieldDesc()->asValueDecl()) {
DiagKind = 1;
Loc = VD->getLocation();
ND = VD;
} else {
DiagKind = 0;
if (const auto *E = P.getFieldDesc()->asExpr())
Loc = E->getExprLoc();
}
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_access_volatile_obj, 1)
<< AK << DiagKind << ND;
S.Note(Loc, diag::note_constexpr_volatile_here) << DiagKind;
return false;
}
bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
AccessKinds AK) {
assert(Ptr.isLive());
assert(!Ptr.isInitialized());
return DiagnoseUninitialized(S, OpPC, Ptr.isExtern(), Ptr.getDeclDesc(), AK);
}
bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern,
const Descriptor *Desc, AccessKinds AK) {
if (Extern && S.checkingPotentialConstantExpression())
return false;
if (const auto *VD = Desc->asVarDecl();
VD && (VD->isConstexpr() || VD->hasGlobalStorage())) {
if (VD == S.EvaluatingDecl &&
!(S.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType())) {
if (!S.getLangOpts().CPlusPlus14 &&
!VD->getType().isConstant(S.getASTContext())) {
// Diagnose as non-const read.
diagnoseNonConstVariable(S, OpPC, VD);
} else {
const SourceInfo &Loc = S.Current->getSource(OpPC);
// Diagnose as "read of object outside its lifetime".
S.FFDiag(Loc, diag::note_constexpr_access_uninit)
<< AK << /*IsIndeterminate=*/false;
}
return false;
}
if (VD->getAnyInitializer()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at);
} else {
diagnoseMissingInitializer(S, OpPC, VD);
}
return false;
}
if (!S.checkingPotentialConstantExpression()) {
S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_uninit)
<< AK << /*uninitialized=*/true << S.Current->getRange(OpPC);
}
return false;
}
static bool CheckLifetime(InterpState &S, CodePtr OpPC, Lifetime LT,
AccessKinds AK) {
if (LT == Lifetime::Started)
return true;
if (!S.checkingPotentialConstantExpression()) {
S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_uninit)
<< AK << /*uninitialized=*/false << S.Current->getRange(OpPC);
}
return false;
}
static bool CheckWeak(InterpState &S, CodePtr OpPC, const Block *B) {
if (!B->isWeak())
return true;
const auto *VD = B->getDescriptor()->asVarDecl();
assert(VD);
S.FFDiag(S.Current->getLocation(OpPC), diag::note_constexpr_var_init_weak)
<< VD;
S.Note(VD->getLocation(), diag::note_declared_at);
return false;
}
// The list of checks here is just the one from CheckLoad, but with the
// ones removed that are impossible on primitive global values.
// For example, since those can't be members of structs, they also can't
// be mutable.
bool CheckGlobalLoad(InterpState &S, CodePtr OpPC, const Block *B) {
const auto &Desc =
*reinterpret_cast<const GlobalInlineDescriptor *>(B->rawData());
if (!CheckExtern(S, OpPC, Pointer(const_cast<Block *>(B))))
return false;
if (!CheckConstant(S, OpPC, B->getDescriptor()))
return false;
if (!CheckDummy(S, OpPC, B, AK_Read))
return false;
if (Desc.InitState != GlobalInitState::Initialized)
return DiagnoseUninitialized(S, OpPC, B->isExtern(), B->getDescriptor(),
AK_Read);
if (!CheckTemporary(S, OpPC, B, AK_Read))
return false;
if (!CheckWeak(S, OpPC, B))
return false;
if (B->getDescriptor()->IsVolatile) {
if (!S.getLangOpts().CPlusPlus)
return Invalid(S, OpPC);
const ValueDecl *D = B->getDescriptor()->asValueDecl();
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_access_volatile_obj, 1)
<< AK_Read << 1 << D;
S.Note(D->getLocation(), diag::note_constexpr_volatile_here) << 1;
return false;
}
return true;
}
// Similarly, for local loads.
bool CheckLocalLoad(InterpState &S, CodePtr OpPC, const Block *B) {
assert(!B->isExtern());
const auto &Desc = *reinterpret_cast<const InlineDescriptor *>(B->rawData());
if (!CheckLifetime(S, OpPC, Desc.LifeState, AK_Read))
return false;
if (!Desc.IsInitialized)
return DiagnoseUninitialized(S, OpPC, /*Extern=*/false, B->getDescriptor(),
AK_Read);
if (B->getDescriptor()->IsVolatile) {
if (!S.getLangOpts().CPlusPlus)
return Invalid(S, OpPC);
const ValueDecl *D = B->getDescriptor()->asValueDecl();
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_access_volatile_obj, 1)
<< AK_Read << 1 << D;
S.Note(D->getLocation(), diag::note_constexpr_volatile_here) << 1;
return false;
}
return true;
}
bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
AccessKinds AK) {
if (!CheckLive(S, OpPC, Ptr, AK))
return false;
if (!CheckExtern(S, OpPC, Ptr))
return false;
if (!CheckConstant(S, OpPC, Ptr))
return false;
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK))
return false;
if (!CheckRange(S, OpPC, Ptr, AK))
return false;
if (!CheckActive(S, OpPC, Ptr, AK))
return false;
if (!CheckLifetime(S, OpPC, Ptr.getLifetime(), AK))
return false;
if (!Ptr.isInitialized())
return DiagnoseUninitialized(S, OpPC, Ptr, AK);
if (Ptr.isBlockPointer() && !CheckTemporary(S, OpPC, Ptr.block(), AK))
return false;
if (Ptr.isBlockPointer() && !CheckWeak(S, OpPC, Ptr.block()))
return false;
if (!CheckMutable(S, OpPC, Ptr))
return false;
if (!CheckVolatile(S, OpPC, Ptr, AK))
return false;
return true;
}
/// This is not used by any of the opcodes directly. It's used by
/// EvalEmitter to do the final lvalue-to-rvalue conversion.
bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!CheckLive(S, OpPC, Ptr, AK_Read))
return false;
if (!CheckConstant(S, OpPC, Ptr))
return false;
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Read))
return false;
if (!CheckExtern(S, OpPC, Ptr))
return false;
if (!CheckActive(S, OpPC, Ptr, AK_Read))
return false;
if (!CheckLifetime(S, OpPC, Ptr.getLifetime(), AK_Read))
return false;
if (!Ptr.isInitialized())
return DiagnoseUninitialized(S, OpPC, Ptr, AK_Read);
if (Ptr.isBlockPointer() && !CheckTemporary(S, OpPC, Ptr.block(), AK_Read))
return false;
if (Ptr.isBlockPointer() && !CheckWeak(S, OpPC, Ptr.block()))
return false;
if (!CheckMutable(S, OpPC, Ptr))
return false;
return true;
}
bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!CheckLive(S, OpPC, Ptr, AK_Assign))
return false;
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Assign))
return false;
if (!CheckLifetime(S, OpPC, Ptr.getLifetime(), AK_Assign))
return false;
if (!CheckExtern(S, OpPC, Ptr))
return false;
if (!CheckRange(S, OpPC, Ptr, AK_Assign))
return false;
if (!CheckActive(S, OpPC, Ptr, AK_Assign))
return false;
if (!CheckGlobal(S, OpPC, Ptr))
return false;
if (!CheckConst(S, OpPC, Ptr))
return false;
if (!S.inConstantContext() && isConstexprUnknown(Ptr))
return false;
return true;
}
static bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!CheckLive(S, OpPC, Ptr, AK_MemberCall))
return false;
if (!Ptr.isDummy()) {
if (!CheckExtern(S, OpPC, Ptr))
return false;
if (!CheckRange(S, OpPC, Ptr, AK_MemberCall))
return false;
}
return true;
}
bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!CheckLive(S, OpPC, Ptr, AK_Assign))
return false;
if (!CheckRange(S, OpPC, Ptr, AK_Assign))
return false;
return true;
}
static bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) {
if (F->isVirtual() && !S.getLangOpts().CPlusPlus20) {
const SourceLocation &Loc = S.Current->getLocation(OpPC);
S.CCEDiag(Loc, diag::note_constexpr_virtual_call);
return false;
}
if (S.checkingPotentialConstantExpression() && S.Current->getDepth() != 0)
return false;
if (F->isValid() && F->hasBody() && F->isConstexpr())
return true;
// Implicitly constexpr.
if (F->isLambdaStaticInvoker())
return true;
// Bail out if the function declaration itself is invalid. We will
// have produced a relevant diagnostic while parsing it, so just
// note the problematic sub-expression.
if (F->getDecl()->isInvalidDecl())
return Invalid(S, OpPC);
// Diagnose failed assertions specially.
if (S.Current->getLocation(OpPC).isMacroID() &&
F->getDecl()->getIdentifier()) {
// FIXME: Instead of checking for an implementation-defined function,
// check and evaluate the assert() macro.
StringRef Name = F->getDecl()->getName();
bool AssertFailed =
Name == "__assert_rtn" || Name == "__assert_fail" || Name == "_wassert";
if (AssertFailed) {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_assert_failed);
return false;
}
}
if (S.getLangOpts().CPlusPlus11) {
const FunctionDecl *DiagDecl = F->getDecl();
// Invalid decls have been diagnosed before.
if (DiagDecl->isInvalidDecl())
return false;
// If this function is not constexpr because it is an inherited
// non-constexpr constructor, diagnose that directly.
const auto *CD = dyn_cast<CXXConstructorDecl>(DiagDecl);
if (CD && CD->isInheritingConstructor()) {
const auto *Inherited = CD->getInheritedConstructor().getConstructor();
if (!Inherited->isConstexpr())
DiagDecl = CD = Inherited;
}
// Silently reject constructors of invalid classes. The invalid class
// has been rejected elsewhere before.
if (CD && CD->getParent()->isInvalidDecl())
return false;
// FIXME: If DiagDecl is an implicitly-declared special member function
// or an inheriting constructor, we should be much more explicit about why
// it's not constexpr.
if (CD && CD->isInheritingConstructor()) {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_invalid_inhctor, 1)
<< CD->getInheritedConstructor().getConstructor()->getParent();
S.Note(DiagDecl->getLocation(), diag::note_declared_at);
} else {
// Don't emit anything if the function isn't defined and we're checking
// for a constant expression. It might be defined at the point we're
// actually calling it.
bool IsExtern = DiagDecl->getStorageClass() == SC_Extern;
bool IsDefined = F->isDefined();
if (!IsDefined && !IsExtern && DiagDecl->isConstexpr() &&
S.checkingPotentialConstantExpression())
return false;
// If the declaration is defined, declared 'constexpr' _and_ has a body,
// the below diagnostic doesn't add anything useful.
if (DiagDecl->isDefined() && DiagDecl->isConstexpr() &&
DiagDecl->hasBody())
return false;
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_invalid_function, 1)
<< DiagDecl->isConstexpr() << (bool)CD << DiagDecl;
if (DiagDecl->getDefinition())
S.Note(DiagDecl->getDefinition()->getLocation(),
diag::note_declared_at);
else
S.Note(DiagDecl->getLocation(), diag::note_declared_at);
}
} else {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_invalid_subexpr_in_const_expr);
}
return false;
}
static bool CheckCallDepth(InterpState &S, CodePtr OpPC) {
if ((S.Current->getDepth() + 1) > S.getLangOpts().ConstexprCallDepth) {
S.FFDiag(S.Current->getSource(OpPC),
diag::note_constexpr_depth_limit_exceeded)
<< S.getLangOpts().ConstexprCallDepth;
return false;
}
return true;
}
bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This) {
if (!This.isZero())
return true;
const Expr *E = S.Current->getExpr(OpPC);
if (S.getLangOpts().CPlusPlus11) {
bool IsImplicit = false;
if (const auto *TE = dyn_cast<CXXThisExpr>(E))
IsImplicit = TE->isImplicit();
S.FFDiag(E, diag::note_constexpr_this) << IsImplicit;
} else {
S.FFDiag(E);
}
return false;
}
bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
APFloat::opStatus Status, FPOptions FPO) {
// [expr.pre]p4:
// If during the evaluation of an expression, the result is not
// mathematically defined [...], the behavior is undefined.
// FIXME: C++ rules require us to not conform to IEEE 754 here.
if (Result.isNan()) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.CCEDiag(E, diag::note_constexpr_float_arithmetic)
<< /*NaN=*/true << S.Current->getRange(OpPC);
return S.noteUndefinedBehavior();
}
// In a constant context, assume that any dynamic rounding mode or FP
// exception state matches the default floating-point environment.
if (S.inConstantContext())
return true;
if ((Status & APFloat::opInexact) &&
FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) {
// Inexact result means that it depends on rounding mode. If the requested
// mode is dynamic, the evaluation cannot be made in compile time.
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_dynamic_rounding);
return false;
}
if ((Status != APFloat::opOK) &&
(FPO.getRoundingMode() == llvm::RoundingMode::Dynamic ||
FPO.getExceptionMode() != LangOptions::FPE_Ignore ||
FPO.getAllowFEnvAccess())) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_float_arithmetic_strict);
return false;
}
if ((Status & APFloat::opStatus::opInvalidOp) &&
FPO.getExceptionMode() != LangOptions::FPE_Ignore) {
const SourceInfo &E = S.Current->getSource(OpPC);
// There is no usefully definable result.
S.FFDiag(E);
return false;
}
return true;
}
bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) {
if (S.getLangOpts().CPlusPlus20)
return true;
const SourceInfo &E = S.Current->getSource(OpPC);
S.CCEDiag(E, diag::note_constexpr_new);
return true;
}
bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
DynamicAllocator::Form AllocForm,
DynamicAllocator::Form DeleteForm, const Descriptor *D,
const Expr *NewExpr) {
if (AllocForm == DeleteForm)
return true;
QualType TypeToDiagnose = D->getDataType(S.getASTContext());
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
<< static_cast<int>(DeleteForm) << static_cast<int>(AllocForm)
<< TypeToDiagnose;
S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here)
<< NewExpr->getSourceRange();
return false;
}
bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
const Pointer &Ptr) {
// Regular new type(...) call.
if (isa_and_nonnull<CXXNewExpr>(Source))
return true;
// operator new.
if (const auto *CE = dyn_cast_if_present<CallExpr>(Source);
CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new)
return true;
// std::allocator.allocate() call
if (const auto *MCE = dyn_cast_if_present<CXXMemberCallExpr>(Source);
MCE && MCE->getMethodDecl()->getIdentifier()->isStr("allocate"))
return true;
// Whatever this is, we didn't heap allocate it.
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc)
<< Ptr.toDiagnosticString(S.getASTContext());
if (Ptr.isTemporary())
S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here);
else
S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
return false;
}
/// We aleady know the given DeclRefExpr is invalid for some reason,
/// now figure out why and print appropriate diagnostics.
bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {
const ValueDecl *D = DR->getDecl();
return diagnoseUnknownDecl(S, OpPC, D);
}
bool CheckDummy(InterpState &S, CodePtr OpPC, const Block *B, AccessKinds AK) {
const Descriptor *Desc = B->getDescriptor();
if (!Desc->isDummy())
return true;
const ValueDecl *D = Desc->asValueDecl();
if (!D)
return false;
if (AK == AK_Read || AK == AK_Increment || AK == AK_Decrement)
return diagnoseUnknownDecl(S, OpPC, D);
if (AK == AK_Destroy || S.getLangOpts().CPlusPlus14) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_modify_global);
}
return false;
}
static bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
const CallExpr *CE, unsigned ArgSize) {
auto Args = ArrayRef(CE->getArgs(), CE->getNumArgs());
auto NonNullArgs = collectNonNullArgs(F->getDecl(), Args);
unsigned Offset = 0;
unsigned Index = 0;
for (const Expr *Arg : Args) {
if (NonNullArgs[Index] && Arg->getType()->isPointerType()) {
const Pointer &ArgPtr = S.Stk.peek<Pointer>(ArgSize - Offset);
if (ArgPtr.isZero()) {
const SourceLocation &Loc = S.Current->getLocation(OpPC);
S.CCEDiag(Loc, diag::note_non_null_attribute_failed);
return false;
}
}
Offset += align(primSize(S.Ctx.classify(Arg).value_or(PT_Ptr)));
++Index;
}
return true;
}
static bool runRecordDestructor(InterpState &S, CodePtr OpPC,
const Pointer &BasePtr,
const Descriptor *Desc) {
assert(Desc->isRecord());
const Record *R = Desc->ElemRecord;
assert(R);
if (Pointer::pointToSameBlock(BasePtr, S.Current->getThis()) &&
S.Current->getFunction()->isDestructor()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_double_destroy);
return false;
}
// Destructor of this record.
if (const CXXDestructorDecl *Dtor = R->getDestructor();
Dtor && !Dtor->isTrivial()) {
const Function *DtorFunc = S.getContext().getOrCreateFunction(Dtor);
if (!DtorFunc)
return false;
S.Stk.push<Pointer>(BasePtr);
if (!Call(S, OpPC, DtorFunc, 0))
return false;
}
return true;
}
static bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
assert(B);
const Descriptor *Desc = B->getDescriptor();
if (Desc->isPrimitive() || Desc->isPrimitiveArray())
return true;
assert(Desc->isRecord() || Desc->isCompositeArray());
if (Desc->isCompositeArray()) {
unsigned N = Desc->getNumElems();
if (N == 0)
return true;
const Descriptor *ElemDesc = Desc->ElemDesc;
assert(ElemDesc->isRecord());
Pointer RP(const_cast<Block *>(B));
for (int I = static_cast<int>(N) - 1; I >= 0; --I) {
if (!runRecordDestructor(S, OpPC, RP.atIndex(I).narrow(), ElemDesc))
return false;
}
return true;
}
assert(Desc->isRecord());
return runRecordDestructor(S, OpPC, Pointer(const_cast<Block *>(B)), Desc);
}
static bool hasVirtualDestructor(QualType T) {
if (const CXXRecordDecl *RD = T->getAsCXXRecordDecl())
if (const CXXDestructorDecl *DD = RD->getDestructor())
return DD->isVirtual();
return false;
}
bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm,
bool IsGlobalDelete) {
if (!CheckDynamicMemoryAllocation(S, OpPC))
return false;
DynamicAllocator &Allocator = S.getAllocator();
const Expr *Source = nullptr;
const Block *BlockToDelete = nullptr;
{
// Extra scope for this so the block doesn't have this pointer
// pointing to it when we destroy it.
Pointer Ptr = S.Stk.pop<Pointer>();
// Deleteing nullptr is always fine.
if (Ptr.isZero())
return true;
// Remove base casts.
QualType InitialType = Ptr.getType();
while (Ptr.isBaseClass())
Ptr = Ptr.getBase();
Source = Ptr.getDeclDesc()->asExpr();
BlockToDelete = Ptr.block();
// Check that new[]/delete[] or new/delete were used, not a mixture.
const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
if (std::optional<DynamicAllocator::Form> AllocForm =
Allocator.getAllocationForm(Source)) {
DynamicAllocator::Form DeleteForm =
DeleteIsArrayForm ? DynamicAllocator::Form::Array
: DynamicAllocator::Form::NonArray;
if (!CheckNewDeleteForms(S, OpPC, *AllocForm, DeleteForm, BlockDesc,
Source))
return false;
}
// For the non-array case, the types must match if the static type
// does not have a virtual destructor.
if (!DeleteIsArrayForm && Ptr.getType() != InitialType &&
!hasVirtualDestructor(InitialType)) {
S.FFDiag(S.Current->getSource(OpPC),
diag::note_constexpr_delete_base_nonvirt_dtor)
<< InitialType << Ptr.getType();
return false;
}
if (!Ptr.isRoot() || Ptr.isOnePastEnd() ||
(Ptr.isArrayElement() && Ptr.getIndex() != 0)) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_delete_subobject)
<< Ptr.toDiagnosticString(S.getASTContext()) << Ptr.isOnePastEnd();
return false;
}
if (!CheckDeleteSource(S, OpPC, Source, Ptr))
return false;
// For a class type with a virtual destructor, the selected operator delete
// is the one looked up when building the destructor.
if (!DeleteIsArrayForm && !IsGlobalDelete) {
QualType AllocType = Ptr.getType();
auto getVirtualOperatorDelete = [](QualType T) -> const FunctionDecl * {
if (const CXXRecordDecl *RD = T->getAsCXXRecordDecl())
if (const CXXDestructorDecl *DD = RD->getDestructor())
return DD->isVirtual() ? DD->getOperatorDelete() : nullptr;
return nullptr;
};
if (const FunctionDecl *VirtualDelete =
getVirtualOperatorDelete(AllocType);
VirtualDelete &&
!VirtualDelete
->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) {
S.FFDiag(S.Current->getSource(OpPC),
diag::note_constexpr_new_non_replaceable)
<< isa<CXXMethodDecl>(VirtualDelete) << VirtualDelete;
return false;
}
}
}
assert(Source);
assert(BlockToDelete);
// Invoke destructors before deallocating the memory.
if (!RunDestructors(S, OpPC, BlockToDelete))
return false;
if (!Allocator.deallocate(Source, BlockToDelete, S)) {
// Nothing has been deallocated, this must be a double-delete.
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_double_delete);
return false;
}
return true;
}
void diagnoseEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED,
const APSInt &Value) {
if (S.EvaluatingDecl && !S.EvaluatingDecl->isConstexpr())
return;
llvm::APInt Min;
llvm::APInt Max;
ED->getValueRange(Max, Min);
--Max;
if (ED->getNumNegativeBits() &&
(Max.slt(Value.getSExtValue()) || Min.sgt(Value.getSExtValue()))) {
const SourceLocation &Loc = S.Current->getLocation(OpPC);
S.CCEDiag(Loc, diag::note_constexpr_unscoped_enum_out_of_range)
<< llvm::toString(Value, 10) << Min.getSExtValue() << Max.getSExtValue()
<< ED;
} else if (!ED->getNumNegativeBits() && Max.ult(Value.getZExtValue())) {
const SourceLocation &Loc = S.Current->getLocation(OpPC);
S.CCEDiag(Loc, diag::note_constexpr_unscoped_enum_out_of_range)
<< llvm::toString(Value, 10) << Min.getZExtValue() << Max.getZExtValue()
<< ED;
}
}
bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T) {
assert(T);
assert(!S.getLangOpts().CPlusPlus23);
// C++1y: A constant initializer for an object o [...] may also invoke
// constexpr constructors for o and its subobjects even if those objects
// are of non-literal class types.
//
// C++11 missed this detail for aggregates, so classes like this:
// struct foo_t { union { int i; volatile int j; } u; };
// are not (obviously) initializable like so:
// __attribute__((__require_constant_initialization__))
// static const foo_t x = {{0}};
// because "i" is a subobject with non-literal initialization (due to the
// volatile member of the union). See:
// http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1677
// Therefore, we use the C++1y behavior.
if (S.Current->getFunction() && S.Current->getFunction()->isConstructor() &&
S.Current->getThis().getDeclDesc()->asDecl() == S.EvaluatingDecl) {
return true;
}
const Expr *E = S.Current->getExpr(OpPC);
if (S.getLangOpts().CPlusPlus11)
S.FFDiag(E, diag::note_constexpr_nonliteral) << E->getType();
else
S.FFDiag(E, diag::note_invalid_subexpr_in_const_expr);
return false;
}
static bool getField(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
uint32_t Off) {
if (S.getLangOpts().CPlusPlus && S.inConstantContext() &&
!CheckNull(S, OpPC, Ptr, CSK_Field))
return false;
if (!CheckRange(S, OpPC, Ptr, CSK_Field))
return false;
if (!CheckArray(S, OpPC, Ptr))
return false;
if (!CheckSubobject(S, OpPC, Ptr, CSK_Field))
return false;
if (Ptr.isIntegralPointer()) {
S.Stk.push<Pointer>(Ptr.asIntPointer().atOffset(S.getASTContext(), Off));
return true;
}
if (!Ptr.isBlockPointer()) {
// FIXME: The only time we (seem to) get here is when trying to access a
// field of a typeid pointer. In that case, we're supposed to diagnose e.g.
// `typeid(int).name`, but we currently diagnose `&typeid(int)`.
S.FFDiag(S.Current->getSource(OpPC),
diag::note_constexpr_access_unreadable_object)
<< AK_Read << Ptr.toDiagnosticString(S.getASTContext());
return false;
}
if ((Ptr.getByteOffset() + Off) >= Ptr.block()->getSize())
return false;
S.Stk.push<Pointer>(Ptr.atField(Off));
return true;
}
bool GetPtrField(InterpState &S, CodePtr OpPC, uint32_t Off) {
const auto &Ptr = S.Stk.peek<Pointer>();
return getField(S, OpPC, Ptr, Off);
}
bool GetPtrFieldPop(InterpState &S, CodePtr OpPC, uint32_t Off) {
const auto &Ptr = S.Stk.pop<Pointer>();
return getField(S, OpPC, Ptr, Off);
}
static bool checkConstructor(InterpState &S, CodePtr OpPC, const Function *Func,
const Pointer &ThisPtr) {
assert(Func->isConstructor());
if (Func->getParentDecl()->isInvalidDecl())
return false;
const Descriptor *D = ThisPtr.getFieldDesc();
// FIXME: I think this case is not 100% correct. E.g. a pointer into a
// subobject of a composite array.
if (!D->ElemRecord)
return true;
if (D->ElemRecord->getNumVirtualBases() == 0)
return true;
S.FFDiag(S.Current->getLocation(OpPC), diag::note_constexpr_virtual_base)
<< Func->getParentDecl();
return false;
}
bool CheckDestructor(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!CheckLive(S, OpPC, Ptr, AK_Destroy))
return false;
if (!CheckTemporary(S, OpPC, Ptr.block(), AK_Destroy))
return false;
if (!CheckRange(S, OpPC, Ptr, AK_Destroy))
return false;
// Can't call a dtor on a global variable.
if (Ptr.block()->isStatic()) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_modify_global);
return false;
}
return CheckActive(S, OpPC, Ptr, AK_Destroy);
}
static void compileFunction(InterpState &S, const Function *Func) {
Compiler<ByteCodeEmitter>(S.getContext(), S.P)
.compileFunc(Func->getDecl()->getMostRecentDecl(),
const_cast<Function *>(Func));
}
bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
uint32_t VarArgSize) {
if (Func->hasThisPointer()) {
size_t ArgSize = Func->getArgSize() + VarArgSize;
size_t ThisOffset = ArgSize - (Func->hasRVO() ? primSize(PT_Ptr) : 0);
const Pointer &ThisPtr = S.Stk.peek<Pointer>(ThisOffset);
// If the current function is a lambda static invoker and
// the function we're about to call is a lambda call operator,
// skip the CheckInvoke, since the ThisPtr is a null pointer
// anyway.
if (!(S.Current->getFunction() &&
S.Current->getFunction()->isLambdaStaticInvoker() &&
Func->isLambdaCallOperator())) {
if (!CheckInvoke(S, OpPC, ThisPtr))
return false;
}
if (S.checkingPotentialConstantExpression())
return false;
}
if (!Func->isFullyCompiled())
compileFunction(S, Func);
if (!CheckCallable(S, OpPC, Func))
return false;
if (!CheckCallDepth(S, OpPC))
return false;
auto NewFrame = std::make_unique<InterpFrame>(S, Func, OpPC, VarArgSize);
InterpFrame *FrameBefore = S.Current;
S.Current = NewFrame.get();
// Note that we cannot assert(CallResult.hasValue()) here since
// Ret() above only sets the APValue if the curent frame doesn't
// have a caller set.
if (Interpret(S)) {
NewFrame.release(); // Frame was delete'd already.
assert(S.Current == FrameBefore);
return true;
}
// Interpreting the function failed somehow. Reset to
// previous state.
S.Current = FrameBefore;
return false;
}
bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
uint32_t VarArgSize) {
assert(Func);
auto cleanup = [&]() -> bool {
cleanupAfterFunctionCall(S, OpPC, Func);
return false;
};
if (Func->hasThisPointer()) {
size_t ArgSize = Func->getArgSize() + VarArgSize;
size_t ThisOffset = ArgSize - (Func->hasRVO() ? primSize(PT_Ptr) : 0);
const Pointer &ThisPtr = S.Stk.peek<Pointer>(ThisOffset);
// C++23 [expr.const]p5.6
// an invocation of a virtual function ([class.virtual]) for an object whose
// dynamic type is constexpr-unknown;
if (ThisPtr.isDummy() && Func->isVirtual())
return false;
// If the current function is a lambda static invoker and
// the function we're about to call is a lambda call operator,
// skip the CheckInvoke, since the ThisPtr is a null pointer
// anyway.
if (S.Current->getFunction() &&
S.Current->getFunction()->isLambdaStaticInvoker() &&
Func->isLambdaCallOperator()) {
assert(ThisPtr.isZero());
} else {
if (!CheckInvoke(S, OpPC, ThisPtr))
return cleanup();
if (!Func->isConstructor() && !Func->isDestructor() &&
!CheckActive(S, OpPC, ThisPtr, AK_MemberCall))
return false;
}
if (Func->isConstructor() && !checkConstructor(S, OpPC, Func, ThisPtr))
return false;
if (Func->isDestructor() && !CheckDestructor(S, OpPC, ThisPtr))
return false;
if (Func->isConstructor() || Func->isDestructor())
S.InitializingBlocks.push_back(ThisPtr.block());
}
if (!Func->isFullyCompiled())
compileFunction(S, Func);
if (!CheckCallable(S, OpPC, Func))
return cleanup();
// FIXME: The isConstructor() check here is not always right. The current
// constant evaluator is somewhat inconsistent in when it allows a function
// call when checking for a constant expression.
if (Func->hasThisPointer() && S.checkingPotentialConstantExpression() &&
!Func->isConstructor())
return cleanup();
if (!CheckCallDepth(S, OpPC))
return cleanup();
auto NewFrame = std::make_unique<InterpFrame>(S, Func, OpPC, VarArgSize);
InterpFrame *FrameBefore = S.Current;
S.Current = NewFrame.get();
InterpStateCCOverride CCOverride(S, Func->isImmediate());
// Note that we cannot assert(CallResult.hasValue()) here since
// Ret() above only sets the APValue if the curent frame doesn't
// have a caller set.
bool Success = Interpret(S);
// Remove initializing block again.
if (Func->isConstructor() || Func->isDestructor())
S.InitializingBlocks.pop_back();
if (!Success) {
// Interpreting the function failed somehow. Reset to
// previous state.
S.Current = FrameBefore;
return false;
}
NewFrame.release(); // Frame was delete'd already.
assert(S.Current == FrameBefore);
return true;
}
bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func,
uint32_t VarArgSize) {
assert(Func->hasThisPointer());
assert(Func->isVirtual());
size_t ArgSize = Func->getArgSize() + VarArgSize;
size_t ThisOffset = ArgSize - (Func->hasRVO() ? primSize(PT_Ptr) : 0);
Pointer &ThisPtr = S.Stk.peek<Pointer>(ThisOffset);
const FunctionDecl *Callee = Func->getDecl();
if (!Func->isFullyCompiled())
compileFunction(S, Func);
// C++2a [class.abstract]p6:
// the effect of making a virtual call to a pure virtual function [...] is
// undefined
if (Callee->isPureVirtual()) {
S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_pure_virtual_call,
1)
<< Callee;
S.Note(Callee->getLocation(), diag::note_declared_at);
return false;
}
const CXXRecordDecl *DynamicDecl = nullptr;
{
Pointer TypePtr = ThisPtr;
while (TypePtr.isBaseClass())
TypePtr = TypePtr.getBase();
QualType DynamicType = TypePtr.getType();
if (DynamicType->isPointerType() || DynamicType->isReferenceType())
DynamicDecl = DynamicType->getPointeeCXXRecordDecl();
else
DynamicDecl = DynamicType->getAsCXXRecordDecl();
}
assert(DynamicDecl);
const auto *StaticDecl = cast<CXXRecordDecl>(Func->getParentDecl());
const auto *InitialFunction = cast<CXXMethodDecl>(Callee);
const CXXMethodDecl *Overrider;
if (StaticDecl != DynamicDecl) {
if (!DynamicDecl->isDerivedFrom(StaticDecl))
return false;
Overrider = S.getContext().getOverridingFunction(DynamicDecl, StaticDecl,
InitialFunction);
} else {
Overrider = InitialFunction;
}
if (Overrider != InitialFunction) {
// DR1872: An instantiated virtual constexpr function can't be called in a
// constant expression (prior to C++20). We can still constant-fold such a
// call.
if (!S.getLangOpts().CPlusPlus20 && Overrider->isVirtual()) {
const Expr *E = S.Current->getExpr(OpPC);
S.CCEDiag(E, diag::note_constexpr_virtual_call) << E->getSourceRange();
}
Func = S.getContext().getOrCreateFunction(Overrider);
const CXXRecordDecl *ThisFieldDecl =
ThisPtr.getFieldDesc()->getType()->getAsCXXRecordDecl();
if (Func->getParentDecl()->isDerivedFrom(ThisFieldDecl)) {
// If the function we call is further DOWN the hierarchy than the
// FieldDesc of our pointer, just go up the hierarchy of this field
// the furthest we can go.
while (ThisPtr.isBaseClass())
ThisPtr = ThisPtr.getBase();
}
}
if (!Call(S, OpPC, Func, VarArgSize))
return false;
// Covariant return types. The return type of Overrider is a pointer
// or reference to a class type.
if (Overrider != InitialFunction &&
Overrider->getReturnType()->isPointerOrReferenceType() &&
InitialFunction->getReturnType()->isPointerOrReferenceType()) {
QualType OverriderPointeeType =
Overrider->getReturnType()->getPointeeType();
QualType InitialPointeeType =
InitialFunction->getReturnType()->getPointeeType();
// We've called Overrider above, but calling code expects us to return what
// InitialFunction returned. According to the rules for covariant return
// types, what InitialFunction returns needs to be a base class of what
// Overrider returns. So, we need to do an upcast here.
unsigned Offset = S.getContext().collectBaseOffset(
InitialPointeeType->getAsRecordDecl(),
OverriderPointeeType->getAsRecordDecl());
return GetPtrBasePop(S, OpPC, Offset, /*IsNullOK=*/true);
}
return true;
}
bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE,
uint32_t BuiltinID) {
// A little arbitrary, but the current interpreter allows evaluation
// of builtin functions in this mode, with some exceptions.
if (BuiltinID == Builtin::BI__builtin_operator_new &&
S.checkingPotentialConstantExpression())
return false;
return InterpretBuiltin(S, OpPC, CE, BuiltinID);
}
bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
const CallExpr *CE) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
if (Ptr.isZero()) {
const auto *E = cast<CallExpr>(S.Current->getExpr(OpPC));
S.FFDiag(E, diag::note_constexpr_null_callee)
<< const_cast<Expr *>(E->getCallee()) << E->getSourceRange();
return false;
}
if (!Ptr.isFunctionPointer())
return Invalid(S, OpPC);
const FunctionPointer &FuncPtr = Ptr.asFunctionPointer();
const Function *F = FuncPtr.getFunction();
assert(F);
// Don't allow calling block pointers.
if (!F->getDecl())
return Invalid(S, OpPC);
// This happens when the call expression has been cast to
// something else, but we don't support that.
if (S.Ctx.classify(F->getDecl()->getReturnType()) !=
S.Ctx.classify(CE->getCallReturnType(S.getASTContext())))
return false;
// Check argument nullability state.
if (F->hasNonNullAttr()) {
if (!CheckNonNullArgs(S, OpPC, F, CE, ArgSize))
return false;
}
assert(ArgSize >= F->getWrittenArgSize());
uint32_t VarArgSize = ArgSize - F->getWrittenArgSize();
// We need to do this explicitly here since we don't have the necessary
// information to do it automatically.
if (F->isThisPointerExplicit())
VarArgSize -= align(primSize(PT_Ptr));
if (F->isVirtual())
return CallVirt(S, OpPC, F, VarArgSize);
return Call(S, OpPC, F, VarArgSize);
}
static void startLifetimeRecurse(const Pointer &Ptr) {
if (const Record *R = Ptr.getRecord()) {
Ptr.startLifetime();
for (const Record::Field &Fi : R->fields())
startLifetimeRecurse(Ptr.atField(Fi.Offset));
return;
}
if (const Descriptor *FieldDesc = Ptr.getFieldDesc();
FieldDesc->isCompositeArray()) {
assert(Ptr.getLifetime() == Lifetime::Started);
for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I)
startLifetimeRecurse(Ptr.atIndex(I).narrow());
return;
}
Ptr.startLifetime();
}
bool StartLifetime(InterpState &S, CodePtr OpPC) {
const auto &Ptr = S.Stk.peek<Pointer>();
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
return false;
startLifetimeRecurse(Ptr.narrow());
return true;
}
// FIXME: It might be better to the recursing as part of the generated code for
// a destructor?
static void endLifetimeRecurse(const Pointer &Ptr) {
if (const Record *R = Ptr.getRecord()) {
Ptr.endLifetime();
for (const Record::Field &Fi : R->fields())
endLifetimeRecurse(Ptr.atField(Fi.Offset));
return;
}
if (const Descriptor *FieldDesc = Ptr.getFieldDesc();
FieldDesc->isCompositeArray()) {
// No endLifetime() for array roots.
assert(Ptr.getLifetime() == Lifetime::Started);
for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I)
endLifetimeRecurse(Ptr.atIndex(I).narrow());
return;
}
Ptr.endLifetime();
}
/// Ends the lifetime of the peek'd pointer.
bool EndLifetime(InterpState &S, CodePtr OpPC) {
const auto &Ptr = S.Stk.peek<Pointer>();
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
return false;
endLifetimeRecurse(Ptr.narrow());
return true;
}
/// Ends the lifetime of the pop'd pointer.
bool EndLifetimePop(InterpState &S, CodePtr OpPC) {
const auto &Ptr = S.Stk.pop<Pointer>();
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
return false;
endLifetimeRecurse(Ptr.narrow());
return true;
}
bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
std::optional<uint64_t> ArraySize) {
const Pointer &Ptr = S.Stk.peek<Pointer>();
if (Ptr.inUnion() && Ptr.getBase().getRecord()->isUnion())
Ptr.activate();
// Similar to CheckStore(), but with the additional CheckTemporary() call and
// the AccessKinds are different.
if (!CheckTemporary(S, OpPC, Ptr.block(), AK_Construct))
return false;
if (!CheckLive(S, OpPC, Ptr, AK_Construct))
return false;
if (!CheckDummy(S, OpPC, Ptr.block(), AK_Construct))
return false;
// CheckLifetime for this and all base pointers.
for (Pointer P = Ptr;;) {
if (!CheckLifetime(S, OpPC, P.getLifetime(), AK_Construct))
return false;
if (P.isRoot())
break;
P = P.getBase();
}
if (!CheckExtern(S, OpPC, Ptr))
return false;
if (!CheckRange(S, OpPC, Ptr, AK_Construct))
return false;
if (!CheckGlobal(S, OpPC, Ptr))
return false;
if (!CheckConst(S, OpPC, Ptr))
return false;
if (!S.inConstantContext() && isConstexprUnknown(Ptr))
return false;
if (!InvalidNewDeleteExpr(S, OpPC, E))
return false;
const auto *NewExpr = cast<CXXNewExpr>(E);
QualType StorageType = Ptr.getFieldDesc()->getDataType(S.getASTContext());
const ASTContext &ASTCtx = S.getASTContext();
QualType AllocType;
if (ArraySize) {
AllocType = ASTCtx.getConstantArrayType(
NewExpr->getAllocatedType(),
APInt(64, static_cast<uint64_t>(*ArraySize), false), nullptr,
ArraySizeModifier::Normal, 0);
} else {
AllocType = NewExpr->getAllocatedType();
}
unsigned StorageSize = 1;
unsigned AllocSize = 1;
if (const auto *CAT = dyn_cast<ConstantArrayType>(AllocType))
AllocSize = CAT->getZExtSize();
if (const auto *CAT = dyn_cast<ConstantArrayType>(StorageType))
StorageSize = CAT->getZExtSize();
if (AllocSize > StorageSize ||
!ASTCtx.hasSimilarType(ASTCtx.getBaseElementType(AllocType),
ASTCtx.getBaseElementType(StorageType))) {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_placement_new_wrong_type)
<< StorageType << AllocType;
return false;
}
// Can't activate fields in a union, unless the direct base is the union.
if (Ptr.inUnion() && !Ptr.isActive() && !Ptr.getBase().getRecord()->isUnion())
return CheckActive(S, OpPC, Ptr, AK_Construct);
return true;
}
bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E) {
assert(E);
if (const auto *NewExpr = dyn_cast<CXXNewExpr>(E)) {
const FunctionDecl *OperatorNew = NewExpr->getOperatorNew();
if (NewExpr->getNumPlacementArgs() > 0) {
// This is allowed pre-C++26, but only an std function.
if (S.getLangOpts().CPlusPlus26 || S.Current->isStdFunction())
return true;
S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_placement)
<< /*C++26 feature*/ 1 << E->getSourceRange();
} else if (
!OperatorNew
->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) {
S.FFDiag(S.Current->getSource(OpPC),
diag::note_constexpr_new_non_replaceable)
<< isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
return false;
} else if (!S.getLangOpts().CPlusPlus26 &&
NewExpr->getNumPlacementArgs() == 1 &&
!OperatorNew->isReservedGlobalPlacementOperator()) {
if (!S.getLangOpts().CPlusPlus26) {
S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_placement)
<< /*Unsupported*/ 0 << E->getSourceRange();
return false;
}
return true;
}
} else {
const auto *DeleteExpr = cast<CXXDeleteExpr>(E);
const FunctionDecl *OperatorDelete = DeleteExpr->getOperatorDelete();
if (!OperatorDelete
->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) {
S.FFDiag(S.Current->getSource(OpPC),
diag::note_constexpr_new_non_replaceable)
<< isa<CXXMethodDecl>(OperatorDelete) << OperatorDelete;
return false;
}
}
return false;
}
bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC,
const FixedPoint &FP) {
const Expr *E = S.Current->getExpr(OpPC);
if (S.checkingForUndefinedBehavior()) {
S.getASTContext().getDiagnostics().Report(
E->getExprLoc(), diag::warn_fixedpoint_constant_overflow)
<< FP.toDiagnosticString(S.getASTContext()) << E->getType();
}
S.CCEDiag(E, diag::note_constexpr_overflow)
<< FP.toDiagnosticString(S.getASTContext()) << E->getType();
return S.noteUndefinedBehavior();
}
bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc,
diag::err_shufflevector_minus_one_is_undefined_behavior_constexpr)
<< Index;
return false;
}
bool CheckPointerToIntegralCast(InterpState &S, CodePtr OpPC,
const Pointer &Ptr, unsigned BitWidth) {
if (Ptr.isDummy())
return false;
if (Ptr.isFunctionPointer())
return true;
const SourceInfo &E = S.Current->getSource(OpPC);
S.CCEDiag(E, diag::note_constexpr_invalid_cast)
<< 2 << S.getLangOpts().CPlusPlus << S.Current->getRange(OpPC);
if (Ptr.isBlockPointer() && !Ptr.isZero()) {
// Only allow based lvalue casts if they are lossless.
if (S.getASTContext().getTargetInfo().getPointerWidth(LangAS::Default) !=
BitWidth)
return Invalid(S, OpPC);
}
return true;
}
bool CastPointerIntegralAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
if (!CheckPointerToIntegralCast(S, OpPC, Ptr, BitWidth))
return false;
auto Result = S.allocAP<IntegralAP<false>>(BitWidth);
Result.copy(APInt(BitWidth, Ptr.getIntegerRepresentation()));
S.Stk.push<IntegralAP<false>>(Result);
return true;
}
bool CastPointerIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
if (!CheckPointerToIntegralCast(S, OpPC, Ptr, BitWidth))
return false;
auto Result = S.allocAP<IntegralAP<true>>(BitWidth);
Result.copy(APInt(BitWidth, Ptr.getIntegerRepresentation()));
S.Stk.push<IntegralAP<true>>(Result);
return true;
}
bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
bool TargetIsUCharOrByte) {
// This is always fine.
if (!HasIndeterminateBits)
return true;
// Indeterminate bits can only be bitcast to unsigned char or std::byte.
if (TargetIsUCharOrByte)
return true;
const Expr *E = S.Current->getExpr(OpPC);
QualType ExprType = E->getType();
S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest)
<< ExprType << S.getLangOpts().CharIsSigned << E->getSourceRange();
return false;
}
bool GetTypeid(InterpState &S, CodePtr OpPC, const Type *TypePtr,
const Type *TypeInfoType) {
S.Stk.push<Pointer>(TypePtr, TypeInfoType);
return true;
}
bool GetTypeidPtr(InterpState &S, CodePtr OpPC, const Type *TypeInfoType) {
const auto &P = S.Stk.pop<Pointer>();
if (!P.isBlockPointer())
return false;
// Pick the most-derived type.
CanQualType T = P.getDeclPtr().getType()->getCanonicalTypeUnqualified();
// ... unless we're currently constructing this object.
// FIXME: We have a similar check to this in more places.
if (S.Current->getFunction()) {
for (const InterpFrame *Frame = S.Current; Frame; Frame = Frame->Caller) {
if (const Function *Func = Frame->getFunction();
Func && (Func->isConstructor() || Func->isDestructor()) &&
P.block() == Frame->getThis().block()) {
T = S.getContext().getASTContext().getCanonicalTagType(
Func->getParentDecl());
break;
}
}
}
S.Stk.push<Pointer>(T->getTypePtr(), TypeInfoType);
return true;
}
bool DiagTypeid(InterpState &S, CodePtr OpPC) {
const auto *E = cast<CXXTypeidExpr>(S.Current->getExpr(OpPC));
S.CCEDiag(E, diag::note_constexpr_typeid_polymorphic)
<< E->getExprOperand()->getType()
<< E->getExprOperand()->getSourceRange();
return false;
}
bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
const Pointer &RHS) {
unsigned LHSOffset = LHS.getIndex();
unsigned RHSOffset = RHS.getIndex();
unsigned LHSLength = (LHS.getNumElems() - 1) * LHS.elemSize();
unsigned RHSLength = (RHS.getNumElems() - 1) * RHS.elemSize();
StringRef LHSStr((const char *)LHS.atIndex(0).getRawAddress(), LHSLength);
StringRef RHSStr((const char *)RHS.atIndex(0).getRawAddress(), RHSLength);
int32_t IndexDiff = RHSOffset - LHSOffset;
if (IndexDiff < 0) {
if (static_cast<int32_t>(LHSLength) < -IndexDiff)
return false;
LHSStr = LHSStr.drop_front(-IndexDiff);
} else {
if (static_cast<int32_t>(RHSLength) < IndexDiff)
return false;
RHSStr = RHSStr.drop_front(IndexDiff);
}
unsigned ShorterCharWidth;
StringRef Shorter;
StringRef Longer;
if (LHSLength < RHSLength) {
ShorterCharWidth = LHS.elemSize();
Shorter = LHSStr;
Longer = RHSStr;
} else {
ShorterCharWidth = RHS.elemSize();
Shorter = RHSStr;
Longer = LHSStr;
}
// The null terminator isn't included in the string data, so check for it
// manually. If the longer string doesn't have a null terminator where the
// shorter string ends, they aren't potentially overlapping.
for (unsigned NullByte : llvm::seq(ShorterCharWidth)) {
if (Shorter.size() + NullByte >= Longer.size())
break;
if (Longer[Shorter.size() + NullByte])
return false;
}
return Shorter == Longer.take_front(Shorter.size());
}
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
PrimType T) {
if (T == PT_IntAPS) {
auto &Val = Ptr.deref<IntegralAP<true>>();
if (!Val.singleWord()) {
uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
Val.take(NewMemory);
}
} else if (T == PT_IntAP) {
auto &Val = Ptr.deref<IntegralAP<false>>();
if (!Val.singleWord()) {
uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
Val.take(NewMemory);
}
} else if (T == PT_Float) {
auto &Val = Ptr.deref<Floating>();
if (!Val.singleWord()) {
uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
Val.take(NewMemory);
}
}
}
template <typename T>
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr) {
assert(needsAlloc<T>());
auto &Val = Ptr.deref<T>();
if (!Val.singleWord()) {
uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
Val.take(NewMemory);
}
}
static void finishGlobalRecurse(InterpState &S, const Pointer &Ptr) {
if (const Record *R = Ptr.getRecord()) {
for (const Record::Field &Fi : R->fields()) {
if (Fi.Desc->isPrimitive()) {
TYPE_SWITCH_ALLOC(Fi.Desc->getPrimType(), {
copyPrimitiveMemory<T>(S, Ptr.atField(Fi.Offset));
});
copyPrimitiveMemory(S, Ptr.atField(Fi.Offset), Fi.Desc->getPrimType());
} else
finishGlobalRecurse(S, Ptr.atField(Fi.Offset));
}
return;
}
if (const Descriptor *D = Ptr.getFieldDesc(); D && D->isArray()) {
unsigned NumElems = D->getNumElems();
if (NumElems == 0)
return;
if (D->isPrimitiveArray()) {
PrimType PT = D->getPrimType();
if (!needsAlloc(PT))
return;
assert(NumElems >= 1);
const Pointer EP = Ptr.atIndex(0);
bool AllSingleWord = true;
TYPE_SWITCH_ALLOC(PT, {
if (!EP.deref<T>().singleWord()) {
copyPrimitiveMemory<T>(S, EP);
AllSingleWord = false;
}
});
if (AllSingleWord)
return;
for (unsigned I = 1; I != D->getNumElems(); ++I) {
const Pointer EP = Ptr.atIndex(I);
copyPrimitiveMemory(S, EP, PT);
}
} else {
assert(D->isCompositeArray());
for (unsigned I = 0; I != D->getNumElems(); ++I) {
const Pointer EP = Ptr.atIndex(I).narrow();
finishGlobalRecurse(S, EP);
}
}
}
}
bool FinishInitGlobal(InterpState &S, CodePtr OpPC) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
finishGlobalRecurse(S, Ptr);
if (Ptr.canBeInitialized()) {
Ptr.initialize();
Ptr.activate();
}
return true;
}
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", off)
#endif
bool Interpret(InterpState &S) {
// The current stack frame when we started Interpret().
// This is being used by the ops to determine wheter
// to return from this function and thus terminate
// interpretation.
const InterpFrame *StartFrame = S.Current;
assert(!S.Current->isRoot());
CodePtr PC = S.Current->getPC();
// Empty program.
if (!PC)
return true;
for (;;) {
auto Op = PC.read<Opcode>();
CodePtr OpPC = PC;
switch (Op) {
#define GET_INTERP
#include "Opcodes.inc"
#undef GET_INTERP
}
}
}
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", on)
#endif
} // namespace interp
} // namespace clang