
This is a major change on how we represent nested name qualifications in the AST. * The nested name specifier itself and how it's stored is changed. The prefixes for types are handled within the type hierarchy, which makes canonicalization for them super cheap, no memory allocation required. Also translating a type into nested name specifier form becomes a no-op. An identifier is stored as a DependentNameType. The nested name specifier gains a lightweight handle class, to be used instead of passing around pointers, which is similar to what is implemented for TemplateName. There is still one free bit available, and this handle can be used within a PointerUnion and PointerIntPair, which should keep bit-packing aficionados happy. * The ElaboratedType node is removed, all type nodes in which it could previously apply to can now store the elaborated keyword and name qualifier, tail allocating when present. * TagTypes can now point to the exact declaration found when producing these, as opposed to the previous situation of there only existing one TagType per entity. This increases the amount of type sugar retained, and can have several applications, for example in tracking module ownership, and other tools which care about source file origins, such as IWYU. These TagTypes are lazily allocated, in order to limit the increase in AST size. This patch offers a great performance benefit. It greatly improves compilation time for [stdexec](https://github.com/NVIDIA/stdexec). For one datapoint, for `test_on2.cpp` in that project, which is the slowest compiling test, this patch improves `-c` compilation time by about 7.2%, with the `-fsyntax-only` improvement being at ~12%. This has great results on compile-time-tracker as well:  This patch also further enables other optimziations in the future, and will reduce the performance impact of template specialization resugaring when that lands. It has some other miscelaneous drive-by fixes. About the review: Yes the patch is huge, sorry about that. Part of the reason is that I started by the nested name specifier part, before the ElaboratedType part, but that had a huge performance downside, as ElaboratedType is a big performance hog. I didn't have the steam to go back and change the patch after the fact. There is also a lot of internal API changes, and it made sense to remove ElaboratedType in one go, versus removing it from one type at a time, as that would present much more churn to the users. Also, the nested name specifier having a different API avoids missing changes related to how prefixes work now, which could make existing code compile but not work. How to review: The important changes are all in `clang/include/clang/AST` and `clang/lib/AST`, with also important changes in `clang/lib/Sema/TreeTransform.h`. The rest and bulk of the changes are mostly consequences of the changes in API. PS: TagType::getDecl is renamed to `getOriginalDecl` in this patch, just for easier to rebasing. I plan to rename it back after this lands. Fixes #136624 Fixes https://github.com/llvm/llvm-project/issues/43179 Fixes https://github.com/llvm/llvm-project/issues/68670 Fixes https://github.com/llvm/llvm-project/issues/92757
3692 lines
117 KiB
C++
3692 lines
117 KiB
C++
//===--- 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 "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 <type_traits>
|
|
|
|
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 Block *B, 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 DiagnoseUninitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
|
|
AccessKinds AK);
|
|
bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern,
|
|
const Descriptor *Desc, AccessKinds AK);
|
|
|
|
/// Checks a direct load of a primitive value from a global or local variable.
|
|
bool CheckGlobalLoad(InterpState &S, CodePtr OpPC, const Block *B);
|
|
bool CheckLocalLoad(InterpState &S, CodePtr OpPC, const Block *B);
|
|
|
|
/// 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 <typename T>
|
|
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 <ShiftDir Dir, 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();
|
|
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<unsigned>(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 <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));
|
|
if constexpr (std::is_same_v<T, Floating>) {
|
|
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<T, FixedPoint>) {
|
|
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 <typename SizeT>
|
|
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<int64_t> 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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Ret(InterpState &S, CodePtr &PC) {
|
|
const T &Ret = S.Stk.pop<T>();
|
|
|
|
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<T>(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 <typename T, bool (*OpFW)(T, T, unsigned, T *),
|
|
template <typename U> 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<T>())
|
|
Result = S.allocAP<T>(LHS.bitWidth());
|
|
|
|
if (!OpFW(LHS, RHS, Bits, &Result)) {
|
|
S.Stk.push<T>(Result);
|
|
return true;
|
|
}
|
|
// If for some reason evaluation continues, use the truncated results.
|
|
S.Stk.push<T>(Result);
|
|
|
|
// Short-circuit fixed-points here since the error handling is easier.
|
|
if constexpr (std::is_same_v<T, FixedPoint>)
|
|
return handleFixedPointOverflow(S, OpPC, Result);
|
|
|
|
// Slow path - compute the result using another bit of precision.
|
|
APSInt Value = OpAP<APSInt>()(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<T>();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Add(InterpState &S, CodePtr OpPC) {
|
|
const T &RHS = S.Stk.pop<T>();
|
|
const T &LHS = S.Stk.pop<T>();
|
|
const unsigned Bits = RHS.bitWidth() + 1;
|
|
|
|
return AddSubMulHelper<T, T::add, std::plus>(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<Floating>();
|
|
const Floating &LHS = S.Stk.pop<Floating>();
|
|
|
|
FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI);
|
|
Floating Result = S.allocFloat(LHS.getSemantics());
|
|
auto Status = Floating::add(LHS, RHS, getRoundingMode(FPO), &Result);
|
|
S.Stk.push<Floating>(Result);
|
|
return CheckFloatResult(S, OpPC, Result, Status, FPO);
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Sub(InterpState &S, CodePtr OpPC) {
|
|
const T &RHS = S.Stk.pop<T>();
|
|
const T &LHS = S.Stk.pop<T>();
|
|
const unsigned Bits = RHS.bitWidth() + 1;
|
|
|
|
return AddSubMulHelper<T, T::sub, std::minus>(S, OpPC, Bits, LHS, RHS);
|
|
}
|
|
|
|
inline bool Subf(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
const Floating &RHS = S.Stk.pop<Floating>();
|
|
const Floating &LHS = S.Stk.pop<Floating>();
|
|
|
|
FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI);
|
|
Floating Result = S.allocFloat(LHS.getSemantics());
|
|
auto Status = Floating::sub(LHS, RHS, getRoundingMode(FPO), &Result);
|
|
S.Stk.push<Floating>(Result);
|
|
return CheckFloatResult(S, OpPC, Result, Status, FPO);
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Mul(InterpState &S, CodePtr OpPC) {
|
|
const T &RHS = S.Stk.pop<T>();
|
|
const T &LHS = S.Stk.pop<T>();
|
|
const unsigned Bits = RHS.bitWidth() * 2;
|
|
|
|
return AddSubMulHelper<T, T::mul, std::multiplies>(S, OpPC, Bits, LHS, RHS);
|
|
}
|
|
|
|
inline bool Mulf(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
const Floating &RHS = S.Stk.pop<Floating>();
|
|
const Floating &LHS = S.Stk.pop<Floating>();
|
|
|
|
FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI);
|
|
Floating Result = S.allocFloat(LHS.getSemantics());
|
|
|
|
auto Status = Floating::mul(LHS, RHS, getRoundingMode(FPO), &Result);
|
|
|
|
S.Stk.push<Floating>(Result);
|
|
return CheckFloatResult(S, OpPC, Result, Status, FPO);
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool Mulc(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &RHS = S.Stk.pop<Pointer>();
|
|
const Pointer &LHS = S.Stk.pop<Pointer>();
|
|
const Pointer &Result = S.Stk.peek<Pointer>();
|
|
|
|
if constexpr (std::is_same_v<T, Floating>) {
|
|
APFloat A = LHS.elem<Floating>(0).getAPFloat();
|
|
APFloat B = LHS.elem<Floating>(1).getAPFloat();
|
|
APFloat C = RHS.elem<Floating>(0).getAPFloat();
|
|
APFloat D = RHS.elem<Floating>(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<Floating>(0) = RA; // Floating(ResR);
|
|
|
|
Floating RI = S.allocFloat(A.getSemantics());
|
|
RI.copy(ResI);
|
|
Result.elem<Floating>(1) = RI; // Floating(ResI);
|
|
Result.initializeAllElements();
|
|
} else {
|
|
// Integer element type.
|
|
const T &LHSR = LHS.elem<T>(0);
|
|
const T &LHSI = LHS.elem<T>(1);
|
|
const T &RHSR = RHS.elem<T>(0);
|
|
const T &RHSI = RHS.elem<T>(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<T>(0)))
|
|
return false;
|
|
|
|
// 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<T>(1)))
|
|
return false;
|
|
Result.initialize();
|
|
Result.initializeAllElements();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool Divc(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &RHS = S.Stk.pop<Pointer>();
|
|
const Pointer &LHS = S.Stk.pop<Pointer>();
|
|
const Pointer &Result = S.Stk.peek<Pointer>();
|
|
|
|
if constexpr (std::is_same_v<T, Floating>) {
|
|
APFloat A = LHS.elem<Floating>(0).getAPFloat();
|
|
APFloat B = LHS.elem<Floating>(1).getAPFloat();
|
|
APFloat C = RHS.elem<Floating>(0).getAPFloat();
|
|
APFloat D = RHS.elem<Floating>(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<Floating>(0) = RA; // Floating(ResR);
|
|
|
|
Floating RI = S.allocFloat(A.getSemantics());
|
|
RI.copy(ResI);
|
|
Result.elem<Floating>(1) = RI; // Floating(ResI);
|
|
|
|
Result.initializeAllElements();
|
|
} else {
|
|
// Integer element type.
|
|
const T &LHSR = LHS.elem<T>(0);
|
|
const T &LHSI = LHS.elem<T>(1);
|
|
const T &RHSR = RHS.elem<T>(0);
|
|
const T &RHSI = RHS.elem<T>(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<T>(0);
|
|
T &ResultI = Result.elem<T>(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;
|
|
|
|
// 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.initializeAllElements();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// 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 constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(Bits);
|
|
|
|
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 constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(Bits);
|
|
|
|
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 constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(Bits);
|
|
|
|
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>();
|
|
const unsigned Bits = RHS.bitWidth() * 2;
|
|
|
|
if (!CheckDivRem(S, OpPC, LHS, RHS))
|
|
return false;
|
|
|
|
T Result;
|
|
if constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(LHS.bitWidth());
|
|
|
|
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>();
|
|
const unsigned Bits = RHS.bitWidth() * 2;
|
|
|
|
if (!CheckDivRem(S, OpPC, LHS, RHS))
|
|
return false;
|
|
|
|
T Result;
|
|
if constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(LHS.bitWidth());
|
|
|
|
if (!T::div(LHS, RHS, Bits, &Result)) {
|
|
S.Stk.push<T>(Result);
|
|
return true;
|
|
}
|
|
|
|
if constexpr (std::is_same_v<T, FixedPoint>) {
|
|
if (handleFixedPointOverflow(S, OpPC, Result)) {
|
|
S.Stk.push<T>(Result);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool Divf(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
const Floating &RHS = S.Stk.pop<Floating>();
|
|
const Floating &LHS = S.Stk.pop<Floating>();
|
|
|
|
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<Floating>(Result);
|
|
return CheckFloatResult(S, OpPC, Result, Status, FPO);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Inv
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
inline bool Inv(InterpState &S, CodePtr OpPC) {
|
|
const auto &Val = S.Stk.pop<Boolean>();
|
|
S.Stk.push<Boolean>(!Val);
|
|
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>();
|
|
|
|
if constexpr (std::is_same_v<T, Floating>) {
|
|
T Result = S.allocFloat(Value.getSemantics());
|
|
|
|
if (!T::neg(Value, &Result)) {
|
|
S.Stk.push<T>(Result);
|
|
return true;
|
|
}
|
|
return false;
|
|
} else {
|
|
T Result;
|
|
if constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(Value.bitWidth());
|
|
|
|
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);
|
|
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 <typename T, IncDecOp Op, PushVal DoPush>
|
|
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<T, Boolean>) {
|
|
if (!S.getLangOpts().CPlusPlus14)
|
|
return Invalid(S, OpPC);
|
|
}
|
|
|
|
const T &Value = Ptr.deref<T>();
|
|
T Result;
|
|
if constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(Value.bitWidth());
|
|
|
|
if constexpr (DoPush == PushVal::Yes)
|
|
S.Stk.push<T>(Value);
|
|
|
|
if constexpr (Op == IncDecOp::Inc) {
|
|
if (!T::increment(Value, &Result) || !CanOverflow) {
|
|
Ptr.deref<T>() = Result;
|
|
return true;
|
|
}
|
|
} else {
|
|
if (!T::decrement(Value, &Result) || !CanOverflow) {
|
|
Ptr.deref<T>() = 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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Inc(InterpState &S, CodePtr OpPC, bool CanOverflow) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Increment))
|
|
return false;
|
|
|
|
return IncDecHelper<T, IncDecOp::Inc, PushVal::Yes>(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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool IncPop(InterpState &S, CodePtr OpPC, bool CanOverflow) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Increment))
|
|
return false;
|
|
|
|
return IncDecHelper<T, IncDecOp::Inc, PushVal::No>(S, OpPC, Ptr, CanOverflow);
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool PreInc(InterpState &S, CodePtr OpPC, bool CanOverflow) {
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Increment))
|
|
return false;
|
|
|
|
return IncDecHelper<T, IncDecOp::Inc, PushVal::No>(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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Dec(InterpState &S, CodePtr OpPC, bool CanOverflow) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Decrement))
|
|
return false;
|
|
|
|
return IncDecHelper<T, IncDecOp::Dec, PushVal::Yes>(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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool DecPop(InterpState &S, CodePtr OpPC, bool CanOverflow) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Decrement))
|
|
return false;
|
|
|
|
return IncDecHelper<T, IncDecOp::Dec, PushVal::No>(S, OpPC, Ptr, CanOverflow);
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool PreDec(InterpState &S, CodePtr OpPC, bool CanOverflow) {
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Decrement))
|
|
return false;
|
|
return IncDecHelper<T, IncDecOp::Dec, PushVal::No>(S, OpPC, Ptr, CanOverflow);
|
|
}
|
|
|
|
template <IncDecOp Op, PushVal DoPush>
|
|
bool IncDecFloatHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
|
|
uint32_t FPOI) {
|
|
Floating Value = Ptr.deref<Floating>();
|
|
Floating Result = S.allocFloat(Value.getSemantics());
|
|
|
|
if constexpr (DoPush == PushVal::Yes)
|
|
S.Stk.push<Floating>(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<Floating>() = Result;
|
|
|
|
return CheckFloatResult(S, OpPC, Result, Status, FPO);
|
|
}
|
|
|
|
inline bool Incf(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Increment))
|
|
return false;
|
|
|
|
return IncDecFloatHelper<IncDecOp::Inc, PushVal::Yes>(S, OpPC, Ptr, FPOI);
|
|
}
|
|
|
|
inline bool IncfPop(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Increment))
|
|
return false;
|
|
|
|
return IncDecFloatHelper<IncDecOp::Inc, PushVal::No>(S, OpPC, Ptr, FPOI);
|
|
}
|
|
|
|
inline bool Decf(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Decrement))
|
|
return false;
|
|
|
|
return IncDecFloatHelper<IncDecOp::Dec, PushVal::Yes>(S, OpPC, Ptr, FPOI);
|
|
}
|
|
|
|
inline bool DecfPop(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr, AK_Decrement))
|
|
return false;
|
|
|
|
return IncDecFloatHelper<IncDecOp::Dec, PushVal::No>(S, OpPC, Ptr, FPOI);
|
|
}
|
|
|
|
/// 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 constexpr (needsAlloc<T>())
|
|
Result = S.allocAP<T>(Val.bitWidth());
|
|
|
|
if (!T::comp(Val, &Result)) {
|
|
S.Stk.push<T>(Result);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// EQ, NE, GT, GE, LT, LE
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
using CompareFn = llvm::function_ref<bool(ComparisonCategoryResult)>;
|
|
|
|
template <typename T>
|
|
bool CmpHelper(InterpState &S, CodePtr OpPC, CompareFn Fn) {
|
|
assert((!std::is_same_v<T, MemberPointer>) &&
|
|
"Non-equality comparisons on member pointer types should already be "
|
|
"rejected in Sema.");
|
|
using BoolT = PrimConv<PT_Bool>::T;
|
|
const T &RHS = S.Stk.pop<T>();
|
|
const T &LHS = S.Stk.pop<T>();
|
|
S.Stk.push<BoolT>(BoolT::from(Fn(LHS.compare(RHS))));
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) {
|
|
return CmpHelper<T>(S, OpPC, Fn);
|
|
}
|
|
|
|
template <>
|
|
inline bool CmpHelper<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
|
|
using BoolT = PrimConv<PT_Bool>::T;
|
|
const Pointer &RHS = S.Stk.pop<Pointer>();
|
|
const Pointer &LHS = S.Stk.pop<Pointer>();
|
|
|
|
// 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<std::pair<Pointer, Pointer>> 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>(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<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
|
|
using BoolT = PrimConv<PT_Bool>::T;
|
|
const Pointer &RHS = S.Stk.pop<Pointer>();
|
|
const Pointer &LHS = S.Stk.pop<Pointer>();
|
|
|
|
if (LHS.isZero() && RHS.isZero()) {
|
|
S.Stk.push<BoolT>(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>(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>(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;
|
|
}
|
|
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<StringLiteral>(E)) {
|
|
const SourceInfo &Loc = S.Current->getSource(OpPC);
|
|
S.FFDiag(Loc, diag::note_constexpr_literal_comparison);
|
|
return false;
|
|
}
|
|
if (const auto *CE = dyn_cast<CallExpr>(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>(BoolT::from(Fn(ComparisonCategoryResult::Unordered)));
|
|
return true;
|
|
}
|
|
|
|
template <>
|
|
inline bool CmpHelperEQ<MemberPointer>(InterpState &S, CodePtr OpPC,
|
|
CompareFn Fn) {
|
|
const auto &RHS = S.Stk.pop<MemberPointer>();
|
|
const auto &LHS = S.Stk.pop<MemberPointer>();
|
|
|
|
// 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<Boolean>(Fn(ComparisonCategoryResult::Equal));
|
|
return true;
|
|
}
|
|
if (LHS.isZero() || RHS.isZero()) {
|
|
S.Stk.push<Boolean>(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>(Boolean::from(Fn(LHS.compare(RHS))));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool EQ(InterpState &S, CodePtr OpPC) {
|
|
return CmpHelperEQ<T>(S, OpPC, [](ComparisonCategoryResult R) {
|
|
return R == ComparisonCategoryResult::Equal;
|
|
});
|
|
}
|
|
|
|
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 constexpr (std::is_same_v<T, Pointer>) {
|
|
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 <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;
|
|
});
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool LT(InterpState &S, CodePtr OpPC) {
|
|
return CmpHelper<T>(S, OpPC, [](ComparisonCategoryResult R) {
|
|
return R == ComparisonCategoryResult::Less;
|
|
});
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool LE(InterpState &S, CodePtr OpPC) {
|
|
return CmpHelper<T>(S, OpPC, [](ComparisonCategoryResult R) {
|
|
return R == ComparisonCategoryResult::Less ||
|
|
R == ComparisonCategoryResult::Equal;
|
|
});
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool GT(InterpState &S, CodePtr OpPC) {
|
|
return CmpHelper<T>(S, OpPC, [](ComparisonCategoryResult R) {
|
|
return R == ComparisonCategoryResult::Greater;
|
|
});
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool GE(InterpState &S, CodePtr OpPC) {
|
|
return CmpHelper<T>(S, OpPC, [](ComparisonCategoryResult R) {
|
|
return R == ComparisonCategoryResult::Greater ||
|
|
R == ComparisonCategoryResult::Equal;
|
|
});
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Dup, Pop, Test
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Dup(InterpState &S, CodePtr OpPC) {
|
|
S.Stk.push<T>(S.Stk.peek<T>());
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Pop(InterpState &S, CodePtr OpPC) {
|
|
S.Stk.discard<T>();
|
|
return true;
|
|
}
|
|
|
|
/// [Value1, Value2] -> [Value2, Value1]
|
|
template <PrimType TopName, PrimType BottomName>
|
|
bool Flip(InterpState &S, CodePtr OpPC) {
|
|
using TopT = typename PrimConv<TopName>::T;
|
|
using BottomT = typename PrimConv<BottomName>::T;
|
|
|
|
const auto &Top = S.Stk.pop<TopT>();
|
|
const auto &Bottom = S.Stk.pop<BottomT>();
|
|
|
|
S.Stk.push<TopT>(Top);
|
|
S.Stk.push<BottomT>(Bottom);
|
|
|
|
return true;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Const
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Const(InterpState &S, CodePtr OpPC, const T &Arg) {
|
|
if constexpr (needsAlloc<T>()) {
|
|
T Result = S.allocAP<T>(Arg.bitWidth());
|
|
Result.copy(Arg.toAPSInt());
|
|
S.Stk.push<T>(Result);
|
|
return true;
|
|
}
|
|
S.Stk.push<T>(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<Floating>(Result);
|
|
return true;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Get/Set Local/Param/Global/This
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
const Block *B = S.Current->getLocalBlock(I);
|
|
if (!CheckLocalLoad(S, OpPC, B))
|
|
return false;
|
|
S.Stk.push<T>(B->deref<T>());
|
|
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 <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>());
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool GetParam(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
if (S.checkingPotentialConstantExpression()) {
|
|
return false;
|
|
}
|
|
S.Stk.push<T>(S.Current->getParam<T>(I));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool SetParam(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
S.Current->setParam<T>(I, S.Stk.pop<T>());
|
|
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>();
|
|
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<T>(Field.deref<T>());
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool SetField(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Obj = S.Stk.peek<Pointer>();
|
|
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<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>();
|
|
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<T>(Field.deref<T>());
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::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<T>(Field.deref<T>());
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool SetThisField(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
if (S.checkingPotentialConstantExpression())
|
|
return false;
|
|
const T &Value = S.Stk.pop<T>();
|
|
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<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool GetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
const Block *B = S.P.getGlobal(I);
|
|
|
|
if (!CheckGlobalLoad(S, OpPC, B))
|
|
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) {
|
|
const Block *B = S.P.getGlobal(I);
|
|
const auto &Desc =
|
|
*reinterpret_cast<const GlobalInlineDescriptor *>(B->rawData());
|
|
if (Desc.InitState != GlobalInitState::Initialized)
|
|
return DiagnoseUninitialized(S, OpPC, B->isExtern(), B->getDescriptor(),
|
|
AK_Read);
|
|
|
|
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.
|
|
return false;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool InitGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
const Pointer &P = S.P.getGlobal(I);
|
|
|
|
P.deref<T>() = S.Stk.pop<T>();
|
|
|
|
if constexpr (std::is_same_v<T, Floating>) {
|
|
auto &Val = P.deref<Floating>();
|
|
if (!Val.singleWord()) {
|
|
uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
|
|
Val.take(NewMemory);
|
|
}
|
|
|
|
} else if constexpr (needsAlloc<T>()) {
|
|
auto &Val = P.deref<T>();
|
|
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 <PrimType Name, class T = typename PrimConv<Name>::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<T>();
|
|
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<T>() = S.Stk.pop<T>();
|
|
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<Pointer>();
|
|
APValue *Cached = Temp->getOrCreateValue(true);
|
|
|
|
S.SeenGlobalTemporaries.push_back(
|
|
std::make_pair(P.getDeclDesc()->asExpr(), Temp));
|
|
|
|
if (std::optional<APValue> APV =
|
|
P.toRValue(S.getASTContext(), Temp->getTemporaryExpr()->getType())) {
|
|
*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() && 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<T>() = S.Stk.pop<T>();
|
|
Field.initialize();
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::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<T>() = S.Stk.pop<T>();
|
|
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 <PrimType Name, class T = typename PrimConv<Name>::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<T>();
|
|
Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue());
|
|
Field.initialize();
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::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<T>();
|
|
Field.deref<T>() = 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 <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 &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckRange(S, OpPC, Ptr, CSK_Field))
|
|
return false;
|
|
const Pointer &Field = Ptr.atField(I);
|
|
Field.deref<T>() = Value;
|
|
Field.initialize();
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool InitFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckRange(S, OpPC, Ptr, CSK_Field))
|
|
return false;
|
|
const Pointer &Field = Ptr.atField(I);
|
|
Field.deref<T>() = Value;
|
|
Field.activate();
|
|
Field.initialize();
|
|
return true;
|
|
}
|
|
|
|
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.peek<Pointer>().atField(F->Offset);
|
|
|
|
if constexpr (needsAlloc<T>()) {
|
|
T Result = S.allocAP<T>(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<T>() = Result;
|
|
} else {
|
|
Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue());
|
|
}
|
|
Field.initialize();
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool InitBitFieldActivate(InterpState &S, CodePtr OpPC,
|
|
const Record::Field *F) {
|
|
assert(F->isBitField());
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Field = S.Stk.peek<Pointer>().atField(F->Offset);
|
|
|
|
if constexpr (needsAlloc<T>()) {
|
|
T Result = S.allocAP<T>(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<T>() = Result;
|
|
} else {
|
|
Field.deref<T>() = 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<Pointer>(S.Current->getLocalPointer(I));
|
|
return true;
|
|
}
|
|
|
|
inline bool GetPtrParam(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
if (S.checkingPotentialConstantExpression()) {
|
|
return false;
|
|
}
|
|
S.Stk.push<Pointer>(S.Current->getParamPointer(I));
|
|
return true;
|
|
}
|
|
|
|
inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
|
|
S.Stk.push<Pointer>(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<Pointer>(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<Pointer>();
|
|
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<Pointer>(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()->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<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 (!Ptr.isBlockPointer()) {
|
|
S.Stk.push<Pointer>(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<Pointer>(Result);
|
|
return true;
|
|
}
|
|
|
|
inline bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t Off,
|
|
bool NullOK) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Base))
|
|
return false;
|
|
|
|
if (!Ptr.isBlockPointer()) {
|
|
S.Stk.push<Pointer>(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<Pointer>(Result);
|
|
return true;
|
|
}
|
|
|
|
inline bool GetMemberPtrBasePop(InterpState &S, CodePtr OpPC, int32_t Off) {
|
|
const auto &Ptr = S.Stk.pop<MemberPointer>();
|
|
S.Stk.push<MemberPointer>(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<Pointer>(This.atField(Off));
|
|
return true;
|
|
}
|
|
|
|
inline bool FinishInitPop(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (Ptr.canBeInitialized())
|
|
Ptr.initialize();
|
|
return true;
|
|
}
|
|
|
|
inline bool FinishInit(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (Ptr.canBeInitialized())
|
|
Ptr.initialize();
|
|
return true;
|
|
}
|
|
|
|
inline bool FinishInitActivate(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (Ptr.canBeInitialized()) {
|
|
Ptr.initialize();
|
|
Ptr.activate();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool FinishInitActivatePop(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
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<Pointer>();
|
|
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<Pointer>(Base.atField(VirtBase->Offset));
|
|
return true;
|
|
}
|
|
|
|
inline bool GetPtrVirtBasePop(InterpState &S, CodePtr OpPC,
|
|
const RecordDecl *D) {
|
|
assert(D);
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Load(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr))
|
|
return false;
|
|
if (!Ptr.isBlockPointer())
|
|
return false;
|
|
S.Stk.push<T>(Ptr.deref<T>());
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool LoadPop(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckLoad(S, OpPC, Ptr))
|
|
return false;
|
|
if (!Ptr.isBlockPointer())
|
|
return false;
|
|
S.Stk.push<T>(Ptr.deref<T>());
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Store(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
if (Ptr.canBeInitialized())
|
|
Ptr.initialize();
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool StorePop(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
if (Ptr.canBeInitialized())
|
|
Ptr.initialize();
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
static inline bool Activate(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool StoreActivate(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
|
|
if (Ptr.canBeInitialized()) {
|
|
Ptr.initialize();
|
|
Ptr.activate();
|
|
}
|
|
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool StoreActivatePop(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (Ptr.canBeInitialized()) {
|
|
Ptr.initialize();
|
|
Ptr.activate();
|
|
}
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool StoreBitField(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
if (Ptr.canBeInitialized())
|
|
Ptr.initialize();
|
|
if (const auto *FD = Ptr.getField())
|
|
Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue());
|
|
else
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
if (Ptr.canBeInitialized())
|
|
Ptr.initialize();
|
|
if (const auto *FD = Ptr.getField())
|
|
Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue());
|
|
else
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool StoreBitFieldActivate(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (Ptr.canBeInitialized()) {
|
|
Ptr.initialize();
|
|
Ptr.activate();
|
|
}
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
if (const auto *FD = Ptr.getField())
|
|
Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue());
|
|
else
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool StoreBitFieldActivatePop(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (Ptr.canBeInitialized()) {
|
|
Ptr.initialize();
|
|
Ptr.activate();
|
|
}
|
|
if (!CheckStore(S, OpPC, Ptr))
|
|
return false;
|
|
if (const auto *FD = Ptr.getField())
|
|
Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue());
|
|
else
|
|
Ptr.deref<T>() = Value;
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Init(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
if (!CheckInit(S, OpPC, Ptr))
|
|
return false;
|
|
Ptr.initialize();
|
|
new (&Ptr.deref<T>()) T(Value);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool InitPop(InterpState &S, CodePtr OpPC) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (!CheckInit(S, OpPC, Ptr))
|
|
return false;
|
|
Ptr.initialize();
|
|
new (&Ptr.deref<T>()) 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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) {
|
|
const T &Value = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
|
|
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>()) T(Value);
|
|
return true;
|
|
}
|
|
|
|
const Pointer &ElemPtr = Ptr.atIndex(Idx);
|
|
if (!CheckInit(S, OpPC, ElemPtr))
|
|
return false;
|
|
ElemPtr.initialize();
|
|
new (&ElemPtr.deref<T>()) T(Value);
|
|
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>();
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
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>()) T(Value);
|
|
return true;
|
|
}
|
|
|
|
const Pointer &ElemPtr = Ptr.atIndex(Idx);
|
|
if (!CheckInit(S, OpPC, ElemPtr))
|
|
return false;
|
|
ElemPtr.initialize();
|
|
new (&ElemPtr.deref<T>()) T(Value);
|
|
return true;
|
|
}
|
|
|
|
inline bool Memcpy(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Src = S.Stk.pop<Pointer>();
|
|
Pointer &Dest = S.Stk.peek<Pointer>();
|
|
|
|
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<MemberPointer>();
|
|
const auto &Base = S.Stk.pop<Pointer>();
|
|
|
|
S.Stk.push<MemberPointer>(Member.takeInstance(Base));
|
|
return true;
|
|
}
|
|
|
|
inline bool CastMemberPtrPtr(InterpState &S, CodePtr OpPC) {
|
|
const auto &MP = S.Stk.pop<MemberPointer>();
|
|
|
|
if (std::optional<Pointer> Ptr = MP.toPointer(S.Ctx)) {
|
|
S.Stk.push<Pointer>(*Ptr);
|
|
return true;
|
|
}
|
|
return Invalid(S, OpPC);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// AddOffset, SubOffset
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <class T, ArithOp Op>
|
|
std::optional<Pointer> 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<uint64_t>(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<uint64_t>(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<uint64_t>(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<int>(!Ptr.inArray()) << MaxIndex;
|
|
Invalid = true;
|
|
};
|
|
|
|
if (Ptr.isBlockPointer()) {
|
|
uint64_t IOffset = static_cast<uint64_t>(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<int64_t>(Index);
|
|
int64_t WideOffset = static_cast<int64_t>(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<uint64_t>(Result));
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool AddOffset(InterpState &S, CodePtr OpPC) {
|
|
const T &Offset = S.Stk.pop<T>();
|
|
Pointer Ptr = S.Stk.pop<Pointer>();
|
|
if (Ptr.isBlockPointer())
|
|
Ptr = Ptr.expand();
|
|
|
|
if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Add>(
|
|
S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
|
|
S.Stk.push<Pointer>(*Result);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool SubOffset(InterpState &S, CodePtr OpPC) {
|
|
const T &Offset = S.Stk.pop<T>();
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Sub>(
|
|
S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
|
|
S.Stk.push<Pointer>(*Result);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <ArithOp Op>
|
|
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<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 (std::optional<Pointer> Result =
|
|
OffsetHelper<OneT, Op>(S, OpPC, One, P, /*IsPointerArith=*/true)) {
|
|
// Store the new value.
|
|
Ptr.deref<Pointer>() = *Result;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline bool IncPtr(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (!Ptr.isInitialized())
|
|
return DiagnoseUninitialized(S, OpPC, Ptr, AK_Increment);
|
|
|
|
return IncDecPtrHelper<ArithOp::Add>(S, OpPC, Ptr);
|
|
}
|
|
|
|
static inline bool DecPtr(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (!Ptr.isInitialized())
|
|
return DiagnoseUninitialized(S, OpPC, Ptr, AK_Decrement);
|
|
|
|
return IncDecPtrHelper<ArithOp::Sub>(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 <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) {
|
|
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<T>();
|
|
return true;
|
|
}
|
|
|
|
for (const Pointer &P : {LHS, RHS}) {
|
|
if (P.isZeroSizeArray()) {
|
|
QualType PtrT = P.getType();
|
|
while (auto *AT = dyn_cast<ArrayType>(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<int64_t>(T::from(R64)) != R64)
|
|
return handleOverflow(S, OpPC, R64);
|
|
|
|
S.Stk.push<T>(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<NamedDecl>(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 <PrimType TIn, PrimType TOut> bool Cast(InterpState &S, CodePtr OpPC) {
|
|
using T = typename PrimConv<TIn>::T;
|
|
using U = typename PrimConv<TOut>::T;
|
|
S.Stk.push<U>(U::from(S.Stk.pop<T>()));
|
|
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 = S.allocFloat(*Sem);
|
|
F.toSemantics(Sem, RM, &Result);
|
|
S.Stk.push<Floating>(Result);
|
|
return true;
|
|
}
|
|
|
|
inline bool CastFixedPoint(InterpState &S, CodePtr OpPC, uint32_t FPS) {
|
|
FixedPointSemantics TargetSemantics =
|
|
FixedPointSemantics::getFromOpaqueInt(FPS);
|
|
const auto &Source = S.Stk.pop<FixedPoint>();
|
|
|
|
bool Overflow;
|
|
FixedPoint Result = Source.toSemantics(TargetSemantics, &Overflow);
|
|
|
|
if (Overflow && !handleFixedPointOverflow(S, OpPC, Result))
|
|
return false;
|
|
|
|
S.Stk.push<FixedPoint>(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) {
|
|
auto Result = S.allocAP<IntegralAP<false>>(BitWidth);
|
|
// Copy data.
|
|
{
|
|
APInt Source = S.Stk.pop<T>().toAPSInt().extOrTrunc(BitWidth);
|
|
Result.copy(Source);
|
|
}
|
|
S.Stk.push<IntegralAP<false>>(Result);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool CastAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
|
|
auto Result = S.allocAP<IntegralAP<true>>(BitWidth);
|
|
// Copy data.
|
|
{
|
|
APInt Source = S.Stk.pop<T>().toAPSInt().extOrTrunc(BitWidth);
|
|
Result.copy(Source);
|
|
}
|
|
S.Stk.push<IntegralAP<true>>(Result);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool CastIntegralFloating(InterpState &S, CodePtr OpPC,
|
|
const llvm::fltSemantics *Sem, uint32_t FPOI) {
|
|
const T &From = S.Stk.pop<T>();
|
|
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<Floating>(Result);
|
|
|
|
return CheckFloatResult(S, OpPC, Result, Status, FPO);
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool CastFloatingIntegral(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
|
|
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)) {
|
|
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;
|
|
}
|
|
|
|
FPOptions FPO = FPOptions::getFromOpaqueInt(FPOI);
|
|
S.Stk.push<T>(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<Floating>();
|
|
|
|
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<IntegralAP<false>>(BitWidth);
|
|
ResultAP.copy(Result);
|
|
|
|
S.Stk.push<IntegralAP<false>>(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<Floating>();
|
|
|
|
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<IntegralAP<true>>(BitWidth);
|
|
ResultAP.copy(Result);
|
|
|
|
S.Stk.push<IntegralAP<true>>(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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool CastPointerIntegral(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
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>(T::from(Ptr.getIntegerRepresentation()));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
static inline bool CastIntegralFixedPoint(InterpState &S, CodePtr OpPC,
|
|
uint32_t FPS) {
|
|
const T &Int = S.Stk.pop<T>();
|
|
|
|
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<FixedPoint>(Result);
|
|
return true;
|
|
}
|
|
|
|
static inline bool CastFloatingFixedPoint(InterpState &S, CodePtr OpPC,
|
|
uint32_t FPS) {
|
|
const auto &Float = S.Stk.pop<Floating>();
|
|
|
|
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<FixedPoint>(Result);
|
|
return true;
|
|
}
|
|
|
|
static inline bool CastFixedPointFloating(InterpState &S, CodePtr OpPC,
|
|
const llvm::fltSemantics *Sem) {
|
|
const auto &Fixed = S.Stk.pop<FixedPoint>();
|
|
Floating Result = S.allocFloat(*Sem);
|
|
Result.copy(Fixed.toFloat(Sem));
|
|
S.Stk.push<Floating>(Result);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
static inline bool CastFixedPointIntegral(InterpState &S, CodePtr OpPC) {
|
|
const auto &Fixed = S.Stk.pop<FixedPoint>();
|
|
|
|
bool Overflow;
|
|
APSInt Int = Fixed.toInt(T::bitWidth(), T::isSigned(), &Overflow);
|
|
|
|
if (Overflow && !handleOverflow(S, OpPC, Int))
|
|
return false;
|
|
|
|
S.Stk.push<T>(Int);
|
|
return true;
|
|
}
|
|
|
|
static inline bool FnPtrCast(InterpState &S, CodePtr OpPC) {
|
|
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;
|
|
}
|
|
|
|
static inline bool PtrPtrCast(InterpState &S, CodePtr OpPC, bool SrcIsVoidPtr) {
|
|
const auto &Ptr = S.Stk.peek<Pointer>();
|
|
|
|
if (SrcIsVoidPtr && S.getLangOpts().CPlusPlus) {
|
|
bool HasValidResult = !Ptr.isZero();
|
|
|
|
if (HasValidResult) {
|
|
if (S.getStdAllocatorCaller("allocate"))
|
|
return true;
|
|
|
|
const auto &E = cast<CastExpr>(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 <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool Zero(InterpState &S, CodePtr OpPC) {
|
|
S.Stk.push<T>(T::zero());
|
|
return true;
|
|
}
|
|
|
|
static inline bool ZeroIntAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
|
|
auto Result = S.allocAP<IntegralAP<false>>(BitWidth);
|
|
if (!Result.singleWord())
|
|
std::memset(Result.Memory, 0, Result.numWords() * sizeof(uint64_t));
|
|
S.Stk.push<IntegralAP<false>>(Result);
|
|
return true;
|
|
}
|
|
|
|
static inline bool ZeroIntAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
|
|
auto Result = S.allocAP<IntegralAP<true>>(BitWidth);
|
|
if (!Result.singleWord())
|
|
std::memset(Result.Memory, 0, Result.numWords() * sizeof(uint64_t));
|
|
S.Stk.push<IntegralAP<true>>(Result);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::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<T>(Value, Desc);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool IsNonNull(InterpState &S, CodePtr OpPC) {
|
|
const auto &P = S.Stk.pop<T>();
|
|
if (P.isWeak())
|
|
return false;
|
|
S.Stk.push<Boolean>(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<CXXMethodDecl>(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<CXXMethodDecl>(S.Current->getFunction()->getDecl())
|
|
->getParent());
|
|
}
|
|
}
|
|
|
|
S.Stk.push<Pointer>(This);
|
|
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 <class LT, class RT, ShiftDir Dir>
|
|
inline bool DoShift(InterpState &S, CodePtr OpPC, LT &LHS, RT &RHS,
|
|
LT *Result) {
|
|
static_assert(!needsAlloc<LT>());
|
|
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<LT, RT,
|
|
Dir == ShiftDir::Left ? ShiftDir::Right : ShiftDir::Left>(
|
|
S, OpPC, LHS, RHS, Result);
|
|
}
|
|
|
|
if (!CheckShift<Dir>(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>(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>(LT::from(R));
|
|
return true;
|
|
}
|
|
|
|
/// A version of DoShift that works on IntegralAP.
|
|
template <class LT, class RT, ShiftDir Dir>
|
|
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<uint64_t>(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<LT, RT,
|
|
Dir == ShiftDir::Left ? ShiftDir::Right : ShiftDir::Left>(
|
|
S, OpPC, LHS, -RHS, Result);
|
|
}
|
|
|
|
if (!CheckShift<Dir>(S, OpPC, static_cast<LT>(LHS), static_cast<RT>(RHS),
|
|
Bits))
|
|
return false;
|
|
|
|
unsigned SA = (unsigned)RHS.getLimitedValue(Bits - 1);
|
|
if constexpr (Dir == ShiftDir::Left) {
|
|
if constexpr (needsAlloc<LT>())
|
|
Result->copy(LHS << SA);
|
|
else
|
|
*Result = LT(LHS << SA);
|
|
} else {
|
|
if constexpr (needsAlloc<LT>())
|
|
Result->copy(LHS >> SA);
|
|
else
|
|
*Result = LT(LHS >> SA);
|
|
}
|
|
|
|
S.Stk.push<LT>(*Result);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType NameL, PrimType NameR>
|
|
inline bool Shr(InterpState &S, CodePtr OpPC) {
|
|
using LT = typename PrimConv<NameL>::T;
|
|
using RT = typename PrimConv<NameR>::T;
|
|
auto RHS = S.Stk.pop<RT>();
|
|
auto LHS = S.Stk.pop<LT>();
|
|
|
|
if constexpr (needsAlloc<LT>() || needsAlloc<RT>()) {
|
|
LT Result;
|
|
if constexpr (needsAlloc<LT>())
|
|
Result = S.allocAP<LT>(LHS.bitWidth());
|
|
return DoShiftAP<LT, RT, ShiftDir::Right>(S, OpPC, LHS.toAPSInt(),
|
|
RHS.toAPSInt(), &Result);
|
|
} else {
|
|
LT Result;
|
|
return DoShift<LT, RT, ShiftDir::Right>(S, OpPC, LHS, RHS, &Result);
|
|
}
|
|
}
|
|
|
|
template <PrimType NameL, PrimType NameR>
|
|
inline bool Shl(InterpState &S, CodePtr OpPC) {
|
|
using LT = typename PrimConv<NameL>::T;
|
|
using RT = typename PrimConv<NameR>::T;
|
|
auto RHS = S.Stk.pop<RT>();
|
|
auto LHS = S.Stk.pop<LT>();
|
|
|
|
if constexpr (needsAlloc<LT>() || needsAlloc<RT>()) {
|
|
LT Result;
|
|
if constexpr (needsAlloc<LT>())
|
|
Result = S.allocAP<LT>(LHS.bitWidth());
|
|
return DoShiftAP<LT, RT, ShiftDir::Left>(S, OpPC, LHS.toAPSInt(),
|
|
RHS.toAPSInt(), &Result);
|
|
} else {
|
|
LT Result;
|
|
return DoShift<LT, RT, ShiftDir::Left>(S, OpPC, LHS, RHS, &Result);
|
|
}
|
|
}
|
|
|
|
static inline bool ShiftFixedPoint(InterpState &S, CodePtr OpPC, bool Left) {
|
|
const auto &RHS = S.Stk.pop<FixedPoint>();
|
|
const auto &LHS = S.Stk.pop<FixedPoint>();
|
|
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<unsigned>(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<FixedPoint>(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<Pointer>();
|
|
S.Stk.push<Pointer>(Ptr.narrow());
|
|
return true;
|
|
}
|
|
|
|
inline bool ExpandPtr(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
if (Ptr.isBlockPointer())
|
|
S.Stk.push<Pointer>(Ptr.expand());
|
|
else
|
|
S.Stk.push<Pointer>(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 <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 (!Ptr.isZero() && !Offset.isZero()) {
|
|
if (!CheckArray(S, OpPC, Ptr))
|
|
return false;
|
|
}
|
|
|
|
if (Offset.isZero()) {
|
|
if (Ptr.getFieldDesc()->isArray() && Ptr.getIndex() == 0) {
|
|
S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
|
|
return true;
|
|
}
|
|
S.Stk.push<Pointer>(Ptr);
|
|
return true;
|
|
}
|
|
|
|
assert(!Offset.isZero());
|
|
|
|
if (std::optional<Pointer> Result =
|
|
OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr)) {
|
|
S.Stk.push<Pointer>(Result->narrow());
|
|
return true;
|
|
}
|
|
|
|
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 (!Ptr.isZero() && !Offset.isZero()) {
|
|
if (!CheckArray(S, OpPC, Ptr))
|
|
return false;
|
|
}
|
|
|
|
if (Offset.isZero()) {
|
|
if (Ptr.getFieldDesc()->isArray() && Ptr.getIndex() == 0) {
|
|
S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
|
|
return true;
|
|
}
|
|
S.Stk.push<Pointer>(Ptr);
|
|
return true;
|
|
}
|
|
|
|
assert(!Offset.isZero());
|
|
|
|
if (std::optional<Pointer> Result =
|
|
OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr)) {
|
|
S.Stk.push<Pointer>(Result->narrow());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool ArrayElem(InterpState &S, CodePtr OpPC, uint32_t Index) {
|
|
const Pointer &Ptr = S.Stk.peek<Pointer>();
|
|
|
|
if (!CheckLoad(S, OpPC, Ptr))
|
|
return false;
|
|
|
|
assert(Ptr.atIndex(Index).getFieldDesc()->getPrimType() == Name);
|
|
S.Stk.push<T>(Ptr.elem<T>(Index));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool ArrayElemPop(InterpState &S, CodePtr OpPC, uint32_t Index) {
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (!CheckLoad(S, OpPC, Ptr))
|
|
return false;
|
|
|
|
assert(Ptr.atIndex(Index).getFieldDesc()->getPrimType() == Name);
|
|
S.Stk.push<T>(Ptr.elem<T>(Index));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool CopyArray(InterpState &S, CodePtr OpPC, uint32_t SrcIndex,
|
|
uint32_t DestIndex, uint32_t Size) {
|
|
const auto &SrcPtr = S.Stk.pop<Pointer>();
|
|
const auto &DestPtr = S.Stk.peek<Pointer>();
|
|
|
|
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<T>() = SP.deref<T>();
|
|
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<Pointer>();
|
|
|
|
if (Ptr.isZero()) {
|
|
S.Stk.push<Pointer>(Ptr);
|
|
return true;
|
|
}
|
|
|
|
if (!CheckRange(S, OpPC, Ptr, CSK_ArrayToPointer))
|
|
return false;
|
|
|
|
if (Ptr.isRoot() || !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;
|
|
}
|
|
|
|
inline bool GetFnPtr(InterpState &S, CodePtr OpPC, const Function *Func) {
|
|
assert(Func);
|
|
S.Stk.push<Pointer>(Func);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
|
|
const T &IntVal = S.Stk.pop<T>();
|
|
|
|
S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_cast)
|
|
<< diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret
|
|
<< S.getLangOpts().CPlusPlus;
|
|
|
|
S.Stk.push<Pointer>(static_cast<uint64_t>(IntVal), Desc);
|
|
return true;
|
|
}
|
|
|
|
inline bool GetMemberPtr(InterpState &S, CodePtr OpPC, const ValueDecl *D) {
|
|
S.Stk.push<MemberPointer>(D);
|
|
return true;
|
|
}
|
|
|
|
inline bool GetMemberPtrBase(InterpState &S, CodePtr OpPC) {
|
|
const auto &MP = S.Stk.pop<MemberPointer>();
|
|
|
|
if (!MP.isBaseCastPossible())
|
|
return false;
|
|
|
|
S.Stk.push<Pointer>(MP.getBase());
|
|
return true;
|
|
}
|
|
|
|
inline bool GetMemberPtrDecl(InterpState &S, CodePtr OpPC) {
|
|
const auto &MP = S.Stk.pop<MemberPointer>();
|
|
|
|
const auto *FD = cast<FunctionDecl>(MP.getDecl());
|
|
const auto *Func = S.getContext().getOrCreateFunction(FD);
|
|
|
|
S.Stk.push<Pointer>(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<unsigned>(Kind) << S.Current->getRange(OpPC);
|
|
return !Fatal;
|
|
}
|
|
if (Kind == CastKind::Volatile) {
|
|
if (!S.checkingPotentialConstantExpression()) {
|
|
const auto *E = cast<CastExpr>(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;
|
|
}
|
|
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<VarDecl>(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<Boolean>();
|
|
|
|
if (Val)
|
|
return true;
|
|
|
|
// Else, diagnose.
|
|
const SourceLocation &Loc = S.Current->getLocation(OpPC);
|
|
S.CCEDiag(Loc, diag::note_constexpr_assumption_failed);
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool CheckNonNullArg(InterpState &S, CodePtr OpPC) {
|
|
const T &Arg = S.Stk.peek<T>();
|
|
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 <PrimType Name, class T = typename PrimConv<Name>::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<T>().toAPSInt();
|
|
diagnoseEnumValue(S, OpPC, ED, Val);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// OldPtr -> Integer -> NewPtr.
|
|
template <PrimType TIn, PrimType TOut>
|
|
inline bool DecayPtr(InterpState &S, CodePtr OpPC) {
|
|
static_assert(isPtrType(TIn) && isPtrType(TOut));
|
|
using FromT = typename PrimConv<TIn>::T;
|
|
using ToT = typename PrimConv<TOut>::T;
|
|
|
|
const FromT &OldPtr = S.Stk.pop<FromT>();
|
|
|
|
if constexpr (std::is_same_v<FromT, FunctionPointer> &&
|
|
std::is_same_v<ToT, Pointer>) {
|
|
S.Stk.push<Pointer>(OldPtr.getFunction(), OldPtr.getOffset());
|
|
return true;
|
|
} else if constexpr (std::is_same_v<FromT, Pointer> &&
|
|
std::is_same_v<ToT, FunctionPointer>) {
|
|
if (OldPtr.isFunctionPointer()) {
|
|
S.Stk.push<FunctionPointer>(OldPtr.asFunctionPointer().getFunction(),
|
|
OldPtr.getByteOffset());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
S.Stk.push<ToT>(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<Pointer>(B);
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class SizeT = typename PrimConv<Name>::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<SizeT>();
|
|
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<Pointer>(0, nullptr);
|
|
return true;
|
|
}
|
|
assert(NumElements.isPositive());
|
|
|
|
if (!CheckArraySize(S, OpPC, static_cast<uint64_t>(NumElements)))
|
|
return false;
|
|
|
|
DynamicAllocator &Allocator = S.getAllocator();
|
|
Block *B =
|
|
Allocator.allocate(Source, T, static_cast<size_t>(NumElements),
|
|
S.Ctx.getEvalID(), DynamicAllocator::Form::Array);
|
|
assert(B);
|
|
if (NumElements.isZero())
|
|
S.Stk.push<Pointer>(B);
|
|
else
|
|
S.Stk.push<Pointer>(Pointer(B).atIndex(0));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType Name, class SizeT = typename PrimConv<Name>::T>
|
|
inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
|
|
bool IsNoThrow) {
|
|
if (!CheckDynamicMemoryAllocation(S, OpPC))
|
|
return false;
|
|
|
|
SizeT NumElements = S.Stk.pop<SizeT>();
|
|
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<Pointer>(0, ElementDesc);
|
|
return true;
|
|
}
|
|
assert(NumElements.isPositive());
|
|
|
|
if (!CheckArraySize(S, OpPC, static_cast<uint64_t>(NumElements)))
|
|
return false;
|
|
|
|
DynamicAllocator &Allocator = S.getAllocator();
|
|
Block *B =
|
|
Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements),
|
|
S.Ctx.getEvalID(), DynamicAllocator::Form::Array);
|
|
assert(B);
|
|
if (NumElements.isZero())
|
|
S.Stk.push<Pointer>(B);
|
|
else
|
|
S.Stk.push<Pointer>(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>(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<uint64_t> ArraySize = std::nullopt);
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) {
|
|
const auto &Size = S.Stk.pop<T>();
|
|
return CheckNewTypeMismatch(S, OpPC, E, static_cast<uint64_t>(Size));
|
|
}
|
|
bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E);
|
|
|
|
template <PrimType Name, class T = typename PrimConv<Name>::T>
|
|
inline bool BitCastPrim(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte,
|
|
uint32_t ResultBitWidth,
|
|
const llvm::fltSemantics *Sem) {
|
|
const Pointer &FromPtr = S.Stk.pop<Pointer>();
|
|
|
|
if (!CheckLoad(S, OpPC, FromPtr))
|
|
return false;
|
|
|
|
if constexpr (std::is_same_v<T, Pointer>) {
|
|
// The only pointer type we can validly bitcast to is nullptr_t.
|
|
S.Stk.push<Pointer>();
|
|
return true;
|
|
} else {
|
|
|
|
size_t BuffSize = ResultBitWidth / 8;
|
|
llvm::SmallVector<std::byte> Buff(BuffSize);
|
|
bool HasIndeterminateBits = false;
|
|
|
|
Bits FullBitWidth(ResultBitWidth);
|
|
Bits BitWidth = FullBitWidth;
|
|
|
|
if constexpr (std::is_same_v<T, Floating>) {
|
|
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<T, Floating>) {
|
|
assert(Sem);
|
|
Floating Result = S.allocFloat(*Sem);
|
|
Floating::bitcastFromMemory(Buff.data(), *Sem, &Result);
|
|
S.Stk.push<Floating>(Result);
|
|
} else if constexpr (needsAlloc<T>()) {
|
|
T Result = S.allocAP<T>(ResultBitWidth);
|
|
T::bitcastFromMemory(Buff.data(), ResultBitWidth, &Result);
|
|
S.Stk.push<T>(Result);
|
|
} else if constexpr (std::is_same_v<T, Boolean>) {
|
|
// Only allow to cast single-byte integers to bool if they are either 0
|
|
// or 1.
|
|
assert(FullBitWidth.getQuantity() == 8);
|
|
auto Val = static_cast<unsigned int>(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>(T::bitcastFromMemory(Buff.data(), ResultBitWidth));
|
|
} else {
|
|
assert(!Sem);
|
|
S.Stk.push<T>(T::bitcastFromMemory(Buff.data(), ResultBitWidth));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
inline bool BitCast(InterpState &S, CodePtr OpPC) {
|
|
const Pointer &FromPtr = S.Stk.pop<Pointer>();
|
|
Pointer &ToPtr = S.Stk.peek<Pointer>();
|
|
|
|
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<Pointer>();
|
|
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 <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) {
|
|
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<false> ReadArg<IntegralAP<false>>(InterpState &S,
|
|
CodePtr &OpPC) {
|
|
uint32_t BitWidth = IntegralAP<false>::deserializeSize(*OpPC);
|
|
auto Result = S.allocAP<IntegralAP<false>>(BitWidth);
|
|
assert(Result.bitWidth() == BitWidth);
|
|
|
|
IntegralAP<false>::deserialize(*OpPC, &Result);
|
|
OpPC += align(Result.bytesToSerialize());
|
|
return Result;
|
|
}
|
|
|
|
template <>
|
|
inline IntegralAP<true> ReadArg<IntegralAP<true>>(InterpState &S,
|
|
CodePtr &OpPC) {
|
|
uint32_t BitWidth = IntegralAP<true>::deserializeSize(*OpPC);
|
|
auto Result = S.allocAP<IntegralAP<true>>(BitWidth);
|
|
assert(Result.bitWidth() == BitWidth);
|
|
|
|
IntegralAP<true>::deserialize(*OpPC, &Result);
|
|
OpPC += align(Result.bytesToSerialize());
|
|
return Result;
|
|
}
|
|
|
|
template <>
|
|
inline FixedPoint ReadArg<FixedPoint>(InterpState &S, CodePtr &OpPC) {
|
|
FixedPoint FP = FixedPoint::deserialize(*OpPC);
|
|
OpPC += align(FP.bytesToSerialize());
|
|
return FP;
|
|
}
|
|
|
|
} // namespace interp
|
|
} // namespace clang
|
|
|
|
#endif
|