diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/AST/Interp/Interp.h')
-rw-r--r-- | contrib/llvm-project/clang/lib/AST/Interp/Interp.h | 1338 |
1 files changed, 1214 insertions, 124 deletions
diff --git a/contrib/llvm-project/clang/lib/AST/Interp/Interp.h b/contrib/llvm-project/clang/lib/AST/Interp/Interp.h index a63c5a871ba3..65c54ed9c89b 100644 --- a/contrib/llvm-project/clang/lib/AST/Interp/Interp.h +++ b/contrib/llvm-project/clang/lib/AST/Interp/Interp.h @@ -13,9 +13,10 @@ #ifndef LLVM_CLANG_AST_INTERP_INTERP_H #define LLVM_CLANG_AST_INTERP_INTERP_H -#include <limits> -#include <vector> +#include "Boolean.h" +#include "Floating.h" #include "Function.h" +#include "FunctionPointer.h" #include "InterpFrame.h" #include "InterpStack.h" #include "InterpState.h" @@ -30,14 +31,15 @@ #include "llvm/ADT/APFloat.h" #include "llvm/ADT/APSInt.h" #include "llvm/Support/Endian.h" +#include <limits> +#include <type_traits> namespace clang { namespace interp { -using APInt = llvm::APInt; using APSInt = llvm::APSInt; -/// Convers a value to an APValue. +/// Convert a value to an APValue. template <typename T> bool ReturnValue(const T &V, APValue &R) { R = V.toAPValue(); return true; @@ -49,9 +51,13 @@ 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 accesible. +/// 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); + /// Checks if a pointer is null. bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr, CheckSubobjectKind CSK); @@ -64,15 +70,25 @@ bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, 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 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); +bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + AccessKinds AK); + /// Checks if a value can be stored in a block. bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr); @@ -83,7 +99,11 @@ bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr); bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a method can be called. -bool CheckCallable(InterpState &S, CodePtr OpPC, Function *F); +bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F); + +/// Checks if calling the currently active function would exceed +/// the allowed call depth. +bool CheckCallDepth(InterpState &S, CodePtr OpPC); /// Checks the 'this' pointer. bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This); @@ -91,7 +111,151 @@ bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This); /// Checks if a method is pure virtual. bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD); -template <typename T> inline bool IsTrue(const T &V) { return !V.isZero(); } +/// Checks if reinterpret casts are legal in the current context. +bool CheckPotentialReinterpretCast(InterpState &S, CodePtr OpPC, + const Pointer &Ptr); + +/// 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); + +/// Checks if the shift operation is legal. +template <typename LT, typename RT> +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(); + return false; + } + + // C++11 [expr.shift]p1: Shift width must be less than the bit width of + // the shifted type. + if (Bits > 1 && RHS >= RT::from(Bits, RHS.bitWidth())) { + 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; + return false; + } + + if (LHS.isSigned() && !S.getLangOpts().CPlusPlus20) { + const Expr *E = S.Current->getExpr(OpPC); + // 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()) + S.CCEDiag(E, diag::note_constexpr_lshift_of_negative) << LHS.toAPSInt(); + else if (LHS.toUnsigned().countLeadingZeros() < static_cast<unsigned>(RHS)) + S.CCEDiag(E, diag::note_constexpr_lshift_discards); + } + + // 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 <typename T> +bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) { + if (RHS.isZero()) { + const auto *Op = cast<BinaryOperator>(S.Current->getExpr(OpPC)); + S.FFDiag(Op, diag::note_expr_divide_by_zero) + << Op->getRHS()->getSourceRange(); + return false; + } + + 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; +} + +/// 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); + +/// Checks why the given DeclRefExpr is invalid. +bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR); + +/// Interpreter entry point. +bool Interpret(InterpState &S, APValue &Result); + +/// Interpret a builtin function. +bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F, + const CallExpr *Call); + +/// Interpret an offsetof operation. +bool InterpretOffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E, + llvm::ArrayRef<int64_t> ArrayIndices, int64_t &Result); + +enum class ArithOp { Add, Sub }; + +//===----------------------------------------------------------------------===// +// Returning values +//===----------------------------------------------------------------------===// + +void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC); + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool Ret(InterpState &S, CodePtr &PC, APValue &Result) { + const T &Ret = S.Stk.pop<T>(); + + // Make sure returned pointers are live. We might be trying to return a + // pointer or reference to a local variable. + // Just return false, since a diagnostic has already been emitted in Sema. + if constexpr (std::is_same_v<T, Pointer>) { + // FIXME: We could be calling isLive() here, but the emitted diagnostics + // seem a little weird, at least if the returned expression is of + // pointer type. + // Null pointers are considered live here. + if (!Ret.isZero() && !Ret.isLive()) + return false; + } + + assert(S.Current); + assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); + if (!S.checkingPotentialConstantExpression() || S.Current->Caller) + cleanupAfterFunctionCall(S, PC); + + if (InterpFrame *Caller = S.Current->Caller) { + PC = S.Current->getRetPC(); + delete S.Current; + S.Current = Caller; + S.Stk.push<T>(Ret); + } else { + delete S.Current; + S.Current = nullptr; + if (!ReturnValue<T>(Ret, Result)) + return false; + } + return true; +} + +inline bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) { + assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); + + if (!S.checkingPotentialConstantExpression() || S.Current->Caller) + cleanupAfterFunctionCall(S, PC); + + if (InterpFrame *Caller = S.Current->Caller) { + PC = S.Current->getRetPC(); + delete S.Current; + S.Current = Caller; + } else { + delete S.Current; + S.Current = nullptr; + } + return true; +} //===----------------------------------------------------------------------===// // Add, Sub, Mul @@ -118,13 +282,19 @@ bool AddSubMulHelper(InterpState &S, CodePtr OpPC, unsigned Bits, const T &LHS, const Expr *E = S.Current->getExpr(OpPC); QualType Type = E->getType(); if (S.checkingForUndefinedBehavior()) { - auto Trunc = Value.trunc(Result.bitWidth()).toString(10); + SmallString<32> Trunc; + Value.trunc(Result.bitWidth()).toString(Trunc, 10); auto Loc = E->getExprLoc(); - S.report(Loc, diag::warn_integer_constant_overflow) << Trunc << Type; + S.report(Loc, diag::warn_integer_constant_overflow) + << Trunc << Type << E->getSourceRange(); return true; } else { S.CCEDiag(E, diag::note_constexpr_overflow) << Value << Type; - return S.noteUndefinedBehavior(); + if (!S.noteUndefinedBehavior()) { + S.Stk.pop<T>(); + return false; + } + return true; } } @@ -136,6 +306,16 @@ bool Add(InterpState &S, CodePtr OpPC) { return AddSubMulHelper<T, T::add, std::plus>(S, OpPC, Bits, LHS, RHS); } +inline bool Addf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop<Floating>(); + const Floating &LHS = S.Stk.pop<Floating>(); + + Floating Result; + auto Status = Floating::add(LHS, RHS, RM, &Result); + S.Stk.push<Floating>(Result); + return CheckFloatResult(S, OpPC, Result, Status); +} + template <PrimType Name, class T = typename PrimConv<Name>::T> bool Sub(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop<T>(); @@ -144,6 +324,16 @@ bool Sub(InterpState &S, CodePtr OpPC) { return AddSubMulHelper<T, T::sub, std::minus>(S, OpPC, Bits, LHS, RHS); } +inline bool Subf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop<Floating>(); + const Floating &LHS = S.Stk.pop<Floating>(); + + Floating Result; + auto Status = Floating::sub(LHS, RHS, RM, &Result); + S.Stk.push<Floating>(Result); + return CheckFloatResult(S, OpPC, Result, Status); +} + template <PrimType Name, class T = typename PrimConv<Name>::T> bool Mul(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop<T>(); @@ -152,6 +342,348 @@ bool Mul(InterpState &S, CodePtr OpPC) { return AddSubMulHelper<T, T::mul, std::multiplies>(S, OpPC, Bits, LHS, RHS); } +inline bool Mulf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop<Floating>(); + const Floating &LHS = S.Stk.pop<Floating>(); + + Floating Result; + auto Status = Floating::mul(LHS, RHS, RM, &Result); + S.Stk.push<Floating>(Result); + return CheckFloatResult(S, OpPC, Result, Status); +} +/// 1) Pops the RHS from the stack. +/// 2) Pops the LHS from the stack. +/// 3) Pushes 'LHS & RHS' on the stack +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool BitAnd(InterpState &S, CodePtr OpPC) { + const T &RHS = S.Stk.pop<T>(); + const T &LHS = S.Stk.pop<T>(); + + unsigned Bits = RHS.bitWidth(); + T Result; + if (!T::bitAnd(LHS, RHS, Bits, &Result)) { + S.Stk.push<T>(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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool BitOr(InterpState &S, CodePtr OpPC) { + const T &RHS = S.Stk.pop<T>(); + const T &LHS = S.Stk.pop<T>(); + + unsigned Bits = RHS.bitWidth(); + T Result; + if (!T::bitOr(LHS, RHS, Bits, &Result)) { + S.Stk.push<T>(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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool BitXor(InterpState &S, CodePtr OpPC) { + const T &RHS = S.Stk.pop<T>(); + const T &LHS = S.Stk.pop<T>(); + + unsigned Bits = RHS.bitWidth(); + T Result; + if (!T::bitXor(LHS, RHS, Bits, &Result)) { + S.Stk.push<T>(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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool Rem(InterpState &S, CodePtr OpPC) { + const T &RHS = S.Stk.pop<T>(); + const T &LHS = S.Stk.pop<T>(); + + if (!CheckDivRem(S, OpPC, LHS, RHS)) + return false; + + const unsigned Bits = RHS.bitWidth() * 2; + T Result; + if (!T::rem(LHS, RHS, Bits, &Result)) { + S.Stk.push<T>(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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool Div(InterpState &S, CodePtr OpPC) { + const T &RHS = S.Stk.pop<T>(); + const T &LHS = S.Stk.pop<T>(); + + if (!CheckDivRem(S, OpPC, LHS, RHS)) + return false; + + const unsigned Bits = RHS.bitWidth() * 2; + T Result; + if (!T::div(LHS, RHS, Bits, &Result)) { + S.Stk.push<T>(Result); + return true; + } + return false; +} + +inline bool Divf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop<Floating>(); + const Floating &LHS = S.Stk.pop<Floating>(); + + if (!CheckDivRem(S, OpPC, LHS, RHS)) + return false; + + Floating Result; + auto Status = Floating::div(LHS, RHS, RM, &Result); + S.Stk.push<Floating>(Result); + return CheckFloatResult(S, OpPC, Result, Status); +} + +//===----------------------------------------------------------------------===// +// Inv +//===----------------------------------------------------------------------===// + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool Inv(InterpState &S, CodePtr OpPC) { + using BoolT = PrimConv<PT_Bool>::T; + const T &Val = S.Stk.pop<T>(); + const unsigned Bits = Val.bitWidth(); + Boolean R; + Boolean::inv(BoolT::from(Val, Bits), &R); + + S.Stk.push<BoolT>(R); + return true; +} + +//===----------------------------------------------------------------------===// +// Neg +//===----------------------------------------------------------------------===// + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool Neg(InterpState &S, CodePtr OpPC) { + const T &Value = S.Stk.pop<T>(); + T Result; + + if (!T::neg(Value, &Result)) { + S.Stk.push<T>(Result); + return true; + } + + assert(isIntegralType(Name) && + "don't expect other types to fail at constexpr negation"); + S.Stk.push<T>(Result); + + APSInt NegatedValue = -Value.toAPSInt(Value.bitWidth() + 1); + const Expr *E = S.Current->getExpr(OpPC); + QualType Type = E->getType(); + + if (S.checkingForUndefinedBehavior()) { + SmallString<32> Trunc; + NegatedValue.trunc(Result.bitWidth()).toString(Trunc, 10); + auto Loc = E->getExprLoc(); + S.report(Loc, diag::warn_integer_constant_overflow) + << Trunc << Type << E->getSourceRange(); + return true; + } + + S.CCEDiag(E, diag::note_constexpr_overflow) << NegatedValue << Type; + return S.noteUndefinedBehavior(); +} + +enum class PushVal : bool { + No, + Yes, +}; +enum class IncDecOp { + Inc, + Dec, +}; + +template <typename T, IncDecOp Op, PushVal DoPush> +bool IncDecHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { + const T &Value = Ptr.deref<T>(); + T Result; + + if constexpr (DoPush == PushVal::Yes) + S.Stk.push<T>(Value); + + if constexpr (Op == IncDecOp::Inc) { + if (!T::increment(Value, &Result)) { + Ptr.deref<T>() = Result; + return true; + } + } else { + if (!T::decrement(Value, &Result)) { + Ptr.deref<T>() = Result; + return true; + } + } + + // 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. + const Expr *E = S.Current->getExpr(OpPC); + QualType Type = E->getType(); + if (S.checkingForUndefinedBehavior()) { + SmallString<32> Trunc; + APResult.trunc(Result.bitWidth()).toString(Trunc, 10); + auto Loc = E->getExprLoc(); + S.report(Loc, diag::warn_integer_constant_overflow) + << Trunc << Type << E->getSourceRange(); + return true; + } + + S.CCEDiag(E, diag::note_constexpr_overflow) << APResult << Type; + return S.noteUndefinedBehavior(); +} + +/// 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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool Inc(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Increment)) + return false; + + return IncDecHelper<T, IncDecOp::Inc, PushVal::Yes>(S, OpPC, Ptr); +} + +/// 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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool IncPop(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Increment)) + return false; + + return IncDecHelper<T, IncDecOp::Inc, PushVal::No>(S, OpPC, Ptr); +} + +/// 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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool Dec(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Decrement)) + return false; + + return IncDecHelper<T, IncDecOp::Dec, PushVal::Yes>(S, OpPC, Ptr); +} + +/// 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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool DecPop(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Decrement)) + return false; + + return IncDecHelper<T, IncDecOp::Dec, PushVal::No>(S, OpPC, Ptr); +} + +template <IncDecOp Op, PushVal DoPush> +bool IncDecFloatHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + llvm::RoundingMode RM) { + Floating Value = Ptr.deref<Floating>(); + Floating Result; + + if constexpr (DoPush == PushVal::Yes) + S.Stk.push<Floating>(Value); + + llvm::APFloat::opStatus Status; + if constexpr (Op == IncDecOp::Inc) + Status = Floating::increment(Value, RM, &Result); + else + Status = Floating::decrement(Value, RM, &Result); + + Ptr.deref<Floating>() = Result; + + return CheckFloatResult(S, OpPC, Result, Status); +} + +inline bool Incf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Increment)) + return false; + + return IncDecFloatHelper<IncDecOp::Inc, PushVal::Yes>(S, OpPC, Ptr, RM); +} + +inline bool IncfPop(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Increment)) + return false; + + return IncDecFloatHelper<IncDecOp::Inc, PushVal::No>(S, OpPC, Ptr, RM); +} + +inline bool Decf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Decrement)) + return false; + + return IncDecFloatHelper<IncDecOp::Dec, PushVal::Yes>(S, OpPC, Ptr, RM); +} + +inline bool DecfPop(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Decrement)) + return false; + + return IncDecFloatHelper<IncDecOp::Dec, PushVal::No>(S, OpPC, Ptr, RM); +} + +/// 1) Pops the value from the stack. +/// 2) Pushes the bitwise complemented value on the stack (~V). +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool Comp(InterpState &S, CodePtr OpPC) { + const T &Val = S.Stk.pop<T>(); + T Result; + if (!T::comp(Val, &Result)) { + S.Stk.push<T>(Result); + return true; + } + + return false; +} + //===----------------------------------------------------------------------===// // EQ, NE, GT, GE, LT, LE //===----------------------------------------------------------------------===// @@ -172,6 +704,29 @@ bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { return CmpHelper<T>(S, OpPC, Fn); } +/// Function pointers cannot be compared in an ordered way. +template <> +inline bool CmpHelper<FunctionPointer>(InterpState &S, CodePtr OpPC, + CompareFn Fn) { + const auto &RHS = S.Stk.pop<FunctionPointer>(); + const auto &LHS = S.Stk.pop<FunctionPointer>(); + + const SourceInfo &Loc = S.Current->getSource(OpPC); + S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_unspecified) + << LHS.toDiagnosticString(S.getCtx()) + << RHS.toDiagnosticString(S.getCtx()); + return false; +} + +template <> +inline bool CmpHelperEQ<FunctionPointer>(InterpState &S, CodePtr OpPC, + CompareFn Fn) { + const auto &RHS = S.Stk.pop<FunctionPointer>(); + const auto &LHS = S.Stk.pop<FunctionPointer>(); + S.Stk.push<Boolean>(Boolean::from(Fn(LHS.compare(RHS)))); + return true; +} + template <> inline bool CmpHelper<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) { using BoolT = PrimConv<PT_Bool>::T; @@ -180,7 +735,9 @@ inline bool CmpHelper<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) { if (!Pointer::hasSameBase(LHS, RHS)) { const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr); + S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_unspecified) + << LHS.toDiagnosticString(S.getCtx()) + << RHS.toDiagnosticString(S.getCtx()); return false; } else { unsigned VL = LHS.getByteOffset(); @@ -207,6 +764,16 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) { } else { unsigned VL = LHS.getByteOffset(); unsigned VR = RHS.getByteOffset(); + + // In our Pointer class, a pointer to an array and a pointer to the first + // element in the same array are NOT equal. They have the same Base value, + // but a different Offset. This is a pretty rare case, so we fix this here + // by comparing pointers to the first elements. + if (LHS.isArrayRoot()) + VL = LHS.atIndex(0).getByteOffset(); + if (RHS.isArrayRoot()) + VR = RHS.atIndex(0).getByteOffset(); + S.Stk.push<BoolT>(BoolT::from(Fn(Compare(VL, VR)))); return true; } @@ -220,6 +787,30 @@ bool EQ(InterpState &S, CodePtr OpPC) { } template <PrimType Name, class T = typename PrimConv<Name>::T> +bool CMP3(InterpState &S, CodePtr OpPC, const ComparisonCategoryInfo *CmpInfo) { + const T &RHS = S.Stk.pop<T>(); + const T &LHS = S.Stk.pop<T>(); + const Pointer &P = S.Stk.peek<Pointer>(); + + ComparisonCategoryResult CmpResult = LHS.compare(RHS); + if (CmpResult == ComparisonCategoryResult::Unordered) { + // This should only happen with pointers. + const SourceInfo &Loc = S.Current->getSource(OpPC); + S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_unspecified) + << LHS.toDiagnosticString(S.getCtx()) + << RHS.toDiagnosticString(S.getCtx()); + return false; + } + + assert(CmpInfo); + const auto *CmpValueInfo = CmpInfo->getValueInfo(CmpResult); + assert(CmpValueInfo); + assert(CmpValueInfo->hasValidIntValue()); + APSInt IntValue = CmpValueInfo->getIntValue(); + return SetThreeWayComparisonField(S, OpPC, P, IntValue); +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> bool NE(InterpState &S, CodePtr OpPC) { return CmpHelperEQ<T>(S, OpPC, [](ComparisonCategoryResult R) { return R != ComparisonCategoryResult::Equal; @@ -302,10 +893,16 @@ bool Const(InterpState &S, CodePtr OpPC, const T &Arg) { template <PrimType Name, class T = typename PrimConv<Name>::T> bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { - S.Stk.push<T>(S.Current->getLocal<T>(I)); + const Pointer &Ptr = S.Current->getLocalPointer(I); + if (!CheckLoad(S, OpPC, Ptr)) + return false; + S.Stk.push<T>(Ptr.deref<T>()); return true; } +/// 1) Pops the value from the stack. +/// 2) Writes the value to the local variable with the +/// given offset. template <PrimType Name, class T = typename PrimConv<Name>::T> bool SetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->setLocal<T>(I, S.Stk.pop<T>()); @@ -327,6 +924,8 @@ bool SetParam(InterpState &S, CodePtr OpPC, uint32_t I) { return true; } +/// 1) Peeks a pointer on the stack +/// 2) Pushes the value of the pointer's field on the stack template <PrimType Name, class T = typename PrimConv<Name>::T> bool GetField(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Obj = S.Stk.peek<Pointer>(); @@ -352,10 +951,13 @@ bool SetField(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Field = Obj.atField(I); if (!CheckStore(S, OpPC, Field)) return false; + Field.initialize(); Field.deref<T>() = Value; return true; } +/// 1) Pops a pointer from the stack +/// 2) Pushes the value of the pointer's field on the stack template <PrimType Name, class T = typename PrimConv<Name>::T> bool GetFieldPop(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Obj = S.Stk.pop<Pointer>(); @@ -401,13 +1003,24 @@ bool SetThisField(InterpState &S, CodePtr OpPC, uint32_t I) { template <PrimType Name, class T = typename PrimConv<Name>::T> bool GetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { - auto *B = S.P.getGlobal(I); + const Block *B = S.P.getGlobal(I); + + if (!CheckConstant(S, OpPC, B->getDescriptor())) + return false; if (B->isExtern()) return false; S.Stk.push<T>(B->deref<T>()); return true; } +/// Same as GetGlobal, but without the checks. +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool GetGlobalUnchecked(InterpState &S, CodePtr OpPC, uint32_t I) { + auto *B = S.P.getGlobal(I); + S.Stk.push<T>(B->deref<T>()); + return true; +} + template <PrimType Name, class T = typename PrimConv<Name>::T> bool SetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { // TODO: emit warning. @@ -420,6 +1033,39 @@ bool InitGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { 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 +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool InitGlobalTemp(InterpState &S, CodePtr OpPC, uint32_t I, + const LifetimeExtendedTemporaryDecl *Temp) { + assert(Temp); + const T Value = S.Stk.peek<T>(); + APValue APV = Value.toAPValue(); + APValue *Cached = Temp->getOrCreateValue(true); + *Cached = APV; + + S.P.getGlobal(I)->deref<T>() = S.Stk.pop<T>(); + 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<Pointer>(); + APValue *Cached = Temp->getOrCreateValue(true); + + if (std::optional<APValue> APV = P.toRValue(S.getCtx())) { + *Cached = *APV; + return true; + } + + return false; +} + template <PrimType Name, class T = typename PrimConv<Name>::T> bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) @@ -433,14 +1079,18 @@ bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) { return true; } +// FIXME: The Field pointer here is too much IMO and we could instead just +// pass an Offset + BitWidth pair. template <PrimType Name, class T = typename PrimConv<Name>::T> -bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { +bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F, + uint32_t FieldOffset) { + assert(F->isBitField()); if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; - const Pointer &Field = This.atField(F->Offset); + const Pointer &Field = This.atField(FieldOffset); const auto &Value = S.Stk.pop<T>(); Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue(S.getCtx())); Field.initialize(); @@ -461,10 +1111,13 @@ bool InitThisFieldActive(InterpState &S, CodePtr OpPC, uint32_t I) { 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 <PrimType Name, class T = typename PrimConv<Name>::T> bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop<T>(); - const Pointer &Field = S.Stk.pop<Pointer>().atField(I); + const Pointer &Field = S.Stk.peek<Pointer>().atField(I); Field.deref<T>() = Value; Field.activate(); Field.initialize(); @@ -473,8 +1126,9 @@ bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) { template <PrimType Name, class T = typename PrimConv<Name>::T> bool InitBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { + assert(F->isBitField()); const T &Value = S.Stk.pop<T>(); - const Pointer &Field = S.Stk.pop<Pointer>().atField(F->Offset); + const Pointer &Field = S.Stk.peek<Pointer>().atField(F->Offset); Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue(S.getCtx())); Field.activate(); Field.initialize(); @@ -514,14 +1168,19 @@ inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { return true; } +/// 1) Pops a Pointer from the stack +/// 2) Pushes Pointer.atField(Off) on the stack inline bool GetPtrField(InterpState &S, CodePtr OpPC, uint32_t Off) { const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (!CheckNull(S, OpPC, Ptr, CSK_Field)) + if (S.inConstantContext() && !CheckNull(S, OpPC, Ptr, CSK_Field)) return false; if (!CheckExtern(S, OpPC, Ptr)) return false; if (!CheckRange(S, OpPC, Ptr, CSK_Field)) return false; + if (!CheckSubobject(S, OpPC, Ptr, CSK_Field)) + return false; + S.Stk.push<Pointer>(Ptr.atField(Off)); return true; } @@ -562,10 +1221,32 @@ inline bool GetPtrActiveThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { return true; } +inline bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, uint32_t Off) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + if (!CheckNull(S, OpPC, Ptr, CSK_Derived)) + return false; + if (!CheckSubobject(S, OpPC, Ptr, CSK_Derived)) + return false; + S.Stk.push<Pointer>(Ptr.atFieldSub(Off)); + return true; +} + inline bool GetPtrBase(InterpState &S, CodePtr OpPC, uint32_t Off) { + const Pointer &Ptr = S.Stk.peek<Pointer>(); + if (!CheckNull(S, OpPC, Ptr, CSK_Base)) + return false; + if (!CheckSubobject(S, OpPC, Ptr, CSK_Base)) + return false; + S.Stk.push<Pointer>(Ptr.atField(Off)); + return true; +} + +inline bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t Off) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckNull(S, OpPC, Ptr, CSK_Base)) return false; + if (!CheckSubobject(S, OpPC, Ptr, CSK_Base)) + return false; S.Stk.push<Pointer>(Ptr.atField(Off)); return true; } @@ -580,6 +1261,12 @@ inline bool GetPtrThisBase(InterpState &S, CodePtr OpPC, uint32_t Off) { return true; } +inline bool InitPtrPop(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + Ptr.initialize(); + return true; +} + inline bool VirtBaseHelper(InterpState &S, CodePtr OpPC, const RecordDecl *Decl, const Pointer &Ptr) { Pointer Base = Ptr; @@ -636,6 +1323,8 @@ bool Store(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); Ptr.deref<T>() = Value; return true; } @@ -646,6 +1335,8 @@ bool StorePop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); Ptr.deref<T>() = Value; return true; } @@ -656,11 +1347,12 @@ bool StoreBitField(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; - if (auto *FD = Ptr.getField()) { + if (!Ptr.isRoot()) + Ptr.initialize(); + if (const auto *FD = Ptr.getField()) Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue(S.getCtx())); - } else { + else Ptr.deref<T>() = Value; - } return true; } @@ -670,11 +1362,12 @@ bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; - if (auto *FD = Ptr.getField()) { + if (!Ptr.isRoot()) + Ptr.initialize(); + if (const auto *FD = Ptr.getField()) Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue(S.getCtx())); - } else { + else Ptr.deref<T>() = Value; - } return true; } @@ -689,6 +1382,9 @@ bool InitPop(InterpState &S, CodePtr OpPC) { 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 <PrimType Name, class T = typename PrimConv<Name>::T> bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop<T>(); @@ -700,6 +1396,7 @@ bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) { return true; } +/// The same as InitElem, but pops the pointer as well. template <PrimType Name, class T = typename PrimConv<Name>::T> bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop<T>(); @@ -715,74 +1412,151 @@ bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) { // AddOffset, SubOffset //===----------------------------------------------------------------------===// -template <class T, bool Add> bool OffsetHelper(InterpState &S, CodePtr OpPC) { - // Fetch the pointer and the offset. - const T &Offset = S.Stk.pop<T>(); - const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex)) - return false; +template <class T, ArithOp Op> +bool OffsetHelper(InterpState &S, CodePtr OpPC, const T &Offset, + const Pointer &Ptr) { if (!CheckRange(S, OpPC, Ptr, CSK_ArrayToPointer)) return false; - // Get a version of the index comparable to the type. - T Index = T::from(Ptr.getIndex(), Offset.bitWidth()); - // A zero offset does not change the pointer, but in the case of an array - // it has to be adjusted to point to the first element instead of the array. + // A zero offset does not change the pointer. if (Offset.isZero()) { - S.Stk.push<Pointer>(Index.isZero() ? Ptr.atIndex(0) : Ptr); + S.Stk.push<Pointer>(Ptr); return true; } + + if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex)) + return false; + // Arrays of unknown bounds cannot have pointers into them. if (!CheckArray(S, OpPC, Ptr)) return false; + // Get a version of the index comparable to the type. + T Index = T::from(Ptr.getIndex(), Offset.bitWidth()); // Compute the largest index into the array. - unsigned MaxIndex = Ptr.getNumElems(); + T MaxIndex = T::from(Ptr.getNumElems(), Offset.bitWidth()); + bool Invalid = false; // Helper to report an invalid offset, computed as APSInt. - auto InvalidOffset = [&]() { + auto DiagInvalidOffset = [&]() -> void { const unsigned Bits = Offset.bitWidth(); APSInt APOffset(Offset.toAPSInt().extend(Bits + 2), false); APSInt APIndex(Index.toAPSInt().extend(Bits + 2), false); - APSInt NewIndex = Add ? (APIndex + APOffset) : (APIndex - APOffset); + APSInt NewIndex = + (Op == ArithOp::Add) ? (APIndex + APOffset) : (APIndex - APOffset); S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) << NewIndex << /*array*/ static_cast<int>(!Ptr.inArray()) << static_cast<unsigned>(MaxIndex); - return false; + Invalid = true; }; - // If the new offset would be negative, bail out. - if (Add && Offset.isNegative() && (Offset.isMin() || -Offset > Index)) - return InvalidOffset(); - if (!Add && Offset.isPositive() && Index < Offset) - return InvalidOffset(); + T MaxOffset = T::from(MaxIndex - Index, Offset.bitWidth()); + if constexpr (Op == ArithOp::Add) { + // If the new offset would be negative, bail out. + if (Offset.isNegative() && (Offset.isMin() || -Offset > Index)) + DiagInvalidOffset(); - // If the new offset would be out of bounds, bail out. - unsigned MaxOffset = MaxIndex - Ptr.getIndex(); - if (Add && Offset.isPositive() && Offset > MaxOffset) - return InvalidOffset(); - if (!Add && Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset)) - return InvalidOffset(); + // If the new offset would be out of bounds, bail out. + if (Offset.isPositive() && Offset > MaxOffset) + DiagInvalidOffset(); + } else { + // If the new offset would be negative, bail out. + if (Offset.isPositive() && Index < Offset) + DiagInvalidOffset(); + + // If the new offset would be out of bounds, bail out. + if (Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset)) + DiagInvalidOffset(); + } + + if (Invalid && !Ptr.isDummy() && S.getLangOpts().CPlusPlus) + return false; // Offset is valid - compute it on unsigned. int64_t WideIndex = static_cast<int64_t>(Index); int64_t WideOffset = static_cast<int64_t>(Offset); - int64_t Result = Add ? (WideIndex + WideOffset) : (WideIndex - WideOffset); + int64_t Result; + if constexpr (Op == ArithOp::Add) + Result = WideIndex + WideOffset; + else + Result = WideIndex - WideOffset; + S.Stk.push<Pointer>(Ptr.atIndex(static_cast<unsigned>(Result))); return true; } template <PrimType Name, class T = typename PrimConv<Name>::T> bool AddOffset(InterpState &S, CodePtr OpPC) { - return OffsetHelper<T, true>(S, OpPC); + const T &Offset = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.pop<Pointer>(); + return OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr); } template <PrimType Name, class T = typename PrimConv<Name>::T> bool SubOffset(InterpState &S, CodePtr OpPC) { - return OffsetHelper<T, false>(S, OpPC); + const T &Offset = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.pop<Pointer>(); + return OffsetHelper<T, ArithOp::Sub>(S, OpPC, Offset, Ptr); +} + +template <ArithOp Op> +static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC, + const Pointer &Ptr) { + using OneT = Integral<8, false>; + + const Pointer &P = Ptr.deref<Pointer>(); + if (!CheckNull(S, OpPC, P, CSK_ArrayIndex)) + return false; + + // Get the current value on the stack. + S.Stk.push<Pointer>(P); + + // Now the current Ptr again and a constant 1. + OneT One = OneT::from(1); + if (!OffsetHelper<OneT, Op>(S, OpPC, One, P)) + return false; + + // Store the new value. + Ptr.deref<Pointer>() = S.Stk.pop<Pointer>(); + return true; +} + +static inline bool IncPtr(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Increment)) + return false; + + return IncDecPtrHelper<ArithOp::Add>(S, OpPC, Ptr); } +static inline bool DecPtr(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckInitialized(S, OpPC, Ptr, AK_Decrement)) + return false; + + return IncDecPtrHelper<ArithOp::Sub>(S, OpPC, Ptr); +} + +/// 1) Pops a Pointer from the stack. +/// 2) Pops another Pointer from the stack. +/// 3) Pushes the different of the indices of the two pointers on the stack. +template <PrimType Name, class T = typename PrimConv<Name>::T> +inline bool SubPtr(InterpState &S, CodePtr OpPC) { + const Pointer &LHS = S.Stk.pop<Pointer>(); + const Pointer &RHS = S.Stk.pop<Pointer>(); + + if (!Pointer::hasSameBase(LHS, RHS) && S.getLangOpts().CPlusPlus) { + // TODO: Diagnose. + return false; + } + + T A = T::from(LHS.getIndex()); + T B = T::from(RHS.getIndex()); + return AddSubMulHelper<T, T::sub, std::minus>(S, OpPC, A.bitWidth(), A, B); +} //===----------------------------------------------------------------------===// // Destroy @@ -804,6 +1578,127 @@ template <PrimType TIn, PrimType TOut> bool Cast(InterpState &S, CodePtr OpPC) { 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>(); + Floating Result = F.toSemantics(Sem, RM); + S.Stk.push<Floating>(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 <PrimType Name, class T = typename PrimConv<Name>::T> +bool CastAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { + S.Stk.push<IntegralAP<false>>( + IntegralAP<false>::from(S.Stk.pop<T>(), BitWidth)); + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool CastAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { + S.Stk.push<IntegralAP<true>>( + IntegralAP<true>::from(S.Stk.pop<T>(), BitWidth)); + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool CastIntegralFloating(InterpState &S, CodePtr OpPC, + const llvm::fltSemantics *Sem, + llvm::RoundingMode RM) { + const T &From = S.Stk.pop<T>(); + APSInt FromAP = From.toAPSInt(); + Floating Result; + + auto Status = Floating::fromIntegral(FromAP, *Sem, RM, Result); + S.Stk.push<Floating>(Result); + + return CheckFloatResult(S, OpPC, Result, Status); +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool CastFloatingIntegral(InterpState &S, CodePtr OpPC) { + const Floating &F = S.Stk.pop<Floating>(); + + if constexpr (std::is_same_v<T, Boolean>) { + S.Stk.push<T>(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) && F.isFinite()) { + 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>(T(Result)); + return true; + } + return false; + } + + S.Stk.push<T>(T(Result)); + return CheckFloatResult(S, OpPC, F, Status); + } +} + +static inline bool CastFloatingIntegralAP(InterpState &S, CodePtr OpPC, + uint32_t BitWidth) { + const Floating &F = S.Stk.pop<Floating>(); + + APSInt Result(BitWidth, /*IsUnsigned=*/true); + auto Status = F.convertToInteger(Result); + + // Float-to-Integral overflow check. + if ((Status & APFloat::opStatus::opInvalidOp) && F.isFinite()) { + const Expr *E = S.Current->getExpr(OpPC); + QualType Type = E->getType(); + + S.CCEDiag(E, diag::note_constexpr_overflow) << F.getAPFloat() << Type; + return S.noteUndefinedBehavior(); + } + + S.Stk.push<IntegralAP<true>>(IntegralAP<true>(Result)); + return CheckFloatResult(S, OpPC, F, Status); +} + +static inline bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, + uint32_t BitWidth) { + const Floating &F = S.Stk.pop<Floating>(); + + APSInt Result(BitWidth, /*IsUnsigned=*/false); + auto Status = F.convertToInteger(Result); + + // Float-to-Integral overflow check. + if ((Status & APFloat::opStatus::opInvalidOp) && F.isFinite()) { + const Expr *E = S.Current->getExpr(OpPC); + QualType Type = E->getType(); + + S.CCEDiag(E, diag::note_constexpr_overflow) << F.getAPFloat() << Type; + return S.noteUndefinedBehavior(); + } + + S.Stk.push<IntegralAP<true>>(IntegralAP<true>(Result)); + return CheckFloatResult(S, OpPC, F, Status); +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool CastPointerIntegral(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!CheckPotentialReinterpretCast(S, OpPC, Ptr)) + return false; + + S.Stk.push<T>(T::from(Ptr.getIntegerRepresentation())); + return true; +} + //===----------------------------------------------------------------------===// // Zero, Nullptr //===----------------------------------------------------------------------===// @@ -814,6 +1709,16 @@ bool Zero(InterpState &S, CodePtr OpPC) { return true; } +static inline bool ZeroIntAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { + S.Stk.push<IntegralAP<false>>(IntegralAP<false>::zero(BitWidth)); + return true; +} + +static inline bool ZeroIntAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { + S.Stk.push<IntegralAP<true>>(IntegralAP<true>::zero(BitWidth)); + return true; +} + template <PrimType Name, class T = typename PrimConv<Name>::T> inline bool Null(InterpState &S, CodePtr OpPC) { S.Stk.push<T>(); @@ -838,88 +1743,53 @@ inline bool This(InterpState &S, CodePtr OpPC) { return true; } +inline bool RVOPtr(InterpState &S, CodePtr OpPC) { + assert(S.Current->getFunction()->hasRVO()); + if (S.checkingPotentialConstantExpression()) + return false; + S.Stk.push<Pointer>(S.Current->getRVOPtr()); + return true; +} + //===----------------------------------------------------------------------===// // Shr, Shl //===----------------------------------------------------------------------===// -template <PrimType TR, PrimType TL, class T = typename PrimConv<TR>::T> -unsigned Trunc(InterpState &S, CodePtr OpPC, unsigned Bits, const T &V) { - // C++11 [expr.shift]p1: Shift width must be less than the bit width of - // the shifted type. - if (Bits > 1 && V >= T::from(Bits, V.bitWidth())) { - const Expr *E = S.Current->getExpr(OpPC); - const APSInt Val = V.toAPSInt(); - QualType Ty = E->getType(); - S.CCEDiag(E, diag::note_constexpr_large_shift) << Val << Ty << Bits; - return Bits; - } else { - return static_cast<unsigned>(V); - } -} +template <PrimType NameL, PrimType NameR> +inline bool Shr(InterpState &S, CodePtr OpPC) { + using LT = typename PrimConv<NameL>::T; + using RT = typename PrimConv<NameR>::T; + const auto &RHS = S.Stk.pop<RT>(); + const auto &LHS = S.Stk.pop<LT>(); + const unsigned Bits = LHS.bitWidth(); -template <PrimType TL, PrimType TR, typename T = typename PrimConv<TL>::T> -inline bool ShiftRight(InterpState &S, CodePtr OpPC, const T &V, unsigned RHS) { - if (RHS >= V.bitWidth()) { - S.Stk.push<T>(T::from(0, V.bitWidth())); - } else { - S.Stk.push<T>(T::from(V >> RHS, V.bitWidth())); - } - return true; -} + if (!CheckShift(S, OpPC, LHS, RHS, Bits)) + return false; -template <PrimType TL, PrimType TR, typename T = typename PrimConv<TL>::T> -inline bool ShiftLeft(InterpState &S, CodePtr OpPC, const T &V, unsigned RHS) { - if (V.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. - // C++2a [expr.shift]p2: E1 << E2 is the unique value congruent to - // E1 x 2^E2 module 2^N. - if (V.isNegative()) { - const Expr *E = S.Current->getExpr(OpPC); - S.CCEDiag(E, diag::note_constexpr_lshift_of_negative) << V.toAPSInt(); - } else if (V.countLeadingZeros() < RHS) { - S.CCEDiag(S.Current->getExpr(OpPC), diag::note_constexpr_lshift_discards); - } - } + typename LT::AsUnsigned R; + LT::AsUnsigned::shiftRight(LT::AsUnsigned::from(LHS), + LT::AsUnsigned::from(RHS), Bits, &R); + S.Stk.push<LT>(LT::from(R)); - if (V.bitWidth() == 1) { - S.Stk.push<T>(V); - } else if (RHS >= V.bitWidth()) { - S.Stk.push<T>(T::from(0, V.bitWidth())); - } else { - S.Stk.push<T>(T::from(V.toUnsigned() << RHS, V.bitWidth())); - } return true; } -template <PrimType TL, PrimType TR> -inline bool Shr(InterpState &S, CodePtr OpPC) { - const auto &RHS = S.Stk.pop<typename PrimConv<TR>::T>(); - const auto &LHS = S.Stk.pop<typename PrimConv<TL>::T>(); - const unsigned Bits = LHS.bitWidth(); - - if (RHS.isSigned() && RHS.isNegative()) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); - return ShiftLeft<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, -RHS)); - } else { - return ShiftRight<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, RHS)); - } -} - -template <PrimType TL, PrimType TR> +template <PrimType NameL, PrimType NameR> inline bool Shl(InterpState &S, CodePtr OpPC) { - const auto &RHS = S.Stk.pop<typename PrimConv<TR>::T>(); - const auto &LHS = S.Stk.pop<typename PrimConv<TL>::T>(); + using LT = typename PrimConv<NameL>::T; + using RT = typename PrimConv<NameR>::T; + const auto &RHS = S.Stk.pop<RT>(); + const auto &LHS = S.Stk.pop<LT>(); const unsigned Bits = LHS.bitWidth(); - if (RHS.isSigned() && RHS.isNegative()) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); - return ShiftRight<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, -RHS)); - } else { - return ShiftLeft<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, RHS)); - } + if (!CheckShift(S, OpPC, LHS, RHS, Bits)) + return false; + + typename LT::AsUnsigned R; + LT::AsUnsigned::shiftLeft(LT::AsUnsigned::from(LHS), + LT::AsUnsigned::from(RHS, Bits), Bits, &R); + S.Stk.push<LT>(LT::from(R)); + return true; } //===----------------------------------------------------------------------===// @@ -948,8 +1818,228 @@ inline bool ExpandPtr(InterpState &S, CodePtr OpPC) { return true; } -/// Interpreter entry point. -bool Interpret(InterpState &S, APValue &Result); +// 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 <PrimType Name, class T = typename PrimConv<Name>::T> +inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) { + const T &Offset = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.peek<Pointer>(); + + if (!OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr)) + return false; + + return NarrowPtr(S, OpPC); +} + +/// Just takes a pointer and checks if its' an incomplete +/// array type. +inline bool ArrayDecay(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!Ptr.isUnknownSizeArray()) { + S.Stk.push<Pointer>(Ptr.atIndex(0)); + return true; + } + + const SourceInfo &E = S.Current->getSource(OpPC); + S.FFDiag(E, diag::note_constexpr_unsupported_unsized_array); + + return false; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) { + const T &Offset = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + if (!OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr)) + return false; + + return NarrowPtr(S, OpPC); +} + +inline bool Call(InterpState &S, CodePtr OpPC, const Function *Func) { + if (Func->hasThisPointer()) { + size_t ThisOffset = + Func->getArgSize() - (Func->hasRVO() ? primSize(PT_Ptr) : 0); + + const Pointer &ThisPtr = S.Stk.peek<Pointer>(ThisOffset); + + // If the current function is a lambda static invoker and + // the function we're about to call is a lambda call operator, + // skip the CheckInvoke, since the ThisPtr is a null pointer + // anyway. + if (!(S.Current->getFunction() && + S.Current->getFunction()->isLambdaStaticInvoker() && + Func->isLambdaCallOperator())) { + if (!CheckInvoke(S, OpPC, ThisPtr)) + return false; + } + + if (S.checkingPotentialConstantExpression()) + return false; + } + + if (!CheckCallable(S, OpPC, Func)) + return false; + + if (!CheckCallDepth(S, OpPC)) + return false; + + auto NewFrame = std::make_unique<InterpFrame>(S, Func, OpPC); + InterpFrame *FrameBefore = S.Current; + S.Current = NewFrame.get(); + + APValue CallResult; + // Note that we cannot assert(CallResult.hasValue()) here since + // Ret() above only sets the APValue if the curent frame doesn't + // have a caller set. + if (Interpret(S, CallResult)) { + NewFrame.release(); // Frame was delete'd already. + assert(S.Current == FrameBefore); + return true; + } + + // Interpreting the function failed somehow. Reset to + // previous state. + S.Current = FrameBefore; + return false; +} + +inline bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func) { + assert(Func->hasThisPointer()); + assert(Func->isVirtual()); + size_t ThisOffset = + Func->getArgSize() - (Func->hasRVO() ? primSize(PT_Ptr) : 0); + Pointer &ThisPtr = S.Stk.peek<Pointer>(ThisOffset); + + const CXXRecordDecl *DynamicDecl = + ThisPtr.getDeclDesc()->getType()->getAsCXXRecordDecl(); + const auto *StaticDecl = cast<CXXRecordDecl>(Func->getParentDecl()); + const auto *InitialFunction = cast<CXXMethodDecl>(Func->getDecl()); + const CXXMethodDecl *Overrider = S.getContext().getOverridingFunction( + DynamicDecl, StaticDecl, InitialFunction); + + if (Overrider != InitialFunction) { + // DR1872: An instantiated virtual constexpr function can't be called in a + // constant expression (prior to C++20). We can still constant-fold such a + // call. + if (!S.getLangOpts().CPlusPlus20 && Overrider->isVirtual()) { + const Expr *E = S.Current->getExpr(OpPC); + S.CCEDiag(E, diag::note_constexpr_virtual_call) << E->getSourceRange(); + } + + Func = S.getContext().getOrCreateFunction(Overrider); + + const CXXRecordDecl *ThisFieldDecl = + ThisPtr.getFieldDesc()->getType()->getAsCXXRecordDecl(); + if (Func->getParentDecl()->isDerivedFrom(ThisFieldDecl)) { + // If the function we call is further DOWN the hierarchy than the + // FieldDesc of our pointer, just get the DeclDesc instead, which + // is the furthest we might go up in the hierarchy. + ThisPtr = ThisPtr.getDeclPtr(); + } + } + + return Call(S, OpPC, Func); +} + +inline bool CallBI(InterpState &S, CodePtr &PC, const Function *Func, + const CallExpr *CE) { + auto NewFrame = std::make_unique<InterpFrame>(S, Func, PC); + + InterpFrame *FrameBefore = S.Current; + S.Current = NewFrame.get(); + + if (InterpretBuiltin(S, PC, Func, CE)) { + NewFrame.release(); + return true; + } + S.Current = FrameBefore; + return false; +} + +inline bool CallPtr(InterpState &S, CodePtr OpPC) { + const FunctionPointer &FuncPtr = S.Stk.pop<FunctionPointer>(); + + const Function *F = FuncPtr.getFunction(); + if (!F || !F->isConstexpr()) + return false; + + if (F->isVirtual()) + return CallVirt(S, OpPC, F); + + return Call(S, OpPC, F); +} + +inline bool GetFnPtr(InterpState &S, CodePtr OpPC, const Function *Func) { + assert(Func); + S.Stk.push<FunctionPointer>(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; +} + +/// Same here, but only for casts. +inline bool InvalidCast(InterpState &S, CodePtr OpPC, CastKind Kind) { + const SourceLocation &Loc = S.Current->getLocation(OpPC); + S.FFDiag(Loc, diag::note_constexpr_invalid_cast) + << static_cast<unsigned>(Kind) << S.Current->getRange(OpPC); + return false; +} + +inline bool InvalidDeclRef(InterpState &S, CodePtr OpPC, + const DeclRefExpr *DR) { + assert(DR); + return CheckDeclRef(S, OpPC, DR); +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +inline bool OffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E) { + llvm::SmallVector<int64_t> ArrayIndices; + for (size_t I = 0; I != E->getNumExpressions(); ++I) + ArrayIndices.emplace_back(S.Stk.pop<int64_t>()); + + int64_t Result; + if (!InterpretOffsetOf(S, OpPC, E, ArrayIndices, Result)) + return false; + + S.Stk.push<T>(T::from(Result)); + + return true; +} + +//===----------------------------------------------------------------------===// +// Read opcode arguments +//===----------------------------------------------------------------------===// + +template <typename T> inline T ReadArg(InterpState &S, CodePtr &OpPC) { + if constexpr (std::is_pointer<T>::value) { + uint32_t ID = OpPC.read<uint32_t>(); + return reinterpret_cast<T>(S.P.getNativePointer(ID)); + } else { + return OpPC.read<T>(); + } +} + +template <> inline Floating ReadArg<Floating>(InterpState &S, CodePtr &OpPC) { + Floating F = Floating::deserialize(*OpPC); + OpPC += align(F.bytesToSerialize()); + return F; +} } // namespace interp } // namespace clang |