Index: clang/lib/AST/CMakeLists.txt =================================================================== --- clang/lib/AST/CMakeLists.txt +++ clang/lib/AST/CMakeLists.txt @@ -73,6 +73,7 @@ Interp/Frame.cpp Interp/Function.cpp Interp/InterpBuiltin.cpp + Interp/Floating.cpp Interp/Interp.cpp Interp/InterpBlock.cpp Interp/InterpFrame.cpp Index: clang/lib/AST/Interp/Boolean.h =================================================================== --- clang/lib/AST/Interp/Boolean.h +++ clang/lib/AST/Interp/Boolean.h @@ -33,6 +33,7 @@ public: /// Zero-initializes a boolean. Boolean() : V(false) {} + Boolean(const APSInt I) : V(!I.isZero()) {} bool operator<(Boolean RHS) const { return V < RHS.V; } bool operator>(Boolean RHS) const { return V > RHS.V; } Index: clang/lib/AST/Interp/ByteCodeExprGen.h =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.h +++ clang/lib/AST/Interp/ByteCodeExprGen.h @@ -62,6 +62,7 @@ // Expression visitors - result returned on interp stack. bool VisitCastExpr(const CastExpr *E); bool VisitIntegerLiteral(const IntegerLiteral *E); + bool VisitFloatingLiteral(const FloatingLiteral *E); bool VisitParenExpr(const ParenExpr *E); bool VisitBinaryOperator(const BinaryOperator *E); bool VisitPointerArithBinOp(const BinaryOperator *E); @@ -255,6 +256,15 @@ return T->getAsCXXRecordDecl(); } + llvm::RoundingMode getRoundingMode(const Expr *E) const { + FPOptions FPO = E->getFPFeaturesInEffect(Ctx.getLangOpts()); + + if (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) + return llvm::RoundingMode::NearestTiesToEven; + + return FPO.getRoundingMode(); + } + protected: /// Variable to storage mapping. llvm::DenseMap Locals; Index: clang/lib/AST/Interp/ByteCodeExprGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -124,6 +124,41 @@ return this->emitGetPtrBase(ToBase->Offset, CE); } + case CK_FloatingCast: { + if (!this->visit(SubExpr)) + return false; + const auto *TargetSemantics = + &Ctx.getASTContext().getFloatTypeSemantics(CE->getType()); + return this->emitCastFP(TargetSemantics, getRoundingMode(CE), CE); + } + + case CK_IntegralToFloating: { + Optional FromT = classify(SubExpr->getType()); + if (!FromT) + return false; + + if (!this->visit(SubExpr)) + return false; + + const auto *TargetSemantics = + &Ctx.getASTContext().getFloatTypeSemantics(CE->getType()); + return this->emitCastIntegralFloating(*FromT, TargetSemantics, CE); + } + + case CK_FloatingToBoolean: + case CK_FloatingToIntegral: { + llvm::RoundingMode RM = getRoundingMode(CE); + Optional ToT = classify(CE->getType()); + + if (!ToT) + return false; + + if (!this->visit(SubExpr)) + return false; + + return this->emitCastFloatingIntegral(*ToT, RM, CE); + } + case CK_ArrayToPointerDecay: case CK_AtomicToNonAtomic: case CK_ConstructorConversion: @@ -165,6 +200,14 @@ return this->emitConst(LE->getValue(), LE); } +template +bool ByteCodeExprGen::VisitFloatingLiteral(const FloatingLiteral *E) { + if (DiscardResult) + return true; + + return this->emitConstFloat(E->getValue(), E); +} + template bool ByteCodeExprGen::VisitParenExpr(const ParenExpr *PE) { return this->visit(PE->getSubExpr()); @@ -224,14 +267,22 @@ case BO_GE: return Discard(this->emitGE(*LT, BO)); case BO_Sub: + if (BO->getType()->isFloatingType()) + return Discard(this->emitSubf(getRoundingMode(BO), BO)); return Discard(this->emitSub(*T, BO)); case BO_Add: + if (BO->getType()->isFloatingType()) + return Discard(this->emitAddf(getRoundingMode(BO), BO)); return Discard(this->emitAdd(*T, BO)); case BO_Mul: + if (BO->getType()->isFloatingType()) + return Discard(this->emitMulf(getRoundingMode(BO), BO)); return Discard(this->emitMul(*T, BO)); case BO_Rem: return Discard(this->emitRem(*T, BO)); case BO_Div: + if (BO->getType()->isFloatingType()) + return Discard(this->emitDivf(getRoundingMode(BO), BO)); return Discard(this->emitDiv(*T, BO)); case BO_Assign: if (DiscardResult) @@ -550,8 +601,13 @@ // Perform operation. switch (E->getOpcode()) { case BO_AddAssign: - if (!this->emitAdd(*LT, E)) - return false; + if (*LT == PT_Float) { + if (!this->emitAddf(getRoundingMode(E), E)) + return false; + } else { + if (!this->emitAdd(*LT, E)) + return false; + } break; case BO_SubAssign: if (!this->emitSub(*LT, E)) @@ -699,6 +755,8 @@ return this->emitZeroUint64(E); case PT_Ptr: return this->emitNullPtr(E); + case PT_Float: + assert(false); } llvm_unreachable("unknown primitive type"); } @@ -863,6 +921,7 @@ case PT_Bool: return this->emitConstBool(Value, E); case PT_Ptr: + case PT_Float: llvm_unreachable("Invalid integral type"); break; } Index: clang/lib/AST/Interp/Context.cpp =================================================================== --- clang/lib/AST/Interp/Context.cpp +++ clang/lib/AST/Interp/Context.cpp @@ -113,6 +113,9 @@ if (T->isNullPtrType()) return PT_Ptr; + if (T->isFloatingType()) + return PT_Float; + if (auto *AT = dyn_cast(T)) return classify(AT->getValueType()); Index: clang/lib/AST/Interp/Descriptor.cpp =================================================================== --- clang/lib/AST/Interp/Descriptor.cpp +++ clang/lib/AST/Interp/Descriptor.cpp @@ -167,6 +167,11 @@ } static BlockCtorFn getCtorPrim(PrimType Type) { + // Floating types are special. They are primitives, but need their + // constructor called. + if (Type == PT_Float) + return ctorTy::T>; + COMPOSITE_TYPE_SWITCH(Type, return ctorTy, return nullptr); } Index: clang/lib/AST/Interp/Floating.h =================================================================== --- /dev/null +++ clang/lib/AST/Interp/Floating.h @@ -0,0 +1,165 @@ +//===--- Floating.h - Types 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the VM types and helpers operating on types. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_FLOATING_H +#define LLVM_CLANG_AST_INTERP_FLOATING_H + +#include "Primitives.h" +#include "clang/AST/APValue.h" +#include "llvm/ADT/APFloat.h" +#include + +namespace clang { +namespace interp { + +using APFloat = llvm::APFloat; +using APSInt = llvm::APSInt; + +class Floating final { +private: + // The underlying value storage. + APFloat F; + + /// Construct a Floating from anything that is convertible to storage. + template explicit Floating(T V) : F(V) {} + +public: + /// Zero-initializes a Floating. + Floating() : F(0.0f) {} + Floating(APFloat F) : F(F) {} + + // Static constructors for special floating point values. + static Floating getInf(const llvm::fltSemantics &Sem) { + return Floating(APFloat::getInf(Sem)); + } + static Floating zero() { return Floating(0.0f); } + + const APFloat &getAPFloat() const { return F; } + + bool operator<(Floating RHS) const { return F < RHS.F; } + bool operator>(Floating RHS) const { return F > RHS.F; } + bool operator<=(Floating RHS) const { return F <= RHS.F; } + bool operator>=(Floating RHS) const { return F >= RHS.F; } + bool operator==(Floating RHS) const { return F == RHS.F; } + bool operator!=(Floating RHS) const { return F != RHS.F; } + Floating operator-() const { return Floating(-F); } + + APFloat::opStatus convertToInteger(llvm::RoundingMode RM, + APSInt &Result) const { + bool IsExact; + return F.convertToInteger(Result, llvm::APFloat::rmTowardZero, &IsExact); + } + + Floating toSemantics(const llvm::fltSemantics *Sem, + llvm::RoundingMode RM) const { + APFloat Copy = F; + bool LosesInfo; + Copy.convert(*Sem, RM, &LosesInfo); + (void)LosesInfo; + return Floating(Copy); + } + + /// Convert this Floating to one with the same semantics as \Other. + Floating toSemantics(const Floating &Other, llvm::RoundingMode RM) const { + return toSemantics(&Other.F.getSemantics(), RM); + } + + bool compatibleWith(const Floating &Other) const { + return &F.getSemantics() == &Other.F.getSemantics(); + } + + APSInt toAPSInt(unsigned NumBits = 0) const { + return APSInt(F.bitcastToAPInt()); + } + APValue toAPValue() const { return APValue(F); } + void print(llvm::raw_ostream &OS) const { F.print(OS); } + + unsigned bitWidth() const { return F.semanticsSizeInBits(F.getSemantics()); } + + bool isSigned() const { return true; } + bool isNegative() const { return F.isNegative(); } + bool isPositive() const { return !F.isNegative(); } + bool isZero() const { return F.isZero(); } + bool isMin() const { return F.isSmallest(); } + bool isMinusOne() const { return F.isExactlyValue(-1.0); } + bool isNan() const { return F.isNaN(); } + + ComparisonCategoryResult compare(const Floating &RHS) const { + return Compare(F, RHS.F); + } + + template static Floating from(ValT Value) { + if constexpr (std::is_integral::value || + std::is_floating_point::value) + return Floating(Value); + else if constexpr (std::is_same_v) { + return Floating(Value.F); + } else + return Floating::from(static_cast(Value)); + } + + static APFloat::opStatus + fromIntegral(APSInt Val, const llvm::fltSemantics &Sem, Floating &Result) { + APFloat F = APFloat(Sem, 1); + APFloat::opStatus Status = + F.convertFromAPInt(Val, Val.isSigned(), APFloat::rmNearestTiesToEven); + Result = Floating(F); + return Status; + } + + template static Floating from(T Value, unsigned NumBits) { + return Floating(Value); + } + + // ------- + + static APFloat::opStatus add(Floating A, Floating B, llvm::RoundingMode RM, + Floating *R) { + Floating RHS = B; + if (!A.compatibleWith(B)) + RHS = B.toSemantics(A, RM); + + *R = Floating(A.F); + return R->F.add(RHS.F, RM); + } + + static APFloat::opStatus sub(Floating A, Floating B, llvm::RoundingMode RM, + Floating *R) { + *R = Floating(A.F); + return R->F.subtract(B.F, RM); + } + + static APFloat::opStatus mul(Floating A, Floating B, llvm::RoundingMode RM, + Floating *R) { + *R = Floating(A.F); + return R->F.multiply(B.F, RM); + } + + static APFloat::opStatus div(Floating A, Floating B, llvm::RoundingMode RM, + Floating *R) { + *R = Floating(A.F); + return R->F.divide(B.F, RM); + } + + static bool neg(Floating A, Floating *R) { + *R = -A; + return false; + } +}; + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, Floating F); +Floating getSwappedBytes(Floating F); + +} // namespace interp +} // namespace clang + +#endif Index: clang/lib/AST/Interp/Floating.cpp =================================================================== --- /dev/null +++ clang/lib/AST/Interp/Floating.cpp @@ -0,0 +1,22 @@ +//===---- Floating.cpp - Support for floating point values ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Floating.h" + +namespace clang { +namespace interp { + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, Floating F) { + F.print(OS); + return OS; +} + +Floating getSwappedBytes(Floating F) { return F; } + +} // namespace interp +} // namespace clang Index: clang/lib/AST/Interp/Integral.h =================================================================== --- clang/lib/AST/Interp/Integral.h +++ clang/lib/AST/Interp/Integral.h @@ -21,33 +21,14 @@ #include #include +#include "Primitives.h" + namespace clang { namespace interp { using APInt = llvm::APInt; using APSInt = llvm::APSInt; -/// Helper to compare two comparable types. -template -ComparisonCategoryResult Compare(const T &X, const T &Y) { - if (X < Y) - return ComparisonCategoryResult::Less; - if (X > Y) - return ComparisonCategoryResult::Greater; - return ComparisonCategoryResult::Equal; -} - -// Helper structure to select the representation. -template struct Repr; -template <> struct Repr<8, false> { using Type = uint8_t; }; -template <> struct Repr<16, false> { using Type = uint16_t; }; -template <> struct Repr<32, false> { using Type = uint32_t; }; -template <> struct Repr<64, false> { using Type = uint64_t; }; -template <> struct Repr<8, true> { using Type = int8_t; }; -template <> struct Repr<16, true> { using Type = int16_t; }; -template <> struct Repr<32, true> { using Type = int32_t; }; -template <> struct Repr<64, true> { using Type = int64_t; }; - /// Wrapper around numeric types. /// /// These wrappers are required to shared an interface between APSint and @@ -56,6 +37,16 @@ template class Integral final { private: template friend class Integral; + // Helper structure to select the representation. + template struct Repr; + template <> struct Repr<8, false> { using Type = uint8_t; }; + template <> struct Repr<16, false> { using Type = uint16_t; }; + template <> struct Repr<32, false> { using Type = uint32_t; }; + template <> struct Repr<64, false> { using Type = uint64_t; }; + template <> struct Repr<8, true> { using Type = int8_t; }; + template <> struct Repr<16, true> { using Type = int16_t; }; + template <> struct Repr<32, true> { using Type = int32_t; }; + template <> struct Repr<64, true> { using Type = int64_t; }; // The primitive representing the integral. using ReprT = typename Repr::Type; @@ -120,6 +111,14 @@ constexpr static unsigned bitWidth() { return Bits; } + /// Checks if this class can represent the given APSInt. + static bool canRepresent(const APSInt &I) { + unsigned APBitWidth = I.getActiveBits(); + if (APBitWidth > (bitWidth() - isSigned())) + return false; + return true; + } + bool isZero() const { return !V; } bool isMin() const { return *this == min(bitWidth()); } Index: clang/lib/AST/Interp/Interp.h =================================================================== --- clang/lib/AST/Interp/Interp.h +++ clang/lib/AST/Interp/Interp.h @@ -102,6 +102,10 @@ template bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS); +/// Checks if the result is a floating-point operation is valid +/// in the current context. +bool CheckFloatResult(InterpState &S, CodePtr OpPC, APFloat::opStatus Status); + /// Interpreter entry point. bool Interpret(InterpState &S, APValue &Result); @@ -153,6 +157,16 @@ return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } +inline bool Addf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop(); + const Floating &LHS = S.Stk.pop(); + + Floating Result; + auto Status = Floating::add(LHS, RHS, RM, &Result); + S.Stk.push(Result); + return CheckFloatResult(S, OpPC, Status); +} + template ::T> bool Sub(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); @@ -161,6 +175,16 @@ return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } +inline bool Subf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop(); + const Floating &LHS = S.Stk.pop(); + + Floating Result; + auto Status = Floating::sub(LHS, RHS, RM, &Result); + S.Stk.push(Result); + return CheckFloatResult(S, OpPC, Status); +} + template ::T> bool Mul(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); @@ -169,6 +193,15 @@ return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } +inline bool Mulf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop(); + const Floating &LHS = S.Stk.pop(); + + Floating Result; + auto Status = Floating::mul(LHS, RHS, RM, &Result); + S.Stk.push(Result); + return CheckFloatResult(S, OpPC, Status); +} /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS & RHS' on the stack @@ -260,6 +293,19 @@ return false; } +inline bool Divf(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &RHS = S.Stk.pop(); + const Floating &LHS = S.Stk.pop(); + + if (!CheckDivRem(S, OpPC, LHS, RHS)) + return false; + + Floating Result; + auto Status = Floating::div(LHS, RHS, RM, &Result); + S.Stk.push(Result); + return CheckFloatResult(S, OpPC, Status); +} + //===----------------------------------------------------------------------===// // Inv //===----------------------------------------------------------------------===// @@ -1154,6 +1200,50 @@ return true; } +/// 1) Pops a Floating from the stack. +/// 2) Pushes a new floating on the stack that uses the given semantics. +/// Not templated, so implemented in Interp.cpp. +bool CastFP(InterpState &S, CodePtr OpPC, const llvm::fltSemantics *Sem, + llvm::RoundingMode RM); + +template ::T> +bool CastIntegralFloating(InterpState &S, CodePtr OpPC, + const llvm::fltSemantics *Sem) { + const T &From = S.Stk.pop(); + APSInt FromAP = From.toAPSInt(); + Floating Result; + + auto Status = Floating::fromIntegral(FromAP, *Sem, Result); + S.Stk.push(Result); + + return CheckFloatResult(S, OpPC, Status); +} + +template ::T> +bool CastFloatingIntegral(InterpState &S, CodePtr OpPC, llvm::RoundingMode RM) { + const Floating &F = S.Stk.pop(); + APSInt Result(std::max(8u, T::bitWidth() + 1), /*IsUnsigned=*/!T::isSigned()); + + auto Status = F.convertToInteger(RM, Result); + if constexpr (std::is_same_v) { + S.Stk.push(T(Result)); + } else { + // Integer conversion. Check if the result can even be represented in + // T's bit width. + if (!T::canRepresent(Result)) { + const Expr *E = S.Current->getExpr(OpPC); + QualType Type = E->getType(); + + S.CCEDiag(E, diag::note_constexpr_overflow) << F.getAPFloat() << Type; + return S.noteUndefinedBehavior(); + } + + S.Stk.push(T(Result)); + } + + return CheckFloatResult(S, OpPC, Status); +} + //===----------------------------------------------------------------------===// // Zero, Nullptr //===----------------------------------------------------------------------===// Index: clang/lib/AST/Interp/Interp.cpp =================================================================== --- clang/lib/AST/Interp/Interp.cpp +++ clang/lib/AST/Interp/Interp.cpp @@ -526,6 +526,49 @@ return CheckFieldsInitialized(S, OpPC, This, R); } +bool CheckFloatResult(InterpState &S, CodePtr OpPC, APFloat::opStatus Status) { + // In a constant context, assume that any dynamic rounding mode or FP + // exception state matches the default floating-point environment. + if (S.inConstantContext()) + return true; + + const SourceInfo &E = S.Current->getSource(OpPC); + FPOptions FPO = E.asExpr()->getFPFeaturesInEffect(S.Ctx.getLangOpts()); + + if ((Status & APFloat::opInexact) && + FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) { + // Inexact result means that it depends on rounding mode. If the requested + // mode is dynamic, the evaluation cannot be made in compile time. + S.FFDiag(E, diag::note_constexpr_dynamic_rounding); + return false; + } + + if ((Status != APFloat::opOK) && + (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic || + FPO.getExceptionMode() != LangOptions::FPE_Ignore || + FPO.getAllowFEnvAccess())) { + S.FFDiag(E, diag::note_constexpr_float_arithmetic_strict); + return false; + } + + if ((Status & APFloat::opStatus::opInvalidOp) && + FPO.getExceptionMode() != LangOptions::FPE_Ignore) { + // There is no usefully definable result. + S.FFDiag(E); + return false; + } + + return true; +} + +bool CastFP(InterpState &S, CodePtr OpPC, const llvm::fltSemantics *Sem, + llvm::RoundingMode RM) { + Floating F = S.Stk.pop(); + Floating Result = F.toSemantics(Sem, RM); + S.Stk.push(Result); + return true; +} + bool Interpret(InterpState &S, APValue &Result) { // The current stack frame when we started Interpret(). // This is being used by the ops to determine wheter Index: clang/lib/AST/Interp/InterpStack.h =================================================================== --- clang/lib/AST/Interp/InterpStack.h +++ clang/lib/AST/Interp/InterpStack.h @@ -160,6 +160,8 @@ else if constexpr (std::is_same_v || std::is_same_v>) return PT_Uint64; + else if constexpr (std::is_same_v) + return PT_Float; llvm_unreachable("unknown type push()'ed into InterpStack"); } Index: clang/lib/AST/Interp/Opcodes.td =================================================================== --- clang/lib/AST/Interp/Opcodes.td +++ clang/lib/AST/Interp/Opcodes.td @@ -25,6 +25,7 @@ def Uint32 : Type; def Sint64 : Type; def Uint64 : Type; +def Float : Type; def Ptr : Type; //===----------------------------------------------------------------------===// @@ -40,12 +41,15 @@ def ArgUint32 : ArgType { let Name = "uint32_t"; } def ArgSint64 : ArgType { let Name = "int64_t"; } def ArgUint64 : ArgType { let Name = "uint64_t"; } +def ArgFloat : ArgType { let Name = "Floating"; } def ArgBool : ArgType { let Name = "bool"; } def ArgFunction : ArgType { let Name = "const Function *"; } def ArgRecordDecl : ArgType { let Name = "const RecordDecl *"; } def ArgRecordField : ArgType { let Name = "const Record::Field *"; } def ArgLETD: ArgType { let Name = "const LifetimeExtendedTemporaryDecl *"; } +def ArgFltSemantics : ArgType { let Name = "const llvm::fltSemantics *"; } +def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; } //===----------------------------------------------------------------------===// // Classes of types instructions operate on. @@ -55,18 +59,21 @@ list Types; } -def NumberTypeClass : TypeClass { +def IntegerTypeClass : TypeClass { let Types = [Sint8, Uint8, Sint16, Uint16, Sint32, Uint32, Sint64, Uint64]; } -def IntegerTypeClass : TypeClass { - let Types = [Sint8, Uint8, Sint16, Uint16, Sint32, - Uint32, Sint64, Uint64]; +def NumberTypeClass : TypeClass { + let Types = !listconcat(IntegerTypeClass.Types, [Float]); +} + +def FloatTypeClass : TypeClass { + let Types = [Float]; } def AluTypeClass : TypeClass { - let Types = !listconcat(NumberTypeClass.Types, [Bool]); + let Types = !listconcat(IntegerTypeClass.Types, [Bool]); } def PtrTypeClass : TypeClass { @@ -77,12 +84,16 @@ let Types = [Bool]; } +def NonPtrTypeClass : TypeClass { + let Types = !listconcat(IntegerTypeClass.Types, [Bool], [Float]); +} + def AllTypeClass : TypeClass { - let Types = !listconcat(AluTypeClass.Types, PtrTypeClass.Types); + let Types = !listconcat(AluTypeClass.Types, PtrTypeClass.Types, FloatTypeClass.Types); } def ComparableTypeClass : TypeClass { - let Types = !listconcat(AluTypeClass.Types, [Ptr]); + let Types = !listconcat(AluTypeClass.Types, [Ptr], [Float]); } class SingletonTypeClass : TypeClass { @@ -109,6 +120,11 @@ let HasGroup = 1; } +class FloatOpcode : Opcode { + let Types = []; + let Args = [ArgRoundingMode]; +} + class IntegerOpcode : Opcode { let Types = [IntegerTypeClass]; let HasGroup = 1; @@ -194,6 +210,7 @@ def ConstUint32 : ConstOpcode; def ConstSint64 : ConstOpcode; def ConstUint64 : ConstOpcode; +def ConstFloat : ConstOpcode; def ConstBool : ConstOpcode; // [] -> [Integer] @@ -418,13 +435,19 @@ //===----------------------------------------------------------------------===// // [Real, Real] -> [Real] -def Sub : AluOpcode; -def Add : AluOpcode; -def Mul : AluOpcode; -def Rem : Opcode { - let Types = [NumberTypeClass]; - let HasGroup = 1; -} +def Add : AluOpcode; +def Addf : FloatOpcode; +def Sub : AluOpcode; +def Subf : FloatOpcode; +def Mul : AluOpcode; +def Mulf : FloatOpcode; +def Rem : IntegerOpcode; +def Div : IntegerOpcode; +def Divf : FloatOpcode; + +def BitAnd : IntegerOpcode; +def BitOr : IntegerOpcode; +def BitXor : IntegerOpcode; def Shl : Opcode { let Types = [IntegerTypeClass, IntegerTypeClass]; @@ -436,14 +459,6 @@ let HasGroup = 1; } -def BitAnd : IntegerOpcode; -def BitOr : IntegerOpcode; -def Div : Opcode { - let Types = [NumberTypeClass]; - let HasGroup = 1; -} -def BitXor : IntegerOpcode; - //===----------------------------------------------------------------------===// // Unary operators. //===----------------------------------------------------------------------===// @@ -461,20 +476,19 @@ // [Real] -> [Real] def Neg: Opcode { - let Types = [AluTypeClass]; + let Types = [NonPtrTypeClass]; let HasGroup = 1; } // [Real] -> [Real] def Comp: Opcode { - let Types = [NumberTypeClass]; + let Types = [IntegerTypeClass]; let HasGroup = 1; } //===----------------------------------------------------------------------===// -// Cast. +// Cast, CastFP. //===----------------------------------------------------------------------===// -// TODO: Expand this to handle casts between more types. def FromCastTypeClass : TypeClass { let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool]; @@ -489,6 +503,25 @@ let HasGroup = 1; } +def CastFP : Opcode { + let Types = []; + let Args = [ArgFltSemantics, ArgRoundingMode]; +} + +// Cast an integer to a floating type +def CastIntegralFloating : Opcode { + let Types = [AluTypeClass]; + let Args = [ArgFltSemantics]; + let HasGroup = 1; +} + +// Cast a floating to an integer type +def CastFloatingIntegral : Opcode { + let Types = [AluTypeClass]; + let Args = [ArgRoundingMode]; + let HasGroup = 1; +} + //===----------------------------------------------------------------------===// // Comparison opcodes. //===----------------------------------------------------------------------===// Index: clang/lib/AST/Interp/PrimType.h =================================================================== --- clang/lib/AST/Interp/PrimType.h +++ clang/lib/AST/Interp/PrimType.h @@ -13,11 +13,12 @@ #ifndef LLVM_CLANG_AST_INTERP_TYPE_H #define LLVM_CLANG_AST_INTERP_TYPE_H +#include "Boolean.h" +#include "Floating.h" +#include "Integral.h" #include #include #include -#include "Boolean.h" -#include "Integral.h" namespace clang { namespace interp { @@ -35,6 +36,7 @@ PT_Sint64, PT_Uint64, PT_Bool, + PT_Float, PT_Ptr, }; @@ -48,6 +50,7 @@ template <> struct PrimConv { using T = Integral<32, false>; }; template <> struct PrimConv { using T = Integral<64, true>; }; template <> struct PrimConv { using T = Integral<64, false>; }; +template <> struct PrimConv { using T = Floating; }; template <> struct PrimConv { using T = Boolean; }; template <> struct PrimConv { using T = Pointer; }; @@ -70,6 +73,7 @@ case PT_Uint32: case PT_Sint64: case PT_Uint64: + case PT_Float: return true; default: return false; @@ -94,6 +98,7 @@ TYPE_SWITCH_CASE(PT_Uint32, B) \ TYPE_SWITCH_CASE(PT_Sint64, B) \ TYPE_SWITCH_CASE(PT_Uint64, B) \ + TYPE_SWITCH_CASE(PT_Float, B) \ TYPE_SWITCH_CASE(PT_Bool, B) \ TYPE_SWITCH_CASE(PT_Ptr, B) \ } \ Index: clang/lib/AST/Interp/Primitives.h =================================================================== --- /dev/null +++ clang/lib/AST/Interp/Primitives.h @@ -0,0 +1,36 @@ +//===------ Primitives.h - Types 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 +// +//===----------------------------------------------------------------------===// +// +// Utilities and helper functions for all primitive types: +// - Integral +// - Floating +// - Boolean +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_PRIMITIVES_H +#define LLVM_CLANG_AST_INTERP_PRIMITIVES_H + +#include "clang/AST/ComparisonCategories.h" + +namespace clang { +namespace interp { + +/// Helper to compare two comparable types. +template ComparisonCategoryResult Compare(const T &X, const T &Y) { + if (X < Y) + return ComparisonCategoryResult::Less; + if (X > Y) + return ComparisonCategoryResult::Greater; + return ComparisonCategoryResult::Equal; +} + +} // namespace interp +} // namespace clang + +#endif Index: clang/test/AST/Interp/const-fpfeatures.cpp =================================================================== --- /dev/null +++ clang/test/AST/Interp/const-fpfeatures.cpp @@ -0,0 +1,65 @@ +// RUN: %clang_cc1 -S -emit-llvm -triple i386-linux -std=c++2a -Wno-unknown-pragmas %s -o - | FileCheck %s +// RUN: %clang_cc1 -S -emit-llvm -triple i386-linux -fexperimental-new-constant-interpreter -std=c++2a -Wno-unknown-pragmas %s -o - | FileCheck %s + + +#pragma STDC FENV_ROUND FE_UPWARD + +float F1u = 1.0F + 0x0.000002p0F; +float F2u = 1.0F + 0x0.000001p0F; +float F3u = 0x1.000001p0; +// CHECK: @F1u = {{.*}} float 0x3FF0000020000000 +// CHECK: @F2u = {{.*}} float 0x3FF0000020000000 +// CHECK: @F3u = {{.*}} float 0x3FF0000020000000 + +#pragma STDC FENV_ROUND FE_DOWNWARD + +float F1d = 1.0F + 0x0.000002p0F; +float F2d = 1.0F + 0x0.000001p0F; +float F3d = 0x1.000001p0; + +// CHECK: @F1d = {{.*}} float 0x3FF0000020000000 +// CHECK: @F2d = {{.*}} float 1.000000e+00 +// CHECK: @F3d = {{.*}} float 1.000000e+00 + +// nextUp(1.F) == 0x1.000002p0F + +constexpr float add_round_down(float x, float y) { + #pragma STDC FENV_ROUND FE_DOWNWARD + float res = x; + res += y; + return res; +} + +constexpr float add_round_up(float x, float y) { + #pragma STDC FENV_ROUND FE_UPWARD + float res = x; + res += y; + return res; +} + +float V1 = add_round_down(1.0F, 0x0.000001p0F); +float V2 = add_round_up(1.0F, 0x0.000001p0F); +// CHECK: @V1 = {{.*}} float 1.000000e+00 +// CHECK: @V2 = {{.*}} float 0x3FF0000020000000 + +constexpr float add_cast_round_down(float x, double y) { + #pragma STDC FENV_ROUND FE_DOWNWARD + float res = x; + res += y; + return res; +} + +constexpr float add_cast_round_up(float x, double y) { + #pragma STDC FENV_ROUND FE_UPWARD + float res = x; + res += y; + return res; +} + +float V3 = add_cast_round_down(1.0F, 0x0.000001p0F); +float V4 = add_cast_round_up(1.0F, 0x0.000001p0F); + +// CHECK: @V3 = {{.*}} float 1.000000e+00 +// CHECK: @V4 = {{.*}} float 0x3FF0000020000000 + + Index: clang/test/AST/Interp/floats.cpp =================================================================== --- /dev/null +++ clang/test/AST/Interp/floats.cpp @@ -0,0 +1,50 @@ +// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify %s +// RUN: %clang_cc1 -verify=ref %s + +constexpr int i = 2; +constexpr float f = 1.0f; +static_assert(f == 1.0f, ""); + +constexpr float f2 = 1u * f; +static_assert(f2 == 1.0f, ""); + +constexpr float f3 = 1.5; +constexpr int i3 = f3; +static_assert(i3 == 1); + +constexpr bool b3 = f3; +static_assert(b3); + + +static_assert(1.0f + 3u == 4, ""); +static_assert(4.0f / 1.0f == 4, ""); +static_assert(10.0f * false == 0, ""); + +constexpr float floats[] = {1.0f, 2.0f, 3.0f, 4.0f}; + +constexpr float m = 5.0f / 0.0f; // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{division by zero}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{division by zero}} + +static_assert(~2.0f == 3, ""); // ref-error {{invalid argument type 'float' to unary expression}} \ + // expected-error {{invalid argument type 'float' to unary expression}} + +/// Initialized by a double. +constexpr float df = 0.0; +/// The other way around. +constexpr double fd = 0.0f; + +static_assert(0.0f == -0.0f, ""); + +const int k = 3 * (1.0f / 3.0f); +static_assert(k == 1, ""); + +constexpr bool b = 1.0; +static_assert(b, ""); + +constexpr double db = true; +static_assert(db == 1.0, ""); + +constexpr float fa[] = {1.0f, 2.0, 1, false}; +constexpr float da[] = {1.0f, 2.0, 1, false}; Index: clang/test/SemaCXX/rounding-math.cpp =================================================================== --- clang/test/SemaCXX/rounding-math.cpp +++ clang/test/SemaCXX/rounding-math.cpp @@ -1,5 +1,6 @@ // RUN: %clang_cc1 -triple x86_64-linux -verify=norounding -Wno-unknown-pragmas %s // RUN: %clang_cc1 -triple x86_64-linux -verify=rounding %s -frounding-math -Wno-unknown-pragmas +// RUN: %clang_cc1 -triple x86_64-linux -verify=rounding %s -frounding-math -fexperimental-new-constant-interpreter -Wno-unknown-pragmas // rounding-no-diagnostics #define fold(x) (__builtin_constant_p(x) ? (x) : (x))