diff --git a/clang/include/clang/Basic/FixedPoint.h b/clang/include/clang/Basic/FixedPoint.h --- a/clang/include/clang/Basic/FixedPoint.h +++ b/clang/include/clang/Basic/FixedPoint.h @@ -132,17 +132,20 @@ APFixedPoint mul(const APFixedPoint &Other, bool *Overflow = nullptr) const; APFixedPoint div(const APFixedPoint &Other, bool *Overflow = nullptr) const; - /// Perform a unary negation (-X) on this fixed point type, taking into - /// account saturation if applicable. - APFixedPoint negate(bool *Overflow = nullptr) const; - - APFixedPoint shr(unsigned Amt) const { + // Perform shift operations on a fixed point type. Unlike the other binary + // operations, the resulting fixed point value will be in the original + // semantic. + APFixedPoint shl(unsigned Amt, bool *Overflow = nullptr) const; + APFixedPoint shr(unsigned Amt, bool *Overflow = nullptr) const { + // Right shift cannot overflow. + if (Overflow) + *Overflow = false; return APFixedPoint(Val >> Amt, Sema); } - APFixedPoint shl(unsigned Amt) const { - return APFixedPoint(Val << Amt, Sema); - } + /// Perform a unary negation (-X) on this fixed point type, taking into + /// account saturation if applicable. + APFixedPoint negate(bool *Overflow = nullptr) const; /// Return the integral part of this fixed point number, rounded towards /// zero. (-2.5k -> -2) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -13003,6 +13003,29 @@ .convert(ResultFXSema, &ConversionOverflow); break; } + case BO_Shl: + case BO_Shr: { + FixedPointSemantics LHSSema = LHSFX.getSemantics(); + llvm::APSInt RHSVal = RHSFX.getValue(); + + unsigned ShiftBW = + LHSSema.getWidth() - (unsigned)LHSSema.hasUnsignedPadding(); + unsigned Amt = RHSVal.getLimitedValue(ShiftBW - 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 (RHSVal.isNegative()) + Info.CCEDiag(E, diag::note_constexpr_negative_shift) << RHSVal; + else if (Amt != RHSVal) + Info.CCEDiag(E, diag::note_constexpr_large_shift) + << RHSVal << E->getType() << ShiftBW; + + if (E->getOpcode() == BO_Shl) + Result = LHSFX.shl(Amt, &OpOverflow); + else + Result = LHSFX.shr(Amt, &OpOverflow); + break; + } default: return false; } diff --git a/clang/lib/Basic/FixedPoint.cpp b/clang/lib/Basic/FixedPoint.cpp --- a/clang/lib/Basic/FixedPoint.cpp +++ b/clang/lib/Basic/FixedPoint.cpp @@ -309,6 +309,40 @@ CommonFXSema); } +APFixedPoint APFixedPoint::shl(unsigned Amt, bool *Overflow) const { + llvm::APSInt ThisVal = Val; + bool Overflowed = false; + + // Widen the LHS. + unsigned Wide = Sema.getWidth() * 2; + if (Sema.isSigned()) + ThisVal = ThisVal.sextOrSelf(Wide); + else + ThisVal = ThisVal.zextOrSelf(Wide); + + // Clamp the shift amount at the original width, and perform the shift. + Amt = std::min(Amt, ThisVal.getBitWidth()); + llvm::APSInt Result = ThisVal << Amt; + Result.setIsSigned(Sema.isSigned()); + + // If our result lies outside of the representative range of the + // semantic, we either have overflow or saturation. + llvm::APSInt Max = APFixedPoint::getMax(Sema).getValue().extOrTrunc(Wide); + llvm::APSInt Min = APFixedPoint::getMin(Sema).getValue().extOrTrunc(Wide); + if (Sema.isSaturated()) { + if (Result < Min) + Result = Min; + else if (Result > Max) + Result = Max; + } else + Overflowed = Result < Min || Result > Max; + + if (Overflow) + *Overflow = Overflowed; + + return APFixedPoint(Result.sextOrTrunc(Sema.getWidth()), Sema); +} + void APFixedPoint::toString(llvm::SmallVectorImpl &Str) const { llvm::APSInt Val = getValue(); unsigned Scale = getScale(); diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -10601,9 +10601,13 @@ } QualType LHSExprType = LHS.get()->getType(); - uint64_t LeftSize = LHSExprType->isExtIntType() - ? S.Context.getIntWidth(LHSExprType) - : S.Context.getTypeSize(LHSExprType); + uint64_t LeftSize = S.Context.getTypeSize(LHSExprType); + if (LHSExprType->isExtIntType()) + LeftSize = S.Context.getIntWidth(LHSExprType); + else if (LHSExprType->isFixedPointType()) { + FixedPointSemantics FXSema = S.Context.getFixedPointSemantics(LHSExprType); + LeftSize = FXSema.getWidth() - (unsigned)FXSema.hasUnsignedPadding(); + } llvm::APInt LeftBits(Right.getBitWidth(), LeftSize); if (Right.uge(LeftBits)) { S.DiagRuntimeBehavior(Loc, RHS.get(), @@ -10612,7 +10616,8 @@ return; } - if (Opc != BO_Shl) + // FIXME: We probably need to handle fixed point types specially here. + if (Opc != BO_Shl || LHSExprType->isFixedPointType()) return; // When left shifting an ICE which is signed, we can check for overflow which @@ -10796,7 +10801,9 @@ QualType RHSType = RHS.get()->getType(); // C99 6.5.7p2: Each of the operands shall have integer type. - if (!LHSType->hasIntegerRepresentation() || + // Embedded-C 4.1.6.2.2: The LHS may also be fixed-point. + if ((!LHSType->isFixedPointOrIntegerType() && + !LHSType->hasIntegerRepresentation()) || !RHSType->hasIntegerRepresentation()) return InvalidOperands(Loc, LHS, RHS); diff --git a/clang/test/Frontend/fixed_point_errors.c b/clang/test/Frontend/fixed_point_errors.c --- a/clang/test/Frontend/fixed_point_errors.c +++ b/clang/test/Frontend/fixed_point_errors.c @@ -259,11 +259,30 @@ short _Accum mul_ovf2 = (-0.5hr - 0.5hr) * (-0.5hr - 0.5hr); // expected-warning {{overflow in expression; result is -1.0 with type 'short _Fract'}} short _Accum div_ovf1 = 255.0hk / 0.5hk; // expected-warning {{overflow in expression; result is -2.0 with type 'short _Accum'}} +short _Accum shl_ovf1 = 255.0hk << 8; // expected-warning {{overflow in expression; result is -256.0 with type 'short _Accum'}} +short _Fract shl_ovf2 = -0.25hr << 3; // expected-warning {{overflow in expression; result is 0.0 with type 'short _Fract'}} +unsigned short _Accum shl_ovf3 = 100.5uhk << 3; // expected-warning {{overflow in expression; result is 36.0 with type 'unsigned short _Accum'}} +short _Fract shl_ovf4 = 0.25hr << 2; // expected-warning {{overflow in expression; result is -1.0 with type 'short _Fract'}} + +_Accum shl_bw1 = 0.000091552734375k << 32; // expected-warning {{shift count >= width of type}} \ + expected-warning {{overflow in expression; result is -65536.0 with type '_Accum'}} +unsigned _Fract shl_bw2 = 0.65ur << 16; // expected-warning {{shift count >= width of type}} \ + expected-warning {{overflow in expression; result is 0.0 with type 'unsigned _Fract'}} +_Sat short _Accum shl_bw3 = (_Sat short _Accum)80.0hk << 17; // expected-warning {{shift count >= width of type}} +short _Accum shr_bw1 = 1.0hk >> 17; // expected-warning {{shift count >= width of type}} + +_Accum shl_neg1 = 25.5k << -5; // expected-warning {{shift count is negative}} \ + // expected-warning {{overflow in expression; result is 0.0 with type '_Accum'}} +_Accum shr_neg1 = 8.75k >> -9; // expected-warning {{shift count is negative}} +_Fract shl_neg2 = 0.25r << -17; // expected-warning {{shift count is negative}} \ + // expected-warning {{overflow in expression; result is 0.0 with type '_Fract'}} + // No warnings for saturation short _Fract add_sat = (_Sat short _Fract)0.5hr + 0.5hr; short _Accum sub_sat = (_Sat short _Accum)-200.0hk - 80.0hk; short _Accum mul_sat = (_Sat short _Accum)80.0hk * 10.0hk; short _Fract div_sat = (_Sat short _Fract)0.9hr / 0.1hr; +short _Accum shl_sat = (_Sat short _Accum)200.0hk << 5; // Division by zero short _Accum div_zero = 4.5k / 0.0lr; // expected-error {{initializer element is not a compile-time constant}} diff --git a/clang/test/Frontend/fixed_point_shift.c b/clang/test/Frontend/fixed_point_shift.c new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/fixed_point_shift.c @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -ffixed-point -S -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,SIGNED +// RUN: %clang_cc1 -ffixed-point -fpadding-on-unsigned-fixed-point -S -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,UNSIGNED + +short _Accum sa_const1 = 1.0hk << 2; // CHECK-DAG: @sa_const1 = {{.*}}global i16 512 +short _Accum sa_const2 = 0.5hk << 2; // CHECK-DAG: @sa_const2 = {{.*}}global i16 256 +short _Accum sa_const3 = 10.0hk >> 3; // CHECK-DAG: @sa_const3 = {{.*}}global i16 160 +short _Accum sa_const4 = 0.0546875hk << 8; // CHECK-DAG: @sa_const4 = {{.*}}global i16 1792 +short _Accum sa_const5 = -1.0hk << 2; // CHECK-DAG: @sa_const5 = {{.*}}global i16 -512 +short _Accum sa_const6 = -255.0hk >> 8; // CHECK-DAG: @sa_const6 = {{.*}}global i16 -128 + +_Fract f_const1 = -1.0r >> 5; // CHECK-DAG: @f_const1 = {{.*}}global i16 -1024 +_Fract f_const2 = 0.0052490234375r >> 3; // CHECK-DAG: @f_const2 = {{.*}}global i16 21 +_Fract f_const3 = -0.0001r << 5; // CHECK-DAG: @f_const3 = {{.*}}global i16 -96 +_Fract f_const4 = -0.75r >> 15; // CHECK-DAG: @f_const4 = {{.*}}global i16 -1 +_Fract f_const5 = 0.078216552734375r << 3; // CHECK-DAG: @f_const5 = {{.*}}global i16 20504 + +unsigned _Fract uf_const1 = 0.375ur >> 13; +// SIGNED-DAG: @uf_const1 = {{.*}}global i16 3 +// UNSIGNED-DAG: @uf_const1 = {{.*}}global i16 1 +unsigned _Fract uf_const2 = 0.0546875ur << 3; +// SIGNED-DAG: @uf_const2 = {{.*}}global i16 28672 +// UNSIGNED-DAG: @uf_const2 = {{.*}}global i16 14336 + +_Sat short _Accum ssa_const1 = (_Sat short _Accum)31.875hk << 4; // CHECK-DAG: @ssa_const1 = {{.*}}global i16 32767 +_Sat short _Accum ssa_const2 = (_Sat short _Accum) - 1.0hk << 8; // CHECK-DAG: @ssa_const2 = {{.*}}global i16 -32768 +_Sat short _Accum ssa_const3 = (_Sat short _Accum)128.0hk << 8; // CHECK-DAG: @ssa_const3 = {{.*}}global i16 32767 +_Sat short _Fract ssf_const1 = (_Sat short _Fract) - 0.5hr << 3; // CHECK-DAG: @ssf_const1 = {{.*}}global i8 -128 + +_Sat unsigned _Fract suf_const1 = (_Sat unsigned _Fract)0.5r << 1; +// SIGNED-DAG: @suf_const1 = {{.*}}global i16 -1 +// UNSIGNED-DAG: @suf_const1 = {{.*}}global i16 32767 +_Sat unsigned _Fract suf_const2 = (_Sat unsigned _Fract)0.25r << 1; +// SIGNED-DAG: @suf_const2 = {{.*}}global i16 -32768 +// UNSIGNED-DAG: @suf_const2 = {{.*}}global i16 16384 +_Sat unsigned _Accum sua_const2 = (_Sat unsigned _Accum)128.0uk << 10; +// SIGNED-DAG: @sua_const2 = {{.*}}global i32 -1 +// UNSIGNED-DAG: @sua_const2 = {{.*}}global i32 2147483647