diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -74,6 +74,7 @@ Interp/Frame.cpp Interp/Function.cpp Interp/InterpBuiltin.cpp + Interp/Floating.cpp Interp/Interp.cpp Interp/InterpBlock.cpp Interp/InterpFrame.cpp diff --git a/clang/lib/AST/Interp/Boolean.h b/clang/lib/AST/Interp/Boolean.h --- a/clang/lib/AST/Interp/Boolean.h +++ b/clang/lib/AST/Interp/Boolean.h @@ -27,12 +27,10 @@ /// Underlying boolean. bool V; - /// Construct a wrapper from a boolean. - explicit Boolean(bool V) : V(V) {} - public: /// Zero-initializes a boolean. Boolean() : V(false) {} + explicit Boolean(bool V) : V(V) {} bool operator<(Boolean RHS) const { return V < RHS.V; } bool operator>(Boolean RHS) const { return V > RHS.V; } diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.cpp b/clang/lib/AST/Interp/ByteCodeEmitter.cpp --- a/clang/lib/AST/Interp/ByteCodeEmitter.cpp +++ b/clang/lib/AST/Interp/ByteCodeEmitter.cpp @@ -8,6 +8,7 @@ #include "ByteCodeEmitter.h" #include "Context.h" +#include "Floating.h" #include "Opcode.h" #include "Program.h" #include "clang/AST/DeclCXX.h" diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -58,6 +58,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); @@ -234,6 +235,15 @@ return VD->hasGlobalStorage() || VD->isConstexpr(); } + 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; diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -11,6 +11,7 @@ #include "ByteCodeGenError.h" #include "ByteCodeStmtGen.h" #include "Context.h" +#include "Floating.h" #include "Function.h" #include "PrimType.h" #include "Program.h" @@ -95,6 +96,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: { + std::optional FromT = classify(SubExpr->getType()); + if (!FromT) + return false; + + if (!this->visit(SubExpr)) + return false; + + const auto *TargetSemantics = + &Ctx.getASTContext().getFloatTypeSemantics(CE->getType()); + llvm::RoundingMode RM = getRoundingMode(CE); + return this->emitCastIntegralFloating(*FromT, TargetSemantics, RM, CE); + } + + case CK_FloatingToBoolean: + case CK_FloatingToIntegral: { + std::optional ToT = classify(CE->getType()); + + if (!ToT) + return false; + + if (!this->visit(SubExpr)) + return false; + + return this->emitCastFloatingIntegral(*ToT, CE); + } + case CK_ArrayToPointerDecay: case CK_AtomicToNonAtomic: case CK_ConstructorConversion: @@ -136,6 +172,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()); @@ -195,14 +239,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) @@ -515,6 +567,9 @@ assert(!E->getType()->isPointerType() && "Support pointer arithmethic in compound assignment operators"); + assert(!E->getType()->isFloatingType() && + "Support floating types in compound assignment operators"); + // Get LHS pointer, load its value and get RHS value. if (!visit(LHS)) return false; @@ -636,6 +691,8 @@ return this->emitZeroUint64(E); case PT_Ptr: return this->emitNullPtr(E); + case PT_Float: + assert(false); } llvm_unreachable("unknown primitive type"); } @@ -800,6 +857,7 @@ case PT_Bool: return this->emitConstBool(Value, E); case PT_Ptr: + case PT_Float: llvm_unreachable("Invalid integral type"); break; } diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp --- a/clang/lib/AST/Interp/Context.cpp +++ b/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()); diff --git a/clang/lib/AST/Interp/Descriptor.cpp b/clang/lib/AST/Interp/Descriptor.cpp --- a/clang/lib/AST/Interp/Descriptor.cpp +++ b/clang/lib/AST/Interp/Descriptor.cpp @@ -8,6 +8,7 @@ #include "Descriptor.h" #include "Boolean.h" +#include "Floating.h" #include "Pointer.h" #include "PrimType.h" #include "Record.h" @@ -170,6 +171,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); } diff --git a/clang/lib/AST/Interp/Disasm.cpp b/clang/lib/AST/Interp/Disasm.cpp --- a/clang/lib/AST/Interp/Disasm.cpp +++ b/clang/lib/AST/Interp/Disasm.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "Floating.h" #include "Function.h" #include "Opcode.h" #include "PrimType.h" diff --git a/clang/lib/AST/Interp/Floating.h b/clang/lib/AST/Interp/Floating.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/Floating.h @@ -0,0 +1,144 @@ +//===--- 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" + +namespace clang { +namespace interp { + +using APFloat = llvm::APFloat; +using APSInt = llvm::APSInt; + +class Floating final { +private: + // The underlying value storage. + APFloat F; + +public: + /// Zero-initializes a Floating. + Floating() : F(0.0f) {} + Floating(const APFloat &F) : F(F) {} + + // Static constructors for special floating point values. + static Floating getInf(const llvm::fltSemantics &Sem) { + return Floating(APFloat::getInf(Sem)); + } + 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(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); + } + + APSInt toAPSInt(unsigned NumBits = 0) const { + return APSInt(F.bitcastToAPInt()); + } + APValue toAPValue() const { return APValue(F); } + void print(llvm::raw_ostream &OS) const { + // Can't use APFloat::print() since it appends a newline. + SmallVector Buffer; + F.toString(Buffer); + OS << Buffer; + } + + 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 isNonZero() const { return F.isNonZero(); } + bool isMin() const { return F.isSmallest(); } + bool isMinusOne() const { return F.isExactlyValue(-1.0); } + bool isNan() const { return F.isNaN(); } + bool isFinite() const { return F.isFinite(); } + + ComparisonCategoryResult compare(const Floating &RHS) const { + return Compare(F, RHS.F); + } + + static APFloat::opStatus fromIntegral(APSInt Val, + const llvm::fltSemantics &Sem, + llvm::RoundingMode RM, + Floating &Result) { + APFloat F = APFloat(Sem); + APFloat::opStatus Status = F.convertFromAPInt(Val, Val.isSigned(), RM); + Result = Floating(F); + return Status; + } + + // ------- + + static APFloat::opStatus add(Floating A, Floating B, llvm::RoundingMode RM, + Floating *R) { + *R = Floating(A.F); + return R->F.add(B.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 diff --git a/clang/lib/AST/Interp/PrimType.cpp b/clang/lib/AST/Interp/Floating.cpp copy from clang/lib/AST/Interp/PrimType.cpp copy to clang/lib/AST/Interp/Floating.cpp --- a/clang/lib/AST/Interp/PrimType.cpp +++ b/clang/lib/AST/Interp/Floating.cpp @@ -1,4 +1,4 @@ -//===--- PrimType.cpp - Types for the constexpr VM --------------*- C++ -*-===// +//===---- 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. @@ -6,20 +6,17 @@ // //===----------------------------------------------------------------------===// -#include "PrimType.h" -#include "Boolean.h" -#include "Pointer.h" - -using namespace clang; -using namespace clang::interp; +#include "Floating.h" namespace clang { namespace interp { -size_t primSize(PrimType Type) { - TYPE_SWITCH(Type, return sizeof(T)); - llvm_unreachable("not a primitive type"); +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 diff --git a/clang/lib/AST/Interp/Integral.h b/clang/lib/AST/Interp/Integral.h --- a/clang/lib/AST/Interp/Integral.h +++ b/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; diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_AST_INTERP_INTERP_H #include "Boolean.h" +#include "Floating.h" #include "Function.h" #include "InterpFrame.h" #include "InterpStack.h" @@ -137,6 +138,10 @@ return true; } +/// 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); @@ -188,6 +193,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(); @@ -196,6 +211,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(); @@ -204,6 +229,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 @@ -295,6 +329,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 //===----------------------------------------------------------------------===// @@ -1182,6 +1229,52 @@ 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, + llvm::RoundingMode RM) { + const T &From = S.Stk.pop(); + APSInt FromAP = From.toAPSInt(); + Floating Result; + + auto Status = Floating::fromIntegral(FromAP, *Sem, RM, Result); + S.Stk.push(Result); + + return CheckFloatResult(S, OpPC, Status); +} + +template ::T> +bool CastFloatingIntegral(InterpState &S, CodePtr OpPC) { + const Floating &F = S.Stk.pop(); + + if constexpr (std::is_same_v) { + S.Stk.push(T(F.isNonZero())); + return true; + } else { + APSInt Result(std::max(8u, T::bitWidth() + 1), + /*IsUnsigned=*/!T::isSigned()); + auto Status = F.convertToInteger(Result); + + // Float-to-Integral overflow check. + if ((Status & APFloat::opStatus::opInvalidOp) && F.isFinite()) { + const Expr *E = S.Current->getExpr(OpPC); + QualType Type = E->getType(); + + S.CCEDiag(E, diag::note_constexpr_overflow) << F.getAPFloat() << Type; + return S.noteUndefinedBehavior(); + } + + S.Stk.push(T(Result)); + return CheckFloatResult(S, OpPC, Status); + } +} + //===----------------------------------------------------------------------===// // Zero, Nullptr //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -473,6 +473,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 diff --git a/clang/lib/AST/Interp/InterpFrame.cpp b/clang/lib/AST/Interp/InterpFrame.cpp --- a/clang/lib/AST/Interp/InterpFrame.cpp +++ b/clang/lib/AST/Interp/InterpFrame.cpp @@ -8,6 +8,7 @@ #include "InterpFrame.h" #include "Boolean.h" +#include "Floating.h" #include "Function.h" #include "InterpStack.h" #include "InterpState.h" diff --git a/clang/lib/AST/Interp/InterpStack.h b/clang/lib/AST/Interp/InterpStack.h --- a/clang/lib/AST/Interp/InterpStack.h +++ b/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"); } diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td --- a/clang/lib/AST/Interp/Opcodes.td +++ b/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,11 +41,14 @@ 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 ArgFltSemantics : ArgType { let Name = "const llvm::fltSemantics *"; } +def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; } //===----------------------------------------------------------------------===// // Classes of types instructions operate on. @@ -54,18 +58,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 { @@ -76,12 +83,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 { @@ -108,6 +119,11 @@ let HasGroup = 1; } +class FloatOpcode : Opcode { + let Types = []; + let Args = [ArgRoundingMode]; +} + class IntegerOpcode : Opcode { let Types = [IntegerTypeClass]; let HasGroup = 1; @@ -199,6 +215,7 @@ def ConstUint32 : ConstOpcode; def ConstSint64 : ConstOpcode; def ConstUint64 : ConstOpcode; +def ConstFloat : ConstOpcode; def ConstBool : ConstOpcode; // [] -> [Integer] @@ -419,13 +436,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]; @@ -437,14 +460,6 @@ let HasGroup = 1; } -def BitAnd : IntegerOpcode; -def BitOr : IntegerOpcode; -def Div : Opcode { - let Types = [NumberTypeClass]; - let HasGroup = 1; -} -def BitXor : IntegerOpcode; - //===----------------------------------------------------------------------===// // Unary operators. //===----------------------------------------------------------------------===// @@ -462,20 +477,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]; @@ -490,6 +504,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, ArgRoundingMode]; + let HasGroup = 1; +} + +// Cast a floating to an integer type +def CastFloatingIntegral : Opcode { + let Types = [AluTypeClass]; + let Args = []; + let HasGroup = 1; +} + //===----------------------------------------------------------------------===// // Comparison opcodes. //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/Interp/PrimType.h b/clang/lib/AST/Interp/PrimType.h --- a/clang/lib/AST/Interp/PrimType.h +++ b/clang/lib/AST/Interp/PrimType.h @@ -23,6 +23,7 @@ class Pointer; class Boolean; +class Floating; /// Enumeration of the primitive types of the VM. enum PrimType : unsigned { @@ -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; }; @@ -77,6 +80,7 @@ case PT_Uint32: case PT_Sint64: case PT_Uint64: + case PT_Float: return true; default: return false; @@ -101,6 +105,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) \ } \ diff --git a/clang/lib/AST/Interp/PrimType.cpp b/clang/lib/AST/Interp/PrimType.cpp --- a/clang/lib/AST/Interp/PrimType.cpp +++ b/clang/lib/AST/Interp/PrimType.cpp @@ -8,6 +8,7 @@ #include "PrimType.h" #include "Boolean.h" +#include "Floating.h" #include "Pointer.h" using namespace clang; diff --git a/clang/lib/AST/Interp/Primitives.h b/clang/lib/AST/Interp/Primitives.h new file mode 100644 --- /dev/null +++ b/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 diff --git a/clang/test/AST/Interp/const-fpfeatures.cpp b/clang/test/AST/Interp/const-fpfeatures.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/Interp/const-fpfeatures.cpp @@ -0,0 +1,74 @@ +// 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 + +float FI1u = 0xFFFFFFFFU; +// CHECK: @FI1u = {{.*}} float 0x41F0000000000000 + +#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 + + +float FI1d = 0xFFFFFFFFU; +// CHECK: @FI1d = {{.*}} float 0x41EFFFFFE0000000 + +// nextUp(1.F) == 0x1.000002p0F + +constexpr float add_round_down(float x, float y) { + #pragma STDC FENV_ROUND FE_DOWNWARD + float res = x; + res = res + y; + return res; +} + +constexpr float add_round_up(float x, float y) { + #pragma STDC FENV_ROUND FE_UPWARD + float res = x; + res = 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 + + +/// FIXME: The following tests need support for compound assign operators +/// with LHS and RHS of different semantics. +#if 0 +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); + + +#endif diff --git a/clang/test/AST/Interp/floats.cpp b/clang/test/AST/Interp/floats.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/Interp/floats.cpp @@ -0,0 +1,56 @@ +// 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 double da[] = {1.0f, 2.0, 1, false}; + +constexpr float fm = __FLT_MAX__; +constexpr int someInt = fm; // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{is outside the range of representable values}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{is outside the range of representable values}} diff --git a/clang/test/SemaCXX/rounding-math.cpp b/clang/test/SemaCXX/rounding-math.cpp --- a/clang/test/SemaCXX/rounding-math.cpp +++ b/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))