//===--- Interp.h - 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 // //===----------------------------------------------------------------------===// // // Definition of the interpreter state and entry point. // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_AST_INTERP_INTERP_H #define LLVM_CLANG_AST_INTERP_INTERP_H #include "../ExprConstShared.h" #include "BitcastBuffer.h" #include "Boolean.h" #include "DynamicAllocator.h" #include "FixedPoint.h" #include "Floating.h" #include "Function.h" #include "InterpBuiltinBitCast.h" #include "InterpFrame.h" #include "InterpStack.h" #include "InterpState.h" #include "MemberPointer.h" #include "Opcode.h" #include "PrimType.h" #include "Program.h" #include "State.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" #include "llvm/ADT/APFloat.h" #include "llvm/ADT/APSInt.h" #include namespace clang { namespace interp { using APSInt = llvm::APSInt; using FixedPointSemantics = llvm::FixedPointSemantics; /// Checks if the variable has externally defined storage. bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if the array is offsetable. bool CheckArray(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a pointer is live and accessible. bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); /// Checks if a pointer is a dummy pointer. bool CheckDummy(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); /// Checks if a pointer is null. bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr, CheckSubobjectKind CSK); /// Checks if a pointer is in range. bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); /// Checks if a field from which a pointer is going to be derived is valid. bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, CheckSubobjectKind CSK); /// Checks if Ptr is a one-past-the-end pointer. bool CheckSubobject(InterpState &S, CodePtr OpPC, const Pointer &Ptr, CheckSubobjectKind CSK); /// Checks if the dowcast using the given offset is possible with the given /// pointer. bool CheckDowncast(InterpState &S, CodePtr OpPC, const Pointer &Ptr, uint32_t Offset); /// Checks if a pointer points to const storage. bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if the Descriptor is of a constexpr or const global variable. bool CheckConstant(InterpState &S, CodePtr OpPC, const Descriptor *Desc); /// Checks if a pointer points to a mutable field. bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a value can be loaded from a block. bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK = AK_Read); bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr); bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); /// Check if a global variable is initialized. bool CheckGlobalInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a value can be stored in a block. bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a value can be initialized. bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks the 'this' pointer. bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This); /// Checks if dynamic memory allocation is available in the current /// language mode. bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC); /// Diagnose mismatched new[]/delete or new/delete[] pairs. bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, DynamicAllocator::Form AllocForm, DynamicAllocator::Form DeleteForm, const Descriptor *D, const Expr *NewExpr); /// Check the source of the pointer passed to delete/delete[] has actually /// been heap allocated by us. bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source, const Pointer &Ptr); bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); /// Sets the given integral value to the pointer, which is of /// a std::{weak,partial,strong}_ordering type. bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC, const Pointer &Ptr, const APSInt &IntValue); /// Copy the contents of Src into Dest. bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest); bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize); bool Call(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize); bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize); bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE, uint32_t BuiltinID); bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, const CallExpr *CE); bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T); bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index); bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits, bool TargetIsUCharOrByte); bool CheckBCPResult(InterpState &S, const Pointer &Ptr); bool CheckDestructor(InterpState &S, CodePtr OpPC, const Pointer &Ptr); template static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) { const Expr *E = S.Current->getExpr(OpPC); S.CCEDiag(E, diag::note_constexpr_overflow) << SrcValue << E->getType(); return S.noteUndefinedBehavior(); } bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC, const FixedPoint &FP); bool isConstexprUnknown(const Pointer &P); inline bool CheckArraySize(InterpState &S, CodePtr OpPC, uint64_t NumElems); enum class ShiftDir { Left, Right }; /// Checks if the shift operation is legal. template bool CheckShift(InterpState &S, CodePtr OpPC, const LT &LHS, const RT &RHS, unsigned Bits) { if (RHS.isNegative()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); if (!S.noteUndefinedBehavior()) return false; } // C++11 [expr.shift]p1: Shift width must be less than the bit width of // the shifted type. if (Bits > 1 && RHS >= Bits) { const Expr *E = S.Current->getExpr(OpPC); const APSInt Val = RHS.toAPSInt(); QualType Ty = E->getType(); S.CCEDiag(E, diag::note_constexpr_large_shift) << Val << Ty << Bits; if (!S.noteUndefinedBehavior()) return false; } if constexpr (Dir == ShiftDir::Left) { if (LHS.isSigned() && !S.getLangOpts().CPlusPlus20) { // C++11 [expr.shift]p2: A signed left shift must have a non-negative // operand, and must not overflow the corresponding unsigned type. if (LHS.isNegative()) { const Expr *E = S.Current->getExpr(OpPC); S.CCEDiag(E, diag::note_constexpr_lshift_of_negative) << LHS.toAPSInt(); if (!S.noteUndefinedBehavior()) return false; } else if (LHS.toUnsigned().countLeadingZeros() < static_cast(RHS)) { const Expr *E = S.Current->getExpr(OpPC); S.CCEDiag(E, diag::note_constexpr_lshift_discards); if (!S.noteUndefinedBehavior()) return false; } } } // C++2a [expr.shift]p2: [P0907R4]: // E1 << E2 is the unique value congruent to // E1 x 2^E2 module 2^N. return true; } /// Checks if Div/Rem operation on LHS and RHS is valid. template bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) { if (RHS.isZero()) { const auto *Op = cast(S.Current->getExpr(OpPC)); if constexpr (std::is_same_v) { S.CCEDiag(Op, diag::note_expr_divide_by_zero) << Op->getRHS()->getSourceRange(); return true; } S.FFDiag(Op, diag::note_expr_divide_by_zero) << Op->getRHS()->getSourceRange(); return false; } if constexpr (!std::is_same_v) { if (LHS.isSigned() && LHS.isMin() && RHS.isNegative() && RHS.isMinusOne()) { APSInt LHSInt = LHS.toAPSInt(); SmallString<32> Trunc; (-LHSInt.extend(LHSInt.getBitWidth() + 1)).toString(Trunc, 10); const SourceInfo &Loc = S.Current->getSource(OpPC); const Expr *E = S.Current->getExpr(OpPC); S.CCEDiag(Loc, diag::note_constexpr_overflow) << Trunc << E->getType(); return false; } } return true; } template bool CheckArraySize(InterpState &S, CodePtr OpPC, SizeT *NumElements, unsigned ElemSize, bool IsNoThrow) { // FIXME: Both the SizeT::from() as well as the // NumElements.toAPSInt() in this function are rather expensive. // Can't be too many elements if the bitwidth of NumElements is lower than // that of Descriptor::MaxArrayElemBytes. if ((NumElements->bitWidth() - NumElements->isSigned()) < (sizeof(Descriptor::MaxArrayElemBytes) * 8)) return true; // FIXME: GH63562 // APValue stores array extents as unsigned, // so anything that is greater that unsigned would overflow when // constructing the array, we catch this here. SizeT MaxElements = SizeT::from(Descriptor::MaxArrayElemBytes / ElemSize); assert(MaxElements.isPositive()); if (NumElements->toAPSInt().getActiveBits() > ConstantArrayType::getMaxSizeBits(S.getASTContext()) || *NumElements > MaxElements) { if (!IsNoThrow) { const SourceInfo &Loc = S.Current->getSource(OpPC); if (NumElements->isSigned() && NumElements->isNegative()) { S.FFDiag(Loc, diag::note_constexpr_new_negative) << NumElements->toDiagnosticString(S.getASTContext()); } else { S.FFDiag(Loc, diag::note_constexpr_new_too_large) << NumElements->toDiagnosticString(S.getASTContext()); } } return false; } return true; } /// Checks if the result of a floating-point operation is valid /// in the current context. bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, APFloat::opStatus Status, FPOptions FPO); /// Checks why the given DeclRefExpr is invalid. bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR); /// Interpreter entry point. bool Interpret(InterpState &S); /// Interpret a builtin function. bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, uint32_t BuiltinID); /// Interpret an offsetof operation. bool InterpretOffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E, ArrayRef ArrayIndices, int64_t &Result); inline bool Invalid(InterpState &S, CodePtr OpPC); enum class ArithOp { Add, Sub }; //===----------------------------------------------------------------------===// // Returning values //===----------------------------------------------------------------------===// void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC, const Function *Func); template ::T> bool Ret(InterpState &S, CodePtr &PC) { const T &Ret = S.Stk.pop(); assert(S.Current); assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); if (!S.checkingPotentialConstantExpression() || S.Current->Caller) cleanupAfterFunctionCall(S, PC, S.Current->getFunction()); if (InterpFrame *Caller = S.Current->Caller) { PC = S.Current->getRetPC(); InterpFrame::free(S.Current); S.Current = Caller; S.Stk.push(Ret); } else { InterpFrame::free(S.Current); S.Current = nullptr; // The topmost frame should come from an EvalEmitter, // which has its own implementation of the Ret<> instruction. } return true; } inline bool RetVoid(InterpState &S, CodePtr &PC) { assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); if (!S.checkingPotentialConstantExpression() || S.Current->Caller) cleanupAfterFunctionCall(S, PC, S.Current->getFunction()); if (InterpFrame *Caller = S.Current->Caller) { PC = S.Current->getRetPC(); InterpFrame::free(S.Current); S.Current = Caller; } else { InterpFrame::free(S.Current); S.Current = nullptr; } return true; } //===----------------------------------------------------------------------===// // Add, Sub, Mul //===----------------------------------------------------------------------===// template class OpAP> bool AddSubMulHelper(InterpState &S, CodePtr OpPC, unsigned Bits, const T &LHS, const T &RHS) { // Fast path - add the numbers with fixed width. T Result; if constexpr (needsAlloc()) Result = S.allocAP(LHS.bitWidth()); if (!OpFW(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } // If for some reason evaluation continues, use the truncated results. S.Stk.push(Result); // Short-circuit fixed-points here since the error handling is easier. if constexpr (std::is_same_v) return handleFixedPointOverflow(S, OpPC, Result); // Slow path - compute the result using another bit of precision. APSInt Value = OpAP()(LHS.toAPSInt(Bits), RHS.toAPSInt(Bits)); // Report undefined behaviour, stopping if required. if (S.checkingForUndefinedBehavior()) { const Expr *E = S.Current->getExpr(OpPC); QualType Type = E->getType(); SmallString<32> Trunc; Value.trunc(Result.bitWidth()) .toString(Trunc, 10, Result.isSigned(), /*formatAsCLiteral=*/false, /*UpperCase=*/true, /*InsertSeparators=*/true); S.report(E->getExprLoc(), diag::warn_integer_constant_overflow) << Trunc << Type << E->getSourceRange(); } if (!handleOverflow(S, OpPC, Value)) { S.Stk.pop(); return false; } return true; } template ::T> bool Add(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() + 1; return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } static inline llvm::RoundingMode getRoundingMode(FPOptions FPO) { auto RM = FPO.getRoundingMode(); if (RM == llvm::RoundingMode::Dynamic) return llvm::RoundingMode::NearestTiesToEven; return RM; } inline bool Addf(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Floating &RHS = S.Stk.pop(); const Floating &LHS = S.Stk.pop(); FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); Floating Result = S.allocFloat(LHS.getSemantics()); auto Status = Floating::add(LHS, RHS, getRoundingMode(FPO), &Result); S.Stk.push(Result); return CheckFloatResult(S, OpPC, Result, Status, FPO); } template ::T> bool Sub(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() + 1; return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } inline bool Subf(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Floating &RHS = S.Stk.pop(); const Floating &LHS = S.Stk.pop(); FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); Floating Result = S.allocFloat(LHS.getSemantics()); auto Status = Floating::sub(LHS, RHS, getRoundingMode(FPO), &Result); S.Stk.push(Result); return CheckFloatResult(S, OpPC, Result, Status, FPO); } template ::T> bool Mul(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() * 2; return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } inline bool Mulf(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Floating &RHS = S.Stk.pop(); const Floating &LHS = S.Stk.pop(); FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); Floating Result = S.allocFloat(LHS.getSemantics()); auto Status = Floating::mul(LHS, RHS, getRoundingMode(FPO), &Result); S.Stk.push(Result); return CheckFloatResult(S, OpPC, Result, Status, FPO); } template ::T> inline bool Mulc(InterpState &S, CodePtr OpPC) { const Pointer &RHS = S.Stk.pop(); const Pointer &LHS = S.Stk.pop(); const Pointer &Result = S.Stk.peek(); if constexpr (std::is_same_v) { APFloat A = LHS.elem(0).getAPFloat(); APFloat B = LHS.elem(1).getAPFloat(); APFloat C = RHS.elem(0).getAPFloat(); APFloat D = RHS.elem(1).getAPFloat(); APFloat ResR(A.getSemantics()); APFloat ResI(A.getSemantics()); HandleComplexComplexMul(A, B, C, D, ResR, ResI); // Copy into the result. Floating RA = S.allocFloat(A.getSemantics()); RA.copy(ResR); Result.elem(0) = RA; // Floating(ResR); Result.atIndex(0).initialize(); Floating RI = S.allocFloat(A.getSemantics()); RI.copy(ResI); Result.elem(1) = RI; // Floating(ResI); Result.atIndex(1).initialize(); Result.initialize(); } else { // Integer element type. const T &LHSR = LHS.elem(0); const T &LHSI = LHS.elem(1); const T &RHSR = RHS.elem(0); const T &RHSI = RHS.elem(1); unsigned Bits = LHSR.bitWidth(); // real(Result) = (real(LHS) * real(RHS)) - (imag(LHS) * imag(RHS)) T A; if (T::mul(LHSR, RHSR, Bits, &A)) return false; T B; if (T::mul(LHSI, RHSI, Bits, &B)) return false; if (T::sub(A, B, Bits, &Result.elem(0))) return false; Result.atIndex(0).initialize(); // imag(Result) = (real(LHS) * imag(RHS)) + (imag(LHS) * real(RHS)) if (T::mul(LHSR, RHSI, Bits, &A)) return false; if (T::mul(LHSI, RHSR, Bits, &B)) return false; if (T::add(A, B, Bits, &Result.elem(1))) return false; Result.atIndex(1).initialize(); Result.initialize(); } return true; } template ::T> inline bool Divc(InterpState &S, CodePtr OpPC) { const Pointer &RHS = S.Stk.pop(); const Pointer &LHS = S.Stk.pop(); const Pointer &Result = S.Stk.peek(); if constexpr (std::is_same_v) { APFloat A = LHS.elem(0).getAPFloat(); APFloat B = LHS.elem(1).getAPFloat(); APFloat C = RHS.elem(0).getAPFloat(); APFloat D = RHS.elem(1).getAPFloat(); APFloat ResR(A.getSemantics()); APFloat ResI(A.getSemantics()); HandleComplexComplexDiv(A, B, C, D, ResR, ResI); // Copy into the result. Floating RA = S.allocFloat(A.getSemantics()); RA.copy(ResR); Result.elem(0) = RA; // Floating(ResR); Result.atIndex(0).initialize(); Floating RI = S.allocFloat(A.getSemantics()); RI.copy(ResI); Result.elem(1) = RI; // Floating(ResI); Result.atIndex(1).initialize(); Result.initialize(); } else { // Integer element type. const T &LHSR = LHS.elem(0); const T &LHSI = LHS.elem(1); const T &RHSR = RHS.elem(0); const T &RHSI = RHS.elem(1); unsigned Bits = LHSR.bitWidth(); const T Zero = T::from(0, Bits); if (Compare(RHSR, Zero) == ComparisonCategoryResult::Equal && Compare(RHSI, Zero) == ComparisonCategoryResult::Equal) { const SourceInfo &E = S.Current->getSource(OpPC); S.FFDiag(E, diag::note_expr_divide_by_zero); return false; } // Den = real(RHS)² + imag(RHS)² T A, B; if (T::mul(RHSR, RHSR, Bits, &A) || T::mul(RHSI, RHSI, Bits, &B)) { // Ignore overflow here, because that's what the current interpeter does. } T Den; if (T::add(A, B, Bits, &Den)) return false; if (Compare(Den, Zero) == ComparisonCategoryResult::Equal) { const SourceInfo &E = S.Current->getSource(OpPC); S.FFDiag(E, diag::note_expr_divide_by_zero); return false; } // real(Result) = ((real(LHS) * real(RHS)) + (imag(LHS) * imag(RHS))) / Den T &ResultR = Result.elem(0); T &ResultI = Result.elem(1); if (T::mul(LHSR, RHSR, Bits, &A) || T::mul(LHSI, RHSI, Bits, &B)) return false; if (T::add(A, B, Bits, &ResultR)) return false; if (T::div(ResultR, Den, Bits, &ResultR)) return false; Result.atIndex(0).initialize(); // imag(Result) = ((imag(LHS) * real(RHS)) - (real(LHS) * imag(RHS))) / Den if (T::mul(LHSI, RHSR, Bits, &A) || T::mul(LHSR, RHSI, Bits, &B)) return false; if (T::sub(A, B, Bits, &ResultI)) return false; if (T::div(ResultI, Den, Bits, &ResultI)) return false; Result.atIndex(1).initialize(); Result.initialize(); } return true; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS & RHS' on the stack template ::T> bool BitAnd(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); unsigned Bits = RHS.bitWidth(); T Result; if constexpr (needsAlloc()) Result = S.allocAP(Bits); if (!T::bitAnd(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS | RHS' on the stack template ::T> bool BitOr(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); unsigned Bits = RHS.bitWidth(); T Result; if constexpr (needsAlloc()) Result = S.allocAP(Bits); if (!T::bitOr(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS ^ RHS' on the stack template ::T> bool BitXor(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); unsigned Bits = RHS.bitWidth(); T Result; if constexpr (needsAlloc()) Result = S.allocAP(Bits); if (!T::bitXor(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS % RHS' on the stack (the remainder of dividing LHS by RHS). template ::T> bool Rem(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() * 2; if (!CheckDivRem(S, OpPC, LHS, RHS)) return false; T Result; if constexpr (needsAlloc()) Result = S.allocAP(LHS.bitWidth()); if (!T::rem(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS / RHS' on the stack template ::T> bool Div(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() * 2; if (!CheckDivRem(S, OpPC, LHS, RHS)) return false; T Result; if constexpr (needsAlloc()) Result = S.allocAP(LHS.bitWidth()); if (!T::div(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } if constexpr (std::is_same_v) { if (handleFixedPointOverflow(S, OpPC, Result)) { S.Stk.push(Result); return true; } } return false; } inline bool Divf(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Floating &RHS = S.Stk.pop(); const Floating &LHS = S.Stk.pop(); if (!CheckDivRem(S, OpPC, LHS, RHS)) return false; FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); Floating Result = S.allocFloat(LHS.getSemantics()); auto Status = Floating::div(LHS, RHS, getRoundingMode(FPO), &Result); S.Stk.push(Result); return CheckFloatResult(S, OpPC, Result, Status, FPO); } //===----------------------------------------------------------------------===// // Inv //===----------------------------------------------------------------------===// inline bool Inv(InterpState &S, CodePtr OpPC) { const auto &Val = S.Stk.pop(); S.Stk.push(!Val); return true; } //===----------------------------------------------------------------------===// // Neg //===----------------------------------------------------------------------===// template ::T> bool Neg(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); if constexpr (std::is_same_v) { T Result = S.allocFloat(Value.getSemantics()); if (!T::neg(Value, &Result)) { S.Stk.push(Result); return true; } return false; } else { T Result; if constexpr (needsAlloc()) Result = S.allocAP(Value.bitWidth()); if (!T::neg(Value, &Result)) { S.Stk.push(Result); return true; } assert(isIntegralType(Name) && "don't expect other types to fail at constexpr negation"); S.Stk.push(Result); APSInt NegatedValue = -Value.toAPSInt(Value.bitWidth() + 1); if (S.checkingForUndefinedBehavior()) { const Expr *E = S.Current->getExpr(OpPC); QualType Type = E->getType(); SmallString<32> Trunc; NegatedValue.trunc(Result.bitWidth()) .toString(Trunc, 10, Result.isSigned(), /*formatAsCLiteral=*/false, /*UpperCase=*/true, /*InsertSeparators=*/true); S.report(E->getExprLoc(), diag::warn_integer_constant_overflow) << Trunc << Type << E->getSourceRange(); return true; } return handleOverflow(S, OpPC, NegatedValue); } } enum class PushVal : bool { No, Yes, }; enum class IncDecOp { Inc, Dec, }; template bool IncDecHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr, bool CanOverflow) { assert(!Ptr.isDummy()); if (!S.inConstantContext()) { if (isConstexprUnknown(Ptr)) return false; } if constexpr (std::is_same_v) { if (!S.getLangOpts().CPlusPlus14) return Invalid(S, OpPC); } const T &Value = Ptr.deref(); T Result; if constexpr (needsAlloc()) Result = S.allocAP(Value.bitWidth()); if constexpr (DoPush == PushVal::Yes) S.Stk.push(Value); if constexpr (Op == IncDecOp::Inc) { if (!T::increment(Value, &Result) || !CanOverflow) { Ptr.deref() = Result; return true; } } else { if (!T::decrement(Value, &Result) || !CanOverflow) { Ptr.deref() = Result; return true; } } assert(CanOverflow); // Something went wrong with the previous operation. Compute the // result with another bit of precision. unsigned Bits = Value.bitWidth() + 1; APSInt APResult; if constexpr (Op == IncDecOp::Inc) APResult = ++Value.toAPSInt(Bits); else APResult = --Value.toAPSInt(Bits); // Report undefined behaviour, stopping if required. if (S.checkingForUndefinedBehavior()) { const Expr *E = S.Current->getExpr(OpPC); QualType Type = E->getType(); SmallString<32> Trunc; APResult.trunc(Result.bitWidth()) .toString(Trunc, 10, Result.isSigned(), /*formatAsCLiteral=*/false, /*UpperCase=*/true, /*InsertSeparators=*/true); S.report(E->getExprLoc(), diag::warn_integer_constant_overflow) << Trunc << Type << E->getSourceRange(); return true; } return handleOverflow(S, OpPC, APResult); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value increased by one back to the pointer /// 4) Pushes the original (pre-inc) value on the stack. template ::T> bool Inc(InterpState &S, CodePtr OpPC, bool CanOverflow) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Increment)) return false; return IncDecHelper(S, OpPC, Ptr, CanOverflow); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value increased by one back to the pointer template ::T> bool IncPop(InterpState &S, CodePtr OpPC, bool CanOverflow) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Increment)) return false; return IncDecHelper(S, OpPC, Ptr, CanOverflow); } template ::T> bool PreInc(InterpState &S, CodePtr OpPC, bool CanOverflow) { const Pointer &Ptr = S.Stk.peek(); if (!CheckLoad(S, OpPC, Ptr, AK_Increment)) return false; return IncDecHelper(S, OpPC, Ptr, CanOverflow); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value decreased by one back to the pointer /// 4) Pushes the original (pre-dec) value on the stack. template ::T> bool Dec(InterpState &S, CodePtr OpPC, bool CanOverflow) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Decrement)) return false; return IncDecHelper(S, OpPC, Ptr, CanOverflow); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value decreased by one back to the pointer template ::T> bool DecPop(InterpState &S, CodePtr OpPC, bool CanOverflow) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Decrement)) return false; return IncDecHelper(S, OpPC, Ptr, CanOverflow); } template ::T> bool PreDec(InterpState &S, CodePtr OpPC, bool CanOverflow) { const Pointer &Ptr = S.Stk.peek(); if (!CheckLoad(S, OpPC, Ptr, AK_Decrement)) return false; return IncDecHelper(S, OpPC, Ptr, CanOverflow); } template bool IncDecFloatHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr, uint32_t FPOI) { Floating Value = Ptr.deref(); Floating Result = S.allocFloat(Value.getSemantics()); if constexpr (DoPush == PushVal::Yes) S.Stk.push(Value); FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); llvm::APFloat::opStatus Status; if constexpr (Op == IncDecOp::Inc) Status = Floating::increment(Value, getRoundingMode(FPO), &Result); else Status = Floating::decrement(Value, getRoundingMode(FPO), &Result); Ptr.deref() = Result; return CheckFloatResult(S, OpPC, Result, Status, FPO); } inline bool Incf(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Increment)) return false; return IncDecFloatHelper(S, OpPC, Ptr, FPOI); } inline bool IncfPop(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Increment)) return false; return IncDecFloatHelper(S, OpPC, Ptr, FPOI); } inline bool Decf(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Decrement)) return false; return IncDecFloatHelper(S, OpPC, Ptr, FPOI); } inline bool DecfPop(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr, AK_Decrement)) return false; return IncDecFloatHelper(S, OpPC, Ptr, FPOI); } /// 1) Pops the value from the stack. /// 2) Pushes the bitwise complemented value on the stack (~V). template ::T> bool Comp(InterpState &S, CodePtr OpPC) { const T &Val = S.Stk.pop(); T Result; if constexpr (needsAlloc()) Result = S.allocAP(Val.bitWidth()); if (!T::comp(Val, &Result)) { S.Stk.push(Result); return true; } return false; } //===----------------------------------------------------------------------===// // EQ, NE, GT, GE, LT, LE //===----------------------------------------------------------------------===// using CompareFn = llvm::function_ref; template bool CmpHelper(InterpState &S, CodePtr OpPC, CompareFn Fn) { assert((!std::is_same_v) && "Non-equality comparisons on member pointer types should already be " "rejected in Sema."); using BoolT = PrimConv::T; const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); S.Stk.push(BoolT::from(Fn(LHS.compare(RHS)))); return true; } template bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { return CmpHelper(S, OpPC, Fn); } template <> inline bool CmpHelper(InterpState &S, CodePtr OpPC, CompareFn Fn) { using BoolT = PrimConv::T; const Pointer &RHS = S.Stk.pop(); const Pointer &LHS = S.Stk.pop(); // Function pointers cannot be compared in an ordered way. if (LHS.isFunctionPointer() || RHS.isFunctionPointer() || LHS.isTypeidPointer() || RHS.isTypeidPointer()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_unspecified) << LHS.toDiagnosticString(S.getASTContext()) << RHS.toDiagnosticString(S.getASTContext()); return false; } if (!Pointer::hasSameBase(LHS, RHS)) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_unspecified) << LHS.toDiagnosticString(S.getASTContext()) << RHS.toDiagnosticString(S.getASTContext()); return false; } // Diagnose comparisons between fields with different access specifiers. if (std::optional> Split = Pointer::computeSplitPoint(LHS, RHS)) { const FieldDecl *LF = Split->first.getField(); const FieldDecl *RF = Split->second.getField(); if (LF && RF && !LF->getParent()->isUnion() && LF->getAccess() != RF->getAccess()) { S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_pointer_comparison_differing_access) << LF << LF->getAccess() << RF << RF->getAccess() << LF->getParent(); } } unsigned VL = LHS.getByteOffset(); unsigned VR = RHS.getByteOffset(); S.Stk.push(BoolT::from(Fn(Compare(VL, VR)))); return true; } static inline bool IsOpaqueConstantCall(const CallExpr *E) { unsigned Builtin = E->getBuiltinCallee(); return (Builtin == Builtin::BI__builtin___CFStringMakeConstantString || Builtin == Builtin::BI__builtin___NSStringMakeConstantString || Builtin == Builtin::BI__builtin_ptrauth_sign_constant || Builtin == Builtin::BI__builtin_function_start); } bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS, const Pointer &RHS); template <> inline bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { using BoolT = PrimConv::T; const Pointer &RHS = S.Stk.pop(); const Pointer &LHS = S.Stk.pop(); if (LHS.isZero() && RHS.isZero()) { S.Stk.push(BoolT::from(Fn(ComparisonCategoryResult::Equal))); return true; } // Reject comparisons to weak pointers. for (const auto &P : {LHS, RHS}) { if (P.isZero()) continue; if (P.isWeak()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_weak_comparison) << P.toDiagnosticString(S.getASTContext()); return false; } } if (!S.inConstantContext()) { if (isConstexprUnknown(LHS) || isConstexprUnknown(RHS)) return false; } if (LHS.isFunctionPointer() && RHS.isFunctionPointer()) { S.Stk.push(BoolT::from(Fn(Compare(LHS.getIntegerRepresentation(), RHS.getIntegerRepresentation())))); return true; } // FIXME: The source check here isn't entirely correct. if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() && LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) { if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_literal_comparison) << LHS.toDiagnosticString(S.getASTContext()) << RHS.toDiagnosticString(S.getASTContext()); return false; } } if (Pointer::hasSameBase(LHS, RHS)) { size_t A = LHS.computeOffsetForComparison(); size_t B = RHS.computeOffsetForComparison(); S.Stk.push(BoolT::from(Fn(Compare(A, B)))); return true; } // Otherwise we need to do a bunch of extra checks before returning Unordered. if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && !RHS.isZero() && RHS.getOffset() == 0) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_past_end) << LHS.toDiagnosticString(S.getASTContext()); return false; } else if (RHS.isOnePastEnd() && !LHS.isOnePastEnd() && !LHS.isZero() && LHS.getOffset() == 0) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_past_end) << RHS.toDiagnosticString(S.getASTContext()); return false; } bool BothNonNull = !LHS.isZero() && !RHS.isZero(); // Reject comparisons to literals. for (const auto &P : {LHS, RHS}) { if (P.isZero()) continue; if (BothNonNull && P.pointsToLiteral()) { const Expr *E = P.getDeclDesc()->asExpr(); if (isa(E)) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_literal_comparison); return false; } else if (const auto *CE = dyn_cast(E); CE && IsOpaqueConstantCall(CE)) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_opaque_call_comparison) << P.toDiagnosticString(S.getASTContext()); return false; } } else if (BothNonNull && P.isIntegralPointer()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_constant_comparison) << LHS.toDiagnosticString(S.getASTContext()) << RHS.toDiagnosticString(S.getASTContext()); return false; } } if (LHS.isUnknownSizeArray() && RHS.isUnknownSizeArray()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_zero_sized) << LHS.toDiagnosticString(S.getASTContext()) << RHS.toDiagnosticString(S.getASTContext()); return false; } S.Stk.push(BoolT::from(Fn(ComparisonCategoryResult::Unordered))); return true; } template <> inline bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { const auto &RHS = S.Stk.pop(); const auto &LHS = S.Stk.pop(); // If either operand is a pointer to a weak function, the comparison is not // constant. for (const auto &MP : {LHS, RHS}) { if (MP.isWeak()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_mem_pointer_weak_comparison) << MP.getMemberFunction(); return false; } } // C++11 [expr.eq]p2: // If both operands are null, they compare equal. Otherwise if only one is // null, they compare unequal. if (LHS.isZero() && RHS.isZero()) { S.Stk.push(Fn(ComparisonCategoryResult::Equal)); return true; } if (LHS.isZero() || RHS.isZero()) { S.Stk.push(Fn(ComparisonCategoryResult::Unordered)); return true; } // We cannot compare against virtual declarations at compile time. for (const auto &MP : {LHS, RHS}) { if (const CXXMethodDecl *MD = MP.getMemberFunction(); MD && MD->isVirtual()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.CCEDiag(Loc, diag::note_constexpr_compare_virtual_mem_ptr) << MD; } } S.Stk.push(Boolean::from(Fn(LHS.compare(RHS)))); return true; } template ::T> bool EQ(InterpState &S, CodePtr OpPC) { return CmpHelperEQ(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Equal; }); } template ::T> bool CMP3(InterpState &S, CodePtr OpPC, const ComparisonCategoryInfo *CmpInfo) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const Pointer &P = S.Stk.peek(); ComparisonCategoryResult CmpResult = LHS.compare(RHS); if constexpr (std::is_same_v) { if (CmpResult == ComparisonCategoryResult::Unordered) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_unspecified) << LHS.toDiagnosticString(S.getASTContext()) << RHS.toDiagnosticString(S.getASTContext()); return false; } } assert(CmpInfo); const auto *CmpValueInfo = CmpInfo->getValueInfo(CmpInfo->makeWeakResult(CmpResult)); assert(CmpValueInfo); assert(CmpValueInfo->hasValidIntValue()); return SetThreeWayComparisonField(S, OpPC, P, CmpValueInfo->getIntValue()); } template ::T> bool NE(InterpState &S, CodePtr OpPC) { return CmpHelperEQ(S, OpPC, [](ComparisonCategoryResult R) { return R != ComparisonCategoryResult::Equal; }); } template ::T> bool LT(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Less; }); } template ::T> bool LE(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Less || R == ComparisonCategoryResult::Equal; }); } template ::T> bool GT(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Greater; }); } template ::T> bool GE(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Greater || R == ComparisonCategoryResult::Equal; }); } //===----------------------------------------------------------------------===// // Dup, Pop, Test //===----------------------------------------------------------------------===// template ::T> bool Dup(InterpState &S, CodePtr OpPC) { S.Stk.push(S.Stk.peek()); return true; } template ::T> bool Pop(InterpState &S, CodePtr OpPC) { S.Stk.discard(); return true; } /// [Value1, Value2] -> [Value2, Value1] template bool Flip(InterpState &S, CodePtr OpPC) { using TopT = typename PrimConv::T; using BottomT = typename PrimConv::T; const auto &Top = S.Stk.pop(); const auto &Bottom = S.Stk.pop(); S.Stk.push(Top); S.Stk.push(Bottom); return true; } //===----------------------------------------------------------------------===// // Const //===----------------------------------------------------------------------===// template ::T> bool Const(InterpState &S, CodePtr OpPC, const T &Arg) { if constexpr (needsAlloc()) { T Result = S.allocAP(Arg.bitWidth()); Result.copy(Arg.toAPSInt()); S.Stk.push(Result); return true; } S.Stk.push(Arg); return true; } inline bool ConstFloat(InterpState &S, CodePtr OpPC, const Floating &F) { Floating Result = S.allocFloat(F.getSemantics()); Result.copy(F.getAPFloat()); S.Stk.push(Result); return true; } //===----------------------------------------------------------------------===// // Get/Set Local/Param/Global/This //===----------------------------------------------------------------------===// template ::T> bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Ptr = S.Current->getLocalPointer(I); if (!CheckLoad(S, OpPC, Ptr)) return false; S.Stk.push(Ptr.deref()); return true; } bool EndLifetime(InterpState &S, CodePtr OpPC); bool EndLifetimePop(InterpState &S, CodePtr OpPC); bool StartLifetime(InterpState &S, CodePtr OpPC); /// 1) Pops the value from the stack. /// 2) Writes the value to the local variable with the /// given offset. template ::T> bool SetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->setLocal(I, S.Stk.pop()); return true; } template ::T> bool GetParam(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) { return false; } S.Stk.push(S.Current->getParam(I)); return true; } template ::T> bool SetParam(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->setParam(I, S.Stk.pop()); return true; } /// 1) Peeks a pointer on the stack /// 2) Pushes the value of the pointer's field on the stack template ::T> bool GetField(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Obj = S.Stk.peek(); if (!CheckNull(S, OpPC, Obj, CSK_Field)) return false; if (!CheckRange(S, OpPC, Obj, CSK_Field)) return false; const Pointer &Field = Obj.atField(I); if (!CheckLoad(S, OpPC, Field)) return false; S.Stk.push(Field.deref()); return true; } template ::T> bool SetField(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop(); const Pointer &Obj = S.Stk.peek(); if (!CheckNull(S, OpPC, Obj, CSK_Field)) return false; if (!CheckRange(S, OpPC, Obj, CSK_Field)) return false; const Pointer &Field = Obj.atField(I); if (!CheckStore(S, OpPC, Field)) return false; Field.initialize(); Field.deref() = Value; return true; } /// 1) Pops a pointer from the stack /// 2) Pushes the value of the pointer's field on the stack template ::T> bool GetFieldPop(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Obj = S.Stk.pop(); if (!CheckNull(S, OpPC, Obj, CSK_Field)) return false; if (!CheckRange(S, OpPC, Obj, CSK_Field)) return false; const Pointer &Field = Obj.atField(I); if (!CheckLoad(S, OpPC, Field)) return false; S.Stk.push(Field.deref()); return true; } template ::T> bool GetThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); if (!CheckLoad(S, OpPC, Field)) return false; S.Stk.push(Field.deref()); return true; } template ::T> bool SetThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) return false; const T &Value = S.Stk.pop(); const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); if (!CheckStore(S, OpPC, Field)) return false; Field.deref() = Value; return true; } template ::T> bool GetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Ptr = S.P.getPtrGlobal(I); if (!CheckConstant(S, OpPC, Ptr.getFieldDesc())) return false; if (Ptr.isExtern()) return false; // If a global variable is uninitialized, that means the initializer we've // compiled for it wasn't a constant expression. Diagnose that. if (!CheckGlobalInitialized(S, OpPC, Ptr)) return false; S.Stk.push(Ptr.deref()); return true; } /// Same as GetGlobal, but without the checks. template ::T> bool GetGlobalUnchecked(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Ptr = S.P.getPtrGlobal(I); if (!CheckInitialized(S, OpPC, Ptr, AK_Read)) return false; S.Stk.push(Ptr.deref()); return true; } template ::T> bool SetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { // TODO: emit warning. return false; } template ::T> bool InitGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &P = S.P.getGlobal(I); P.deref() = S.Stk.pop(); if constexpr (std::is_same_v) { auto &Val = P.deref(); if (!Val.singleWord()) { uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()]; Val.take(NewMemory); } } else if constexpr (needsAlloc()) { auto &Val = P.deref(); if (!Val.singleWord()) { uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()]; Val.take(NewMemory); } } P.initialize(); return true; } /// 1) Converts the value on top of the stack to an APValue /// 2) Sets that APValue on \Temp /// 3) Initializes global with index \I with that template ::T> bool InitGlobalTemp(InterpState &S, CodePtr OpPC, uint32_t I, const LifetimeExtendedTemporaryDecl *Temp) { const Pointer &Ptr = S.P.getGlobal(I); const T Value = S.Stk.peek(); APValue APV = Value.toAPValue(S.getASTContext()); APValue *Cached = Temp->getOrCreateValue(true); *Cached = APV; assert(Ptr.getDeclDesc()->asExpr()); S.SeenGlobalTemporaries.push_back( std::make_pair(Ptr.getDeclDesc()->asExpr(), Temp)); Ptr.deref() = S.Stk.pop(); Ptr.initialize(); return true; } /// 1) Converts the value on top of the stack to an APValue /// 2) Sets that APValue on \Temp /// 3) Initialized global with index \I with that inline bool InitGlobalTempComp(InterpState &S, CodePtr OpPC, const LifetimeExtendedTemporaryDecl *Temp) { assert(Temp); const Pointer &P = S.Stk.peek(); APValue *Cached = Temp->getOrCreateValue(true); S.SeenGlobalTemporaries.push_back( std::make_pair(P.getDeclDesc()->asExpr(), Temp)); if (std::optional APV = P.toRValue(S.getASTContext(), Temp->getTemporaryExpr()->getType())) { *Cached = *APV; return true; } return false; } template ::T> bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); assert(Field.canBeInitialized()); Field.deref() = S.Stk.pop(); Field.initialize(); return true; } template ::T> bool InitThisFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); assert(Field.canBeInitialized()); Field.deref() = S.Stk.pop(); Field.activate(); Field.initialize(); return true; } // FIXME: The Field pointer here is too much IMO and we could instead just // pass an Offset + BitWidth pair. template ::T> bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F, uint32_t FieldOffset) { assert(F->isBitField()); if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(FieldOffset); assert(Field.canBeInitialized()); const auto &Value = S.Stk.pop(); Field.deref() = Value.truncate(F->Decl->getBitWidthValue()); Field.initialize(); return true; } template ::T> bool InitThisBitFieldActivate(InterpState &S, CodePtr OpPC, const Record::Field *F, uint32_t FieldOffset) { assert(F->isBitField()); if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(FieldOffset); assert(Field.canBeInitialized()); const auto &Value = S.Stk.pop(); Field.deref() = Value.truncate(F->Decl->getBitWidthValue()); Field.initialize(); Field.activate(); return true; } /// 1) Pops the value from the stack /// 2) Peeks a pointer from the stack /// 3) Pushes the value to field I of the pointer on the stack template ::T> bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!CheckRange(S, OpPC, Ptr, CSK_Field)) return false; const Pointer &Field = Ptr.atField(I); Field.deref() = Value; Field.initialize(); return true; } template ::T> bool InitFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!CheckRange(S, OpPC, Ptr, CSK_Field)) return false; const Pointer &Field = Ptr.atField(I); Field.deref() = Value; Field.activate(); Field.initialize(); return true; } template ::T> bool InitBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { assert(F->isBitField()); const T &Value = S.Stk.pop(); const Pointer &Field = S.Stk.peek().atField(F->Offset); if constexpr (needsAlloc()) { T Result = S.allocAP(Value.bitWidth()); if (T::isSigned()) Result.copy(Value.toAPSInt() .trunc(F->Decl->getBitWidthValue()) .sextOrTrunc(Value.bitWidth())); else Result.copy(Value.toAPSInt() .trunc(F->Decl->getBitWidthValue()) .zextOrTrunc(Value.bitWidth())); Field.deref() = Result; } else { Field.deref() = Value.truncate(F->Decl->getBitWidthValue()); } Field.initialize(); return true; } template ::T> bool InitBitFieldActivate(InterpState &S, CodePtr OpPC, const Record::Field *F) { assert(F->isBitField()); const T &Value = S.Stk.pop(); const Pointer &Field = S.Stk.peek().atField(F->Offset); if constexpr (needsAlloc()) { T Result = S.allocAP(Value.bitWidth()); if (T::isSigned()) Result.copy(Value.toAPSInt() .trunc(F->Decl->getBitWidthValue()) .sextOrTrunc(Value.bitWidth())); else Result.copy(Value.toAPSInt() .trunc(F->Decl->getBitWidthValue()) .zextOrTrunc(Value.bitWidth())); Field.deref() = Result; } else { Field.deref() = Value.truncate(F->Decl->getBitWidthValue()); } Field.activate(); Field.initialize(); return true; } //===----------------------------------------------------------------------===// // GetPtr Local/Param/Global/Field/This //===----------------------------------------------------------------------===// inline bool GetPtrLocal(InterpState &S, CodePtr OpPC, uint32_t I) { S.Stk.push(S.Current->getLocalPointer(I)); return true; } inline bool GetPtrParam(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) { return false; } S.Stk.push(S.Current->getParamPointer(I)); return true; } inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { S.Stk.push(S.P.getPtrGlobal(I)); return true; } /// 1) Peeks a Pointer /// 2) Pushes Pointer.atField(Off) on the stack bool GetPtrField(InterpState &S, CodePtr OpPC, uint32_t Off); bool GetPtrFieldPop(InterpState &S, CodePtr OpPC, uint32_t Off); inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; S.Stk.push(This.atField(Off)); return true; } inline bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK, const Type *TargetType) { const Pointer &Ptr = S.Stk.pop(); if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Derived)) return false; if (!Ptr.isBlockPointer()) { // FIXME: We don't have the necessary information in integral pointers. // The Descriptor only has a record, but that does of course not include // the potential derived classes of said record. S.Stk.push(Ptr); return true; } if (!CheckSubobject(S, OpPC, Ptr, CSK_Derived)) return false; if (!CheckDowncast(S, OpPC, Ptr, Off)) return false; const Record *TargetRecord = Ptr.atFieldSub(Off).getRecord(); assert(TargetRecord); if (TargetRecord->getDecl() ->getTypeForDecl() ->getAsCXXRecordDecl() ->getCanonicalDecl() != TargetType->getAsCXXRecordDecl()->getCanonicalDecl()) { QualType MostDerivedType = Ptr.getDeclDesc()->getType(); S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_downcast) << MostDerivedType << QualType(TargetType, 0); return false; } S.Stk.push(Ptr.atFieldSub(Off)); return true; } inline bool GetPtrBase(InterpState &S, CodePtr OpPC, uint32_t Off) { const Pointer &Ptr = S.Stk.peek(); if (!CheckNull(S, OpPC, Ptr, CSK_Base)) return false; if (!Ptr.isBlockPointer()) { S.Stk.push(Ptr.asIntPointer().baseCast(S.getASTContext(), Off)); return true; } if (!CheckSubobject(S, OpPC, Ptr, CSK_Base)) return false; const Pointer &Result = Ptr.atField(Off); if (Result.isPastEnd() || !Result.isBaseClass()) return false; S.Stk.push(Result); return true; } inline bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK) { const Pointer &Ptr = S.Stk.pop(); if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Base)) return false; if (!Ptr.isBlockPointer()) { S.Stk.push(Ptr.asIntPointer().baseCast(S.getASTContext(), Off)); return true; } if (!CheckSubobject(S, OpPC, Ptr, CSK_Base)) return false; const Pointer &Result = Ptr.atField(Off); if (Result.isPastEnd() || !Result.isBaseClass()) return false; S.Stk.push(Result); return true; } inline bool GetMemberPtrBasePop(InterpState &S, CodePtr OpPC, int32_t Off) { const auto &Ptr = S.Stk.pop(); S.Stk.push(Ptr.atInstanceBase(Off)); return true; } inline bool GetPtrThisBase(InterpState &S, CodePtr OpPC, uint32_t Off) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; S.Stk.push(This.atField(Off)); return true; } inline bool FinishInitPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (Ptr.canBeInitialized()) Ptr.initialize(); return true; } inline bool FinishInit(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); if (Ptr.canBeInitialized()) Ptr.initialize(); return true; } inline bool FinishInitActivate(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } return true; } inline bool FinishInitActivatePop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } return true; } bool FinishInitGlobal(InterpState &S, CodePtr OpPC); inline bool Dump(InterpState &S, CodePtr OpPC) { S.Stk.dump(); return true; } inline bool CheckNull(InterpState &S, CodePtr OpPC) { const auto &Ptr = S.Stk.peek(); if (Ptr.isZero()) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_dereferencing_null); return S.noteUndefinedBehavior(); } return true; } inline bool VirtBaseHelper(InterpState &S, CodePtr OpPC, const RecordDecl *Decl, const Pointer &Ptr) { Pointer Base = Ptr; while (Base.isBaseClass()) Base = Base.getBase(); const Record::Base *VirtBase = Base.getRecord()->getVirtualBase(Decl); S.Stk.push(Base.atField(VirtBase->Offset)); return true; } inline bool GetPtrVirtBasePop(InterpState &S, CodePtr OpPC, const RecordDecl *D) { assert(D); const Pointer &Ptr = S.Stk.pop(); if (!CheckNull(S, OpPC, Ptr, CSK_Base)) return false; return VirtBaseHelper(S, OpPC, D, Ptr); } inline bool GetPtrThisVirtBase(InterpState &S, CodePtr OpPC, const RecordDecl *D) { assert(D); if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; return VirtBaseHelper(S, OpPC, D, S.Current->getThis()); } //===----------------------------------------------------------------------===// // Load, Store, Init //===----------------------------------------------------------------------===// template ::T> bool Load(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); if (!CheckLoad(S, OpPC, Ptr)) return false; if (!Ptr.isBlockPointer()) return false; S.Stk.push(Ptr.deref()); return true; } template ::T> bool LoadPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr)) return false; if (!Ptr.isBlockPointer()) return false; S.Stk.push(Ptr.deref()); return true; } template ::T> bool Store(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!CheckStore(S, OpPC, Ptr)) return false; if (Ptr.canBeInitialized()) Ptr.initialize(); Ptr.deref() = Value; return true; } template ::T> bool StorePop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!CheckStore(S, OpPC, Ptr)) return false; if (Ptr.canBeInitialized()) Ptr.initialize(); Ptr.deref() = Value; return true; } static inline bool Activate(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); if (Ptr.canBeInitialized()) Ptr.activate(); return true; } static inline bool ActivateThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &Ptr = S.Current->getThis(); assert(Ptr.atField(I).canBeInitialized()); Ptr.atField(I).activate(); return true; } template ::T> bool StoreActivate(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } if (!CheckStore(S, OpPC, Ptr)) return false; Ptr.deref() = Value; return true; } template ::T> bool StoreActivatePop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } if (!CheckStore(S, OpPC, Ptr)) return false; Ptr.deref() = Value; return true; } template ::T> bool StoreBitField(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!CheckStore(S, OpPC, Ptr)) return false; if (Ptr.canBeInitialized()) Ptr.initialize(); if (const auto *FD = Ptr.getField()) Ptr.deref() = Value.truncate(FD->getBitWidthValue()); else Ptr.deref() = Value; return true; } template ::T> bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!CheckStore(S, OpPC, Ptr)) return false; if (Ptr.canBeInitialized()) Ptr.initialize(); if (const auto *FD = Ptr.getField()) Ptr.deref() = Value.truncate(FD->getBitWidthValue()); else Ptr.deref() = Value; return true; } template ::T> bool StoreBitFieldActivate(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } if (!CheckStore(S, OpPC, Ptr)) return false; if (const auto *FD = Ptr.getField()) Ptr.deref() = Value.truncate(FD->getBitWidthValue()); else Ptr.deref() = Value; return true; } template ::T> bool StoreBitFieldActivatePop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } if (!CheckStore(S, OpPC, Ptr)) return false; if (const auto *FD = Ptr.getField()) Ptr.deref() = Value.truncate(FD->getBitWidthValue()); else Ptr.deref() = Value; return true; } template ::T> bool Init(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!CheckInit(S, OpPC, Ptr)) return false; Ptr.initialize(); new (&Ptr.deref()) T(Value); return true; } template ::T> bool InitPop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!CheckInit(S, OpPC, Ptr)) return false; Ptr.initialize(); new (&Ptr.deref()) T(Value); return true; } /// 1) Pops the value from the stack /// 2) Peeks a pointer and gets its index \Idx /// 3) Sets the value on the pointer, leaving the pointer on the stack. template ::T> bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (Ptr.isUnknownSizeArray()) return false; // In the unlikely event that we're initializing the first item of // a non-array, skip the atIndex(). if (Idx == 0 && !Ptr.getFieldDesc()->isArray()) { Ptr.initialize(); new (&Ptr.deref()) T(Value); return true; } const Pointer &ElemPtr = Ptr.atIndex(Idx); if (!CheckInit(S, OpPC, ElemPtr)) return false; ElemPtr.initialize(); new (&ElemPtr.deref()) T(Value); return true; } /// The same as InitElem, but pops the pointer as well. template ::T> bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (Ptr.isUnknownSizeArray()) return false; // In the unlikely event that we're initializing the first item of // a non-array, skip the atIndex(). if (Idx == 0 && !Ptr.getFieldDesc()->isArray()) { Ptr.initialize(); new (&Ptr.deref()) T(Value); return true; } const Pointer &ElemPtr = Ptr.atIndex(Idx); if (!CheckInit(S, OpPC, ElemPtr)) return false; ElemPtr.initialize(); new (&ElemPtr.deref()) T(Value); return true; } inline bool Memcpy(InterpState &S, CodePtr OpPC) { const Pointer &Src = S.Stk.pop(); Pointer &Dest = S.Stk.peek(); if (!CheckLoad(S, OpPC, Src)) return false; return DoMemcpy(S, OpPC, Src, Dest); } inline bool ToMemberPtr(InterpState &S, CodePtr OpPC) { const auto &Member = S.Stk.pop(); const auto &Base = S.Stk.pop(); S.Stk.push(Member.takeInstance(Base)); return true; } inline bool CastMemberPtrPtr(InterpState &S, CodePtr OpPC) { const auto &MP = S.Stk.pop(); if (std::optional Ptr = MP.toPointer(S.Ctx)) { S.Stk.push(*Ptr); return true; } return Invalid(S, OpPC); } //===----------------------------------------------------------------------===// // AddOffset, SubOffset //===----------------------------------------------------------------------===// template std::optional OffsetHelper(InterpState &S, CodePtr OpPC, const T &Offset, const Pointer &Ptr, bool IsPointerArith = false) { // A zero offset does not change the pointer. if (Offset.isZero()) return Ptr; if (IsPointerArith && !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 std::nullopt; } // Arrays of unknown bounds cannot have pointers into them. if (!CheckArray(S, OpPC, Ptr)) return std::nullopt; // This is much simpler for integral pointers, so handle them first. if (Ptr.isIntegralPointer()) { uint64_t V = Ptr.getIntegerRepresentation(); uint64_t O = static_cast(Offset) * Ptr.elemSize(); if constexpr (Op == ArithOp::Add) return Pointer(V + O, Ptr.asIntPointer().Desc); else return Pointer(V - O, Ptr.asIntPointer().Desc); } else if (Ptr.isFunctionPointer()) { uint64_t O = static_cast(Offset); uint64_t N; if constexpr (Op == ArithOp::Add) N = Ptr.getByteOffset() + O; else N = Ptr.getByteOffset() - O; if (N > 1) S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) << N << /*non-array*/ true << 0; return Pointer(Ptr.asFunctionPointer().getFunction(), N); } assert(Ptr.isBlockPointer()); uint64_t MaxIndex = static_cast(Ptr.getNumElems()); uint64_t Index; if (Ptr.isOnePastEnd()) Index = MaxIndex; else Index = Ptr.getIndex(); bool Invalid = false; // Helper to report an invalid offset, computed as APSInt. auto DiagInvalidOffset = [&]() -> void { const unsigned Bits = Offset.bitWidth(); APSInt APOffset(Offset.toAPSInt().extend(Bits + 2), /*IsUnsigend=*/false); APSInt APIndex(APInt(Bits + 2, Index, /*IsSigned=*/true), /*IsUnsigned=*/false); APSInt NewIndex = (Op == ArithOp::Add) ? (APIndex + APOffset) : (APIndex - APOffset); S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) << NewIndex << /*array*/ static_cast(!Ptr.inArray()) << MaxIndex; Invalid = true; }; if (Ptr.isBlockPointer()) { uint64_t IOffset = static_cast(Offset); uint64_t MaxOffset = MaxIndex - Index; if constexpr (Op == ArithOp::Add) { // If the new offset would be negative, bail out. if (Offset.isNegative() && (Offset.isMin() || -IOffset > Index)) DiagInvalidOffset(); // If the new offset would be out of bounds, bail out. if (Offset.isPositive() && IOffset > MaxOffset) DiagInvalidOffset(); } else { // If the new offset would be negative, bail out. if (Offset.isPositive() && Index < IOffset) DiagInvalidOffset(); // If the new offset would be out of bounds, bail out. if (Offset.isNegative() && (Offset.isMin() || -IOffset > MaxOffset)) DiagInvalidOffset(); } } if (Invalid && S.getLangOpts().CPlusPlus) return std::nullopt; // Offset is valid - compute it on unsigned. int64_t WideIndex = static_cast(Index); int64_t WideOffset = static_cast(Offset); int64_t Result; if constexpr (Op == ArithOp::Add) Result = WideIndex + WideOffset; else Result = WideIndex - WideOffset; // When the pointer is one-past-end, going back to index 0 is the only // useful thing we can do. Any other index has been diagnosed before and // we don't get here. if (Result == 0 && Ptr.isOnePastEnd()) { if (Ptr.getFieldDesc()->isArray()) return Ptr.atIndex(0); return Pointer(Ptr.asBlockPointer().Pointee, Ptr.asBlockPointer().Base); } return Ptr.atIndex(static_cast(Result)); } template ::T> bool AddOffset(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop(); Pointer Ptr = S.Stk.pop(); if (Ptr.isBlockPointer()) Ptr = Ptr.expand(); if (std::optional Result = OffsetHelper( S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) { S.Stk.push(*Result); return true; } return false; } template ::T> bool SubOffset(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (std::optional Result = OffsetHelper( S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) { S.Stk.push(*Result); return true; } return false; } template static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { if (Ptr.isDummy()) return false; using OneT = Integral<8, false>; const Pointer &P = Ptr.deref(); if (!CheckNull(S, OpPC, P, CSK_ArrayIndex)) return false; // Get the current value on the stack. S.Stk.push(P); // Now the current Ptr again and a constant 1. OneT One = OneT::from(1); if (std::optional Result = OffsetHelper(S, OpPC, One, P, /*IsPointerArith=*/true)) { // Store the new value. Ptr.deref() = *Result; return true; } return false; } static inline bool IncPtr(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (!CheckInitialized(S, OpPC, Ptr, AK_Increment)) return false; return IncDecPtrHelper(S, OpPC, Ptr); } static inline bool DecPtr(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (!CheckInitialized(S, OpPC, Ptr, AK_Decrement)) return false; return IncDecPtrHelper(S, OpPC, Ptr); } /// 1) Pops a Pointer from the stack. /// 2) Pops another Pointer from the stack. /// 3) Pushes the difference of the indices of the two pointers on the stack. template ::T> inline bool SubPtr(InterpState &S, CodePtr OpPC) { const Pointer &LHS = S.Stk.pop(); const Pointer &RHS = S.Stk.pop(); if (!Pointer::hasSameBase(LHS, RHS) && S.getLangOpts().CPlusPlus) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_pointer_arith_unspecified) << LHS.toDiagnosticString(S.getASTContext()) << RHS.toDiagnosticString(S.getASTContext()); return false; } if (LHS == RHS) { S.Stk.push(); return true; } for (const Pointer &P : {LHS, RHS}) { if (P.isZeroSizeArray()) { QualType PtrT = P.getType(); while (auto *AT = dyn_cast(PtrT)) PtrT = AT->getElementType(); QualType ArrayTy = S.getASTContext().getConstantArrayType( PtrT, APInt::getZero(1), nullptr, ArraySizeModifier::Normal, 0); S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_pointer_subtraction_zero_size) << ArrayTy; return false; } } int64_t A64 = LHS.isBlockPointer() ? (LHS.isElementPastEnd() ? LHS.getNumElems() : LHS.getIndex()) : LHS.getIntegerRepresentation(); int64_t B64 = RHS.isBlockPointer() ? (RHS.isElementPastEnd() ? RHS.getNumElems() : RHS.getIndex()) : RHS.getIntegerRepresentation(); int64_t R64 = A64 - B64; if (static_cast(T::from(R64)) != R64) return handleOverflow(S, OpPC, R64); S.Stk.push(T::from(R64)); return true; } //===----------------------------------------------------------------------===// // Destroy //===----------------------------------------------------------------------===// inline bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I) { assert(S.Current->getFunction()); // FIXME: We iterate the scope once here and then again in the destroy() call // below. for (auto &Local : S.Current->getFunction()->getScope(I).locals_reverse()) { const Pointer &Ptr = S.Current->getLocalPointer(Local.Offset); if (Ptr.getLifetime() == Lifetime::Ended) { auto *D = cast(Ptr.getFieldDesc()->asDecl()); S.FFDiag(D->getLocation(), diag::note_constexpr_destroy_out_of_lifetime) << D->getNameAsString(); return false; } } S.Current->destroy(I); return true; } inline bool InitScope(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->initScope(I); return true; } //===----------------------------------------------------------------------===// // Cast, CastFP //===----------------------------------------------------------------------===// template bool Cast(InterpState &S, CodePtr OpPC) { using T = typename PrimConv::T; using U = typename PrimConv::T; S.Stk.push(U::from(S.Stk.pop())); return true; } /// 1) Pops a Floating from the stack. /// 2) Pushes a new floating on the stack that uses the given semantics. inline bool CastFP(InterpState &S, CodePtr OpPC, const llvm::fltSemantics *Sem, llvm::RoundingMode RM) { Floating F = S.Stk.pop(); Floating Result = S.allocFloat(*Sem); F.toSemantics(Sem, RM, &Result); S.Stk.push(Result); return true; } inline bool CastFixedPoint(InterpState &S, CodePtr OpPC, uint32_t FPS) { FixedPointSemantics TargetSemantics = FixedPointSemantics::getFromOpaqueInt(FPS); const auto &Source = S.Stk.pop(); bool Overflow; FixedPoint Result = Source.toSemantics(TargetSemantics, &Overflow); if (Overflow && !handleFixedPointOverflow(S, OpPC, Result)) return false; S.Stk.push(Result); return true; } /// Like Cast(), but we cast to an arbitrary-bitwidth integral, so we need /// to know what bitwidth the result should be. template ::T> bool CastAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { auto Result = S.allocAP>(BitWidth); // Copy data. { APInt Source = S.Stk.pop().toAPSInt().extOrTrunc(BitWidth); Result.copy(Source); } S.Stk.push>(Result); return true; } template ::T> bool CastAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { auto Result = S.allocAP>(BitWidth); // Copy data. { APInt Source = S.Stk.pop().toAPSInt().extOrTrunc(BitWidth); Result.copy(Source); } S.Stk.push>(Result); return true; } template ::T> bool CastIntegralFloating(InterpState &S, CodePtr OpPC, const llvm::fltSemantics *Sem, uint32_t FPOI) { const T &From = S.Stk.pop(); APSInt FromAP = From.toAPSInt(); FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); Floating Result = S.allocFloat(*Sem); auto Status = Floating::fromIntegral(FromAP, *Sem, getRoundingMode(FPO), &Result); S.Stk.push(Result); return CheckFloatResult(S, OpPC, Result, Status, FPO); } template ::T> bool CastFloatingIntegral(InterpState &S, CodePtr OpPC, uint32_t FPOI) { const Floating &F = S.Stk.pop(); if constexpr (std::is_same_v) { S.Stk.push(T(F.isNonZero())); return true; } else { APSInt Result(std::max(8u, T::bitWidth()), /*IsUnsigned=*/!T::isSigned()); auto Status = F.convertToInteger(Result); // Float-to-Integral overflow check. if ((Status & APFloat::opStatus::opInvalidOp)) { const Expr *E = S.Current->getExpr(OpPC); QualType Type = E->getType(); S.CCEDiag(E, diag::note_constexpr_overflow) << F.getAPFloat() << Type; if (S.noteUndefinedBehavior()) { S.Stk.push(T(Result)); return true; } return false; } FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); S.Stk.push(T(Result)); return CheckFloatResult(S, OpPC, F, Status, FPO); } } static inline bool CastFloatingIntegralAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth, uint32_t FPOI) { const Floating &F = S.Stk.pop(); APSInt Result(BitWidth, /*IsUnsigned=*/true); auto Status = F.convertToInteger(Result); // Float-to-Integral overflow check. if ((Status & APFloat::opStatus::opInvalidOp) && F.isFinite()) return handleOverflow(S, OpPC, F.getAPFloat()); FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); auto ResultAP = S.allocAP>(BitWidth); ResultAP.copy(Result); S.Stk.push>(ResultAP); return CheckFloatResult(S, OpPC, F, Status, FPO); } static inline bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth, uint32_t FPOI) { const Floating &F = S.Stk.pop(); APSInt Result(BitWidth, /*IsUnsigned=*/false); auto Status = F.convertToInteger(Result); // Float-to-Integral overflow check. if ((Status & APFloat::opStatus::opInvalidOp) && F.isFinite()) return handleOverflow(S, OpPC, F.getAPFloat()); FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI); auto ResultAP = S.allocAP>(BitWidth); ResultAP.copy(Result); S.Stk.push>(ResultAP); return CheckFloatResult(S, OpPC, F, Status, FPO); } bool CheckPointerToIntegralCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr, unsigned BitWidth); bool CastPointerIntegralAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth); bool CastPointerIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth); template ::T> bool CastPointerIntegral(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_cast) << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret << S.getLangOpts().CPlusPlus << S.Current->getRange(OpPC); if (!CheckPointerToIntegralCast(S, OpPC, Ptr, T::bitWidth())) return Invalid(S, OpPC); S.Stk.push(T::from(Ptr.getIntegerRepresentation())); return true; } template ::T> static inline bool CastIntegralFixedPoint(InterpState &S, CodePtr OpPC, uint32_t FPS) { const T &Int = S.Stk.pop(); FixedPointSemantics Sem = FixedPointSemantics::getFromOpaqueInt(FPS); bool Overflow; FixedPoint Result = FixedPoint::from(Int.toAPSInt(), Sem, &Overflow); if (Overflow && !handleFixedPointOverflow(S, OpPC, Result)) return false; S.Stk.push(Result); return true; } static inline bool CastFloatingFixedPoint(InterpState &S, CodePtr OpPC, uint32_t FPS) { const auto &Float = S.Stk.pop(); FixedPointSemantics Sem = FixedPointSemantics::getFromOpaqueInt(FPS); bool Overflow; FixedPoint Result = FixedPoint::from(Float.getAPFloat(), Sem, &Overflow); if (Overflow && !handleFixedPointOverflow(S, OpPC, Result)) return false; S.Stk.push(Result); return true; } static inline bool CastFixedPointFloating(InterpState &S, CodePtr OpPC, const llvm::fltSemantics *Sem) { const auto &Fixed = S.Stk.pop(); Floating Result = S.allocFloat(*Sem); Result.copy(Fixed.toFloat(Sem)); S.Stk.push(Result); return true; } template ::T> static inline bool CastFixedPointIntegral(InterpState &S, CodePtr OpPC) { const auto &Fixed = S.Stk.pop(); bool Overflow; APSInt Int = Fixed.toInt(T::bitWidth(), T::isSigned(), &Overflow); if (Overflow && !handleOverflow(S, OpPC, Int)) return false; S.Stk.push(Int); return true; } static inline bool PtrPtrCast(InterpState &S, CodePtr OpPC, bool SrcIsVoidPtr) { const auto &Ptr = S.Stk.peek(); if (SrcIsVoidPtr && S.getLangOpts().CPlusPlus) { bool HasValidResult = !Ptr.isZero(); if (HasValidResult) { if (S.getStdAllocatorCaller("allocate")) return true; const auto &E = cast(S.Current->getExpr(OpPC)); if (S.getLangOpts().CPlusPlus26 && S.getASTContext().hasSimilarType(Ptr.getType(), E->getType()->getPointeeType())) return true; S.CCEDiag(E, diag::note_constexpr_invalid_void_star_cast) << E->getSubExpr()->getType() << S.getLangOpts().CPlusPlus26 << Ptr.getType().getCanonicalType() << E->getType()->getPointeeType(); } else if (!S.getLangOpts().CPlusPlus26) { const SourceInfo &E = S.Current->getSource(OpPC); S.CCEDiag(E, diag::note_constexpr_invalid_cast) << diag::ConstexprInvalidCastKind::CastFrom << "'void *'" << S.Current->getRange(OpPC); } } else { const SourceInfo &E = S.Current->getSource(OpPC); S.CCEDiag(E, diag::note_constexpr_invalid_cast) << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret << S.getLangOpts().CPlusPlus << S.Current->getRange(OpPC); } return true; } //===----------------------------------------------------------------------===// // Zero, Nullptr //===----------------------------------------------------------------------===// template ::T> bool Zero(InterpState &S, CodePtr OpPC) { S.Stk.push(T::zero()); return true; } static inline bool ZeroIntAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { auto Result = S.allocAP>(BitWidth); if (!Result.singleWord()) std::memset(Result.Memory, 0, Result.numWords() * sizeof(uint64_t)); S.Stk.push>(Result); return true; } static inline bool ZeroIntAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { auto Result = S.allocAP>(BitWidth); if (!Result.singleWord()) std::memset(Result.Memory, 0, Result.numWords() * sizeof(uint64_t)); S.Stk.push>(Result); return true; } template ::T> inline bool Null(InterpState &S, CodePtr OpPC, uint64_t Value, const Descriptor *Desc) { // FIXME(perf): This is a somewhat often-used function and the value of a // null pointer is almost always 0. S.Stk.push(Value, Desc); return true; } template ::T> inline bool IsNonNull(InterpState &S, CodePtr OpPC) { const auto &P = S.Stk.pop(); if (P.isWeak()) return false; S.Stk.push(Boolean::from(!P.isZero())); return true; } //===----------------------------------------------------------------------===// // This, ImplicitThis //===----------------------------------------------------------------------===// inline bool This(InterpState &S, CodePtr OpPC) { // Cannot read 'this' in this mode. if (S.checkingPotentialConstantExpression()) { return false; } const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; // Ensure the This pointer has been cast to the correct base. if (!This.isDummy()) { assert(isa(S.Current->getFunction()->getDecl())); if (!This.isTypeidPointer()) { [[maybe_unused]] const Record *R = This.getRecord(); if (!R) R = This.narrow().getRecord(); assert(R); assert(R->getDecl() == cast(S.Current->getFunction()->getDecl()) ->getParent()); } } S.Stk.push(This); return true; } inline bool RVOPtr(InterpState &S, CodePtr OpPC) { assert(S.Current->getFunction()->hasRVO()); if (S.checkingPotentialConstantExpression()) return false; S.Stk.push(S.Current->getRVOPtr()); return true; } //===----------------------------------------------------------------------===// // Shr, Shl //===----------------------------------------------------------------------===// template inline bool DoShift(InterpState &S, CodePtr OpPC, LT &LHS, RT &RHS, LT *Result) { static_assert(!needsAlloc()); const unsigned Bits = LHS.bitWidth(); // OpenCL 6.3j: shift values are effectively % word size of LHS. if (S.getLangOpts().OpenCL) RT::bitAnd(RHS, RT::from(LHS.bitWidth() - 1, RHS.bitWidth()), RHS.bitWidth(), &RHS); if (RHS.isNegative()) { // During constant-folding, a negative shift is an opposite shift. Such a // shift is not a constant expression. const SourceInfo &Loc = S.Current->getSource(OpPC); S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); if (!S.noteUndefinedBehavior()) return false; RHS = -RHS; return DoShift( S, OpPC, LHS, RHS, Result); } if (!CheckShift(S, OpPC, LHS, RHS, Bits)) return false; // Limit the shift amount to Bits - 1. If this happened, // it has already been diagnosed by CheckShift() above, // but we still need to handle it. // Note that we have to be extra careful here since we're doing the shift in // any case, but we need to adjust the shift amount or the way we do the shift // for the potential error cases. typename LT::AsUnsigned R; unsigned MaxShiftAmount = LHS.bitWidth() - 1; if constexpr (Dir == ShiftDir::Left) { if (Compare(RHS, RT::from(MaxShiftAmount, RHS.bitWidth())) == ComparisonCategoryResult::Greater) { if (LHS.isNegative()) R = LT::AsUnsigned::zero(LHS.bitWidth()); else { RHS = RT::from(LHS.countLeadingZeros(), RHS.bitWidth()); LT::AsUnsigned::shiftLeft(LT::AsUnsigned::from(LHS), LT::AsUnsigned::from(RHS, Bits), Bits, &R); } } else if (LHS.isNegative()) { if (LHS.isMin()) { R = LT::AsUnsigned::zero(LHS.bitWidth()); } else { // If the LHS is negative, perform the cast and invert the result. typename LT::AsUnsigned LHSU = LT::AsUnsigned::from(-LHS); LT::AsUnsigned::shiftLeft(LHSU, LT::AsUnsigned::from(RHS, Bits), Bits, &R); R = -R; } } else { // The good case, a simple left shift. LT::AsUnsigned::shiftLeft(LT::AsUnsigned::from(LHS), LT::AsUnsigned::from(RHS, Bits), Bits, &R); } S.Stk.push(LT::from(R)); return true; } // Right shift. if (Compare(RHS, RT::from(MaxShiftAmount, RHS.bitWidth())) == ComparisonCategoryResult::Greater) { R = LT::AsUnsigned::from(-1); } else { // Do the shift on potentially signed LT, then convert to unsigned type. LT A; LT::shiftRight(LHS, LT::from(RHS, Bits), Bits, &A); R = LT::AsUnsigned::from(A); } S.Stk.push(LT::from(R)); return true; } /// A version of DoShift that works on IntegralAP. template inline bool DoShiftAP(InterpState &S, CodePtr OpPC, const APSInt &LHS, APSInt RHS, LT *Result) { const unsigned Bits = LHS.getBitWidth(); // OpenCL 6.3j: shift values are effectively % word size of LHS. if (S.getLangOpts().OpenCL) RHS &= APSInt(llvm::APInt(RHS.getBitWidth(), static_cast(Bits - 1)), RHS.isUnsigned()); if (RHS.isNegative()) { // During constant-folding, a negative shift is an opposite shift. Such a // shift is not a constant expression. const SourceInfo &Loc = S.Current->getSource(OpPC); S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS; //.toAPSInt(); if (!S.noteUndefinedBehavior()) return false; return DoShiftAP( S, OpPC, LHS, -RHS, Result); } if (!CheckShift(S, OpPC, static_cast(LHS), static_cast(RHS), Bits)) return false; unsigned SA = (unsigned)RHS.getLimitedValue(Bits - 1); if constexpr (Dir == ShiftDir::Left) { if constexpr (needsAlloc()) Result->copy(LHS << SA); else *Result = LT(LHS << SA); } else { if constexpr (needsAlloc()) Result->copy(LHS >> SA); else *Result = LT(LHS >> SA); } S.Stk.push(*Result); return true; } template inline bool Shr(InterpState &S, CodePtr OpPC) { using LT = typename PrimConv::T; using RT = typename PrimConv::T; auto RHS = S.Stk.pop(); auto LHS = S.Stk.pop(); if constexpr (needsAlloc() || needsAlloc()) { LT Result; if constexpr (needsAlloc()) Result = S.allocAP(LHS.bitWidth()); return DoShiftAP(S, OpPC, LHS.toAPSInt(), RHS.toAPSInt(), &Result); } else { LT Result; return DoShift(S, OpPC, LHS, RHS, &Result); } } template inline bool Shl(InterpState &S, CodePtr OpPC) { using LT = typename PrimConv::T; using RT = typename PrimConv::T; auto RHS = S.Stk.pop(); auto LHS = S.Stk.pop(); if constexpr (needsAlloc() || needsAlloc()) { LT Result; if constexpr (needsAlloc()) Result = S.allocAP(LHS.bitWidth()); return DoShiftAP(S, OpPC, LHS.toAPSInt(), RHS.toAPSInt(), &Result); } else { LT Result; return DoShift(S, OpPC, LHS, RHS, &Result); } } static inline bool ShiftFixedPoint(InterpState &S, CodePtr OpPC, bool Left) { const auto &RHS = S.Stk.pop(); const auto &LHS = S.Stk.pop(); llvm::FixedPointSemantics LHSSema = LHS.getSemantics(); unsigned ShiftBitWidth = LHSSema.getWidth() - (unsigned)LHSSema.hasUnsignedPadding() - 1; // Embedded-C 4.1.6.2.2: // The right operand must be nonnegative and less than the total number // of (nonpadding) bits of the fixed-point operand ... if (RHS.isNegative()) { S.CCEDiag(S.Current->getLocation(OpPC), diag::note_constexpr_negative_shift) << RHS.toAPSInt(); } else if (static_cast(RHS.toAPSInt().getLimitedValue( ShiftBitWidth)) != RHS.toAPSInt()) { const Expr *E = S.Current->getExpr(OpPC); S.CCEDiag(E, diag::note_constexpr_large_shift) << RHS.toAPSInt() << E->getType() << ShiftBitWidth; } FixedPoint Result; if (Left) { if (FixedPoint::shiftLeft(LHS, RHS, ShiftBitWidth, &Result) && !handleFixedPointOverflow(S, OpPC, Result)) return false; } else { if (FixedPoint::shiftRight(LHS, RHS, ShiftBitWidth, &Result) && !handleFixedPointOverflow(S, OpPC, Result)) return false; } S.Stk.push(Result); return true; } //===----------------------------------------------------------------------===// // NoRet //===----------------------------------------------------------------------===// inline bool NoRet(InterpState &S, CodePtr OpPC) { SourceLocation EndLoc = S.Current->getCallee()->getEndLoc(); S.FFDiag(EndLoc, diag::note_constexpr_no_return); return false; } //===----------------------------------------------------------------------===// // NarrowPtr, ExpandPtr //===----------------------------------------------------------------------===// inline bool NarrowPtr(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); S.Stk.push(Ptr.narrow()); return true; } inline bool ExpandPtr(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (Ptr.isBlockPointer()) S.Stk.push(Ptr.expand()); else S.Stk.push(Ptr); return true; } // 1) Pops an integral value from the stack // 2) Peeks a pointer // 3) Pushes a new pointer that's a narrowed array // element of the peeked pointer with the value // from 1) added as offset. // // This leaves the original pointer on the stack and pushes a new one // with the offset applied and narrowed. template ::T> inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!Ptr.isZero() && !Offset.isZero()) { if (!CheckArray(S, OpPC, Ptr)) return false; } if (Offset.isZero()) { if (Ptr.getFieldDesc()->isArray() && Ptr.getIndex() == 0) { S.Stk.push(Ptr.atIndex(0).narrow()); return true; } S.Stk.push(Ptr); return true; } assert(!Offset.isZero()); if (std::optional Result = OffsetHelper(S, OpPC, Offset, Ptr)) { S.Stk.push(Result->narrow()); return true; } return false; } template ::T> inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) { const T &Offset = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!Ptr.isZero() && !Offset.isZero()) { if (!CheckArray(S, OpPC, Ptr)) return false; } if (Offset.isZero()) { if (Ptr.getFieldDesc()->isArray() && Ptr.getIndex() == 0) { S.Stk.push(Ptr.atIndex(0).narrow()); return true; } S.Stk.push(Ptr); return true; } assert(!Offset.isZero()); if (std::optional Result = OffsetHelper(S, OpPC, Offset, Ptr)) { S.Stk.push(Result->narrow()); return true; } return false; } template ::T> inline bool ArrayElem(InterpState &S, CodePtr OpPC, uint32_t Index) { const Pointer &Ptr = S.Stk.peek(); if (!CheckLoad(S, OpPC, Ptr)) return false; assert(Ptr.atIndex(Index).getFieldDesc()->getPrimType() == Name); S.Stk.push(Ptr.elem(Index)); return true; } template ::T> inline bool ArrayElemPop(InterpState &S, CodePtr OpPC, uint32_t Index) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr)) return false; assert(Ptr.atIndex(Index).getFieldDesc()->getPrimType() == Name); S.Stk.push(Ptr.elem(Index)); return true; } template ::T> inline bool CopyArray(InterpState &S, CodePtr OpPC, uint32_t SrcIndex, uint32_t DestIndex, uint32_t Size) { const auto &SrcPtr = S.Stk.pop(); const auto &DestPtr = S.Stk.peek(); for (uint32_t I = 0; I != Size; ++I) { const Pointer &SP = SrcPtr.atIndex(SrcIndex + I); if (!CheckLoad(S, OpPC, SP)) return false; const Pointer &DP = DestPtr.atIndex(DestIndex + I); DP.deref() = SP.deref(); DP.initialize(); } return true; } /// Just takes a pointer and checks if it's an incomplete /// array type. inline bool ArrayDecay(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (Ptr.isZero()) { S.Stk.push(Ptr); return true; } if (!CheckRange(S, OpPC, Ptr, CSK_ArrayToPointer)) return false; if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) { S.Stk.push(Ptr.atIndex(0)); return true; } const SourceInfo &E = S.Current->getSource(OpPC); S.FFDiag(E, diag::note_constexpr_unsupported_unsized_array); return false; } inline bool GetFnPtr(InterpState &S, CodePtr OpPC, const Function *Func) { assert(Func); S.Stk.push(Func); return true; } template ::T> inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Descriptor *Desc) { const T &IntVal = S.Stk.pop(); S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_cast) << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret << S.getLangOpts().CPlusPlus; S.Stk.push(static_cast(IntVal), Desc); return true; } inline bool GetMemberPtr(InterpState &S, CodePtr OpPC, const ValueDecl *D) { S.Stk.push(D); return true; } inline bool GetMemberPtrBase(InterpState &S, CodePtr OpPC) { const auto &MP = S.Stk.pop(); S.Stk.push(MP.getBase()); return true; } inline bool GetMemberPtrDecl(InterpState &S, CodePtr OpPC) { const auto &MP = S.Stk.pop(); const auto *FD = cast(MP.getDecl()); const auto *Func = S.getContext().getOrCreateFunction(FD); S.Stk.push(Func); 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) { const SourceLocation &Loc = S.Current->getLocation(OpPC); S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr) << S.Current->getRange(OpPC); return false; } inline bool Unsupported(InterpState &S, CodePtr OpPC) { const SourceLocation &Loc = S.Current->getLocation(OpPC); S.FFDiag(Loc, diag::note_constexpr_stmt_expr_unsupported) << S.Current->getRange(OpPC); return false; } inline bool StartSpeculation(InterpState &S, CodePtr OpPC) { ++S.SpeculationDepth; if (S.SpeculationDepth != 1) return true; assert(S.PrevDiags == nullptr); S.PrevDiags = S.getEvalStatus().Diag; S.getEvalStatus().Diag = nullptr; return true; } inline bool EndSpeculation(InterpState &S, CodePtr OpPC) { assert(S.SpeculationDepth != 0); --S.SpeculationDepth; if (S.SpeculationDepth == 0) { S.getEvalStatus().Diag = S.PrevDiags; S.PrevDiags = nullptr; } return true; } inline bool PushCC(InterpState &S, CodePtr OpPC, bool Value) { S.ConstantContextOverride = Value; return true; } inline bool PopCC(InterpState &S, CodePtr OpPC) { S.ConstantContextOverride = std::nullopt; return true; } /// Do nothing and just abort execution. inline bool Error(InterpState &S, CodePtr OpPC) { return false; } inline bool SideEffect(InterpState &S, CodePtr OpPC) { return S.noteSideEffect(); } /// Same here, but only for casts. inline bool InvalidCast(InterpState &S, CodePtr OpPC, CastKind Kind, bool Fatal) { const SourceLocation &Loc = S.Current->getLocation(OpPC); if (Kind == CastKind::Reinterpret) { S.CCEDiag(Loc, diag::note_constexpr_invalid_cast) << static_cast(Kind) << S.Current->getRange(OpPC); return !Fatal; } else if (Kind == CastKind::Volatile) { if (!S.checkingPotentialConstantExpression()) { const auto *E = cast(S.Current->getExpr(OpPC)); if (S.getLangOpts().CPlusPlus) S.FFDiag(E, diag::note_constexpr_access_volatile_type) << AK_Read << E->getSubExpr()->getType(); else S.FFDiag(E); } return false; } else if (Kind == CastKind::Dynamic) { assert(!S.getLangOpts().CPlusPlus20); S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_cast) << diag::ConstexprInvalidCastKind::Dynamic; return true; } return false; } inline bool InvalidDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR, bool InitializerFailed) { assert(DR); if (InitializerFailed) { const SourceInfo &Loc = S.Current->getSource(OpPC); const auto *VD = cast(DR->getDecl()); S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD; S.Note(VD->getLocation(), diag::note_declared_at); return false; } return CheckDeclRef(S, OpPC, DR); } inline bool SizelessVectorElementSize(InterpState &S, CodePtr OpPC) { if (S.inConstantContext()) { const SourceRange &ArgRange = S.Current->getRange(OpPC); const Expr *E = S.Current->getExpr(OpPC); S.CCEDiag(E, diag::note_constexpr_non_const_vectorelements) << ArgRange; } return false; } inline bool CheckPseudoDtor(InterpState &S, CodePtr OpPC) { if (!S.getLangOpts().CPlusPlus20) S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_pseudo_destructor); return true; } inline bool Assume(InterpState &S, CodePtr OpPC) { const auto Val = S.Stk.pop(); if (Val) return true; // Else, diagnose. const SourceLocation &Loc = S.Current->getLocation(OpPC); S.CCEDiag(Loc, diag::note_constexpr_assumption_failed); return false; } template ::T> inline bool OffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E) { llvm::SmallVector ArrayIndices; for (size_t I = 0; I != E->getNumExpressions(); ++I) ArrayIndices.emplace_back(S.Stk.pop()); int64_t Result; if (!InterpretOffsetOf(S, OpPC, E, ArrayIndices, Result)) return false; S.Stk.push(T::from(Result)); return true; } template ::T> inline bool CheckNonNullArg(InterpState &S, CodePtr OpPC) { const T &Arg = S.Stk.peek(); if (!Arg.isZero()) return true; const SourceLocation &Loc = S.Current->getLocation(OpPC); S.CCEDiag(Loc, diag::note_non_null_attribute_failed); return false; } void diagnoseEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED, const APSInt &Value); template ::T> inline bool CheckEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED) { assert(ED); assert(!ED->isFixed()); if (S.inConstantContext()) { const APSInt Val = S.Stk.peek().toAPSInt(); diagnoseEnumValue(S, OpPC, ED, Val); } return true; } /// OldPtr -> Integer -> NewPtr. template inline bool DecayPtr(InterpState &S, CodePtr OpPC) { static_assert(isPtrType(TIn) && isPtrType(TOut)); using FromT = typename PrimConv::T; using ToT = typename PrimConv::T; const FromT &OldPtr = S.Stk.pop(); if constexpr (std::is_same_v && std::is_same_v) { S.Stk.push(OldPtr.getFunction(), OldPtr.getOffset()); return true; } else if constexpr (std::is_same_v && std::is_same_v) { if (OldPtr.isFunctionPointer()) { S.Stk.push(OldPtr.asFunctionPointer().getFunction(), OldPtr.getByteOffset()); return true; } } S.Stk.push(ToT(OldPtr.getIntegerRepresentation(), nullptr)); return true; } inline bool CheckDecl(InterpState &S, CodePtr OpPC, const VarDecl *VD) { // An expression E is a core constant expression unless the evaluation of E // would evaluate one of the following: [C++23] - a control flow that passes // through a declaration of a variable with static or thread storage duration // unless that variable is usable in constant expressions. assert(VD->isLocalVarDecl() && VD->isStaticLocal()); // Checked before emitting this. if (VD == S.EvaluatingDecl) return true; if (!VD->isUsableInConstantExpressions(S.getASTContext())) { S.CCEDiag(VD->getLocation(), diag::note_constexpr_static_local) << (VD->getTSCSpec() == TSCS_unspecified ? 0 : 1) << VD; return false; } return true; } inline bool Alloc(InterpState &S, CodePtr OpPC, const Descriptor *Desc) { assert(Desc); if (!CheckDynamicMemoryAllocation(S, OpPC)) return false; DynamicAllocator &Allocator = S.getAllocator(); Block *B = Allocator.allocate(Desc, S.Ctx.getEvalID(), DynamicAllocator::Form::NonArray); assert(B); S.Stk.push(B); return true; } template ::T> inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source, bool IsNoThrow) { if (!CheckDynamicMemoryAllocation(S, OpPC)) return false; SizeT NumElements = S.Stk.pop(); if (!CheckArraySize(S, OpPC, &NumElements, primSize(T), IsNoThrow)) { if (!IsNoThrow) return false; // If this failed and is nothrow, just return a null ptr. S.Stk.push(0, nullptr); return true; } assert(NumElements.isPositive()); if (!CheckArraySize(S, OpPC, static_cast(NumElements))) return false; DynamicAllocator &Allocator = S.getAllocator(); Block *B = Allocator.allocate(Source, T, static_cast(NumElements), S.Ctx.getEvalID(), DynamicAllocator::Form::Array); assert(B); if (NumElements.isZero()) S.Stk.push(B); else S.Stk.push(Pointer(B).atIndex(0)); return true; } template ::T> inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc, bool IsNoThrow) { if (!CheckDynamicMemoryAllocation(S, OpPC)) return false; SizeT NumElements = S.Stk.pop(); if (!CheckArraySize(S, OpPC, &NumElements, ElementDesc->getSize(), IsNoThrow)) { if (!IsNoThrow) return false; // If this failed and is nothrow, just return a null ptr. S.Stk.push(0, ElementDesc); return true; } assert(NumElements.isPositive()); if (!CheckArraySize(S, OpPC, static_cast(NumElements))) return false; DynamicAllocator &Allocator = S.getAllocator(); Block *B = Allocator.allocate(ElementDesc, static_cast(NumElements), S.Ctx.getEvalID(), DynamicAllocator::Form::Array); assert(B); if (NumElements.isZero()) S.Stk.push(B); else S.Stk.push(Pointer(B).atIndex(0)); return true; } bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm, bool IsGlobalDelete); static inline bool IsConstantContext(InterpState &S, CodePtr OpPC) { S.Stk.push(Boolean::from(S.inConstantContext())); return true; } static inline bool CheckAllocations(InterpState &S, CodePtr OpPC) { return S.maybeDiagnoseDanglingAllocations(); } /// Check if the initializer and storage types of a placement-new expression /// match. bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E, std::optional ArraySize = std::nullopt); template ::T> bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) { const auto &Size = S.Stk.pop(); return CheckNewTypeMismatch(S, OpPC, E, static_cast(Size)); } bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E); template ::T> inline bool BitCastPrim(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte, uint32_t ResultBitWidth, const llvm::fltSemantics *Sem) { const Pointer &FromPtr = S.Stk.pop(); if (!CheckLoad(S, OpPC, FromPtr)) return false; if constexpr (std::is_same_v) { // The only pointer type we can validly bitcast to is nullptr_t. S.Stk.push(); return true; } else { size_t BuffSize = ResultBitWidth / 8; llvm::SmallVector Buff(BuffSize); bool HasIndeterminateBits = false; Bits FullBitWidth(ResultBitWidth); Bits BitWidth = FullBitWidth; if constexpr (std::is_same_v) { assert(Sem); BitWidth = Bits(llvm::APFloatBase::getSizeInBits(*Sem)); } if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BitWidth, FullBitWidth, HasIndeterminateBits)) return false; if (!CheckBitCast(S, OpPC, HasIndeterminateBits, TargetIsUCharOrByte)) return false; if constexpr (std::is_same_v) { assert(Sem); Floating Result = S.allocFloat(*Sem); Floating::bitcastFromMemory(Buff.data(), *Sem, &Result); S.Stk.push(Result); } else if constexpr (needsAlloc()) { T Result = S.allocAP(ResultBitWidth); T::bitcastFromMemory(Buff.data(), ResultBitWidth, &Result); S.Stk.push(Result); } else if constexpr (std::is_same_v) { // Only allow to cast single-byte integers to bool if they are either 0 // or 1. assert(FullBitWidth.getQuantity() == 8); auto Val = static_cast(Buff[0]); if (Val > 1) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_bit_cast_unrepresentable_value) << S.getASTContext().BoolTy << Val; return false; } S.Stk.push(T::bitcastFromMemory(Buff.data(), ResultBitWidth)); } else { assert(!Sem); S.Stk.push(T::bitcastFromMemory(Buff.data(), ResultBitWidth)); } return true; } } inline bool BitCast(InterpState &S, CodePtr OpPC) { const Pointer &FromPtr = S.Stk.pop(); Pointer &ToPtr = S.Stk.peek(); if (!CheckLoad(S, OpPC, FromPtr)) return false; if (!DoBitCastPtr(S, OpPC, FromPtr, ToPtr)) return false; return true; } /// Typeid support. bool GetTypeid(InterpState &S, CodePtr OpPC, const Type *TypePtr, const Type *TypeInfoType); bool GetTypeidPtr(InterpState &S, CodePtr OpPC, const Type *TypeInfoType); bool DiagTypeid(InterpState &S, CodePtr OpPC); inline bool CheckDestruction(InterpState &S, CodePtr OpPC) { const auto &Ptr = S.Stk.peek(); return CheckDestructor(S, OpPC, Ptr); } inline bool CheckArraySize(InterpState &S, CodePtr OpPC, uint64_t NumElems) { uint64_t Limit = S.getLangOpts().ConstexprStepLimit; if (NumElems > Limit) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_exceeds_limits) << NumElems << Limit; return false; } return true; } //===----------------------------------------------------------------------===// // Read opcode arguments //===----------------------------------------------------------------------===// template inline T ReadArg(InterpState &S, CodePtr &OpPC) { if constexpr (std::is_pointer::value) { uint32_t ID = OpPC.read(); return reinterpret_cast(S.P.getNativePointer(ID)); } else { return OpPC.read(); } } template <> inline Floating ReadArg(InterpState &S, CodePtr &OpPC) { auto &Semantics = llvm::APFloatBase::EnumToSemantics(Floating::deserializeSemantics(*OpPC)); auto F = S.allocFloat(Semantics); Floating::deserialize(*OpPC, &F); OpPC += align(F.bytesToSerialize()); return F; } template <> inline IntegralAP ReadArg>(InterpState &S, CodePtr &OpPC) { uint32_t BitWidth = IntegralAP::deserializeSize(*OpPC); auto Result = S.allocAP>(BitWidth); assert(Result.bitWidth() == BitWidth); IntegralAP::deserialize(*OpPC, &Result); OpPC += align(Result.bytesToSerialize()); return Result; } template <> inline IntegralAP ReadArg>(InterpState &S, CodePtr &OpPC) { uint32_t BitWidth = IntegralAP::deserializeSize(*OpPC); auto Result = S.allocAP>(BitWidth); assert(Result.bitWidth() == BitWidth); IntegralAP::deserialize(*OpPC, &Result); OpPC += align(Result.bytesToSerialize()); return Result; } template <> inline FixedPoint ReadArg(InterpState &S, CodePtr &OpPC) { FixedPoint FP = FixedPoint::deserialize(*OpPC); OpPC += align(FP.bytesToSerialize()); return FP; } } // namespace interp } // namespace clang #endif