Skip to content

Commit

Permalink
[Fixed Point Arithmetic] Fixed Point Constant
Browse files Browse the repository at this point in the history
This patch proposes an abstract type that represents fixed point numbers, similar to APInt or APSInt that was discussed in https://reviews.llvm.org/D48456#inline-425585. This type holds a value, scale, and saturation and is meant to perform intermediate calculations on constant fixed point values.

Currently this class is used as a way for handling the conversions between fixed point numbers with different sizes and radixes. For example, if I'm casting from a signed _Accum to a saturated unsigned short _Accum, I will need to check the value of the signed _Accum to see if it fits into the short _Accum which involves getting and comparing against the max/min values of the short _Accum. The FixedPointNumber class currently handles the radix shifting and extension when converting to a signed _Accum.

Differential Revision: https://reviews.llvm.org/D48661

llvm-svn: 339028
  • Loading branch information
PiJoules committed Aug 6, 2018
1 parent a489d11 commit a677942
Show file tree
Hide file tree
Showing 10 changed files with 991 additions and 6 deletions.
5 changes: 5 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Expand Up @@ -79,6 +79,7 @@ struct fltSemantics;

namespace clang {

class APFixedPoint;
class APValue;
class ASTMutationListener;
class ASTRecordLayout;
Expand All @@ -92,6 +93,7 @@ class CXXMethodDecl;
class CXXRecordDecl;
class DiagnosticsEngine;
class Expr;
class FixedPointSemantics;
class MangleContext;
class MangleNumberingContext;
class MaterializeTemporaryExpr;
Expand Down Expand Up @@ -1961,6 +1963,9 @@ class ASTContext : public RefCountedBase<ASTContext> {

unsigned char getFixedPointScale(QualType Ty) const;
unsigned char getFixedPointIBits(QualType Ty) const;
FixedPointSemantics getFixedPointSemantics(QualType Ty) const;
APFixedPoint getFixedPointMax(QualType Ty) const;
APFixedPoint getFixedPointMin(QualType Ty) const;

DeclarationNameInfo getNameForTemplate(TemplateName Name,
SourceLocation NameLoc) const;
Expand Down
138 changes: 138 additions & 0 deletions clang/include/clang/Basic/FixedPoint.h
@@ -0,0 +1,138 @@
//===- FixedPoint.h - Fixed point constant handling -------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
/// \file
/// Defines the fixed point number interface.
/// This is a class for abstracting various operations performed on fixed point
/// types described in ISO/IEC JTC1 SC22 WG14 N1169 starting at clause 4.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_BASIC_FIXEDPOINT_H
#define LLVM_CLANG_BASIC_FIXEDPOINT_H

#include "llvm/ADT/APSInt.h"

namespace clang {

class ASTContext;
class QualType;

/// The fixed point semantics work similarly to llvm::fltSemantics. The width
/// specifies the whole bit width of the underlying scaled integer (with padding
/// if any). The scale represents the number of fractional bits in this type.
/// When HasUnsignedPadding is true and this type is signed, the first bit
/// in the value this represents is treaded as padding.
class FixedPointSemantics {
public:
FixedPointSemantics(unsigned Width, unsigned Scale, bool IsSigned,
bool IsSaturated, bool HasUnsignedPadding)
: Width(Width), Scale(Scale), IsSigned(IsSigned),
IsSaturated(IsSaturated), HasUnsignedPadding(HasUnsignedPadding) {
assert(Width >= Scale && "Not enough room for the scale");
}

unsigned getWidth() const { return Width; }
unsigned getScale() const { return Scale; }
bool isSigned() const { return IsSigned; }
bool isSaturated() const { return IsSaturated; }
bool hasUnsignedPadding() const { return HasUnsignedPadding; }

void setSaturated(bool Saturated) { IsSaturated = Saturated; }

unsigned getIntegralBits() const {
if (IsSigned || (!IsSigned && HasUnsignedPadding))
return Width - Scale - 1;
else
return Width - Scale;
}

private:
unsigned Width;
unsigned Scale;
bool IsSigned;
bool IsSaturated;
bool HasUnsignedPadding;
};

/// The APFixedPoint class works similarly to APInt/APSInt in that it is a
/// functional replacement for a scaled integer. It is meant to replicate the
/// fixed point types proposed in ISO/IEC JTC1 SC22 WG14 N1169. The class carries
/// info about the fixed point type's width, sign, scale, and saturation, and
/// provides different operations that would normally be performed on fixed point
/// types.
///
/// Semantically this does not represent any existing C type other than fixed
/// point types and should eventually be moved to LLVM if fixed point types gain
/// native IR support.
class APFixedPoint {
public:
APFixedPoint(const llvm::APInt &Val, const FixedPointSemantics &Sema)
: Val(Val, !Sema.isSigned()), Sema(Sema) {
assert(Val.getBitWidth() == Sema.getWidth() &&
"The value should have a bit width that matches the Sema width");
}

APFixedPoint(uint64_t Val, const FixedPointSemantics &Sema)
: APFixedPoint(llvm::APInt(Sema.getWidth(), Val, Sema.isSigned()),
Sema) {}

llvm::APSInt getValue() const { return llvm::APSInt(Val, !Sema.isSigned()); }
inline unsigned getWidth() const { return Sema.getWidth(); }
inline unsigned getScale() const { return Sema.getScale(); }
inline bool isSaturated() const { return Sema.isSaturated(); }
inline bool isSigned() const { return Sema.isSigned(); }
inline bool hasPadding() const { return Sema.hasUnsignedPadding(); }

// Convert this number to match the semantics provided.
APFixedPoint convert(const FixedPointSemantics &DstSema) const;

APFixedPoint shr(unsigned Amt) const {
return APFixedPoint(Val >> Amt, Sema);
}

APFixedPoint shl(unsigned Amt) const {
return APFixedPoint(Val << Amt, Sema);
}

llvm::APSInt getIntPart() const {
if (Val < 0 && Val != -Val) // Cover the case when we have the min val
return -(-Val >> getScale());
else
return Val >> getScale();
}

// If LHS > RHS, return 1. If LHS == RHS, return 0. If LHS < RHS, return -1.
int compare(const APFixedPoint &Other) const;
bool operator==(const APFixedPoint &Other) const {
return compare(Other) == 0;
}
bool operator!=(const APFixedPoint &Other) const {
return compare(Other) != 0;
}
bool operator>(const APFixedPoint &Other) const { return compare(Other) > 0; }
bool operator<(const APFixedPoint &Other) const { return compare(Other) < 0; }
bool operator>=(const APFixedPoint &Other) const {
return compare(Other) >= 0;
}
bool operator<=(const APFixedPoint &Other) const {
return compare(Other) <= 0;
}

static APFixedPoint getMax(const FixedPointSemantics &Sema);
static APFixedPoint getMin(const FixedPointSemantics &Sema);

private:
llvm::APSInt Val;
FixedPointSemantics Sema;
};

} // namespace clang

#endif
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/TargetInfo.h
Expand Up @@ -312,6 +312,14 @@ class TargetInfo : public RefCountedBase<TargetInfo> {
}
}

/// In the event this target uses the same number of fractional bits for its
/// unsigned types as it does with its signed counterparts, there will be
/// exactly one bit of padding.
/// Return true if unsigned fixed point types have padding for this target.
bool doUnsignedFixedPointTypesHavePadding() const {
return PaddingOnUnsignedFixedPoint;
}

/// Return the width (in bits) of the specified integer type enum.
///
/// For example, SignedInt -> getIntWidth().
Expand Down
20 changes: 20 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Expand Up @@ -48,6 +48,7 @@
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CommentOptions.h"
#include "clang/Basic/ExceptionSpecificationType.h"
#include "clang/Basic/FixedPoint.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/LangOptions.h"
Expand Down Expand Up @@ -10433,3 +10434,22 @@ unsigned char ASTContext::getFixedPointIBits(QualType Ty) const {
return 0;
}
}

FixedPointSemantics ASTContext::getFixedPointSemantics(QualType Ty) const {
assert(Ty->isFixedPointType());
bool isSigned = Ty->isSignedFixedPointType();
return FixedPointSemantics(
static_cast<unsigned>(getTypeSize(Ty)), getFixedPointScale(Ty), isSigned,
Ty->isSaturatedFixedPointType(),
!isSigned && getTargetInfo().doUnsignedFixedPointTypesHavePadding());
}

APFixedPoint ASTContext::getFixedPointMax(QualType Ty) const {
assert(Ty->isFixedPointType());
return APFixedPoint::getMax(getFixedPointSemantics(Ty));
}

APFixedPoint ASTContext::getFixedPointMin(QualType Ty) const {
assert(Ty->isFixedPointType());
return APFixedPoint::getMin(getFixedPointSemantics(Ty));
}
1 change: 1 addition & 0 deletions clang/lib/Basic/CMakeLists.txt
Expand Up @@ -54,6 +54,7 @@ add_clang_library(clangBasic
DiagnosticOptions.cpp
FileManager.cpp
FileSystemStatCache.cpp
FixedPoint.cpp
IdentifierTable.cpp
LangOptions.cpp
MemoryBufferCache.cpp
Expand Down
115 changes: 115 additions & 0 deletions clang/lib/Basic/FixedPoint.cpp
@@ -0,0 +1,115 @@
//===- FixedPoint.cpp - Fixed point constant handling -----------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
/// \file
/// Defines the implementation for the fixed point number interface.
//
//===----------------------------------------------------------------------===//

#include "clang/AST/ASTContext.h"
#include "clang/Basic/FixedPoint.h"

namespace clang {

APFixedPoint APFixedPoint::convert(const FixedPointSemantics &DstSema) const {
llvm::APSInt NewVal = Val;
unsigned DstWidth = DstSema.getWidth();
unsigned DstScale = DstSema.getScale();
bool Upscaling = DstScale > getScale();

if (Upscaling) {
NewVal = NewVal.extend(NewVal.getBitWidth() + DstScale - getScale());
NewVal <<= (DstScale - getScale());
} else {
NewVal >>= (getScale() - DstScale);
}

if (DstSema.isSaturated()) {
auto Mask = llvm::APInt::getBitsSetFrom(
NewVal.getBitWidth(),
std::min(DstScale + DstSema.getIntegralBits(), NewVal.getBitWidth()));
llvm::APInt Masked(NewVal & Mask);

// Change in the bits above the sign
if (!(Masked == Mask || Masked == 0))
NewVal = NewVal.isNegative() ? Mask : ~Mask;

if (!DstSema.isSigned() && NewVal.isNegative())
NewVal = 0;
}

NewVal = NewVal.extOrTrunc(DstWidth);
NewVal.setIsSigned(DstSema.isSigned());
return APFixedPoint(NewVal, DstSema);
}

int APFixedPoint::compare(const APFixedPoint &Other) const {
llvm::APSInt ThisVal = getValue();
llvm::APSInt OtherVal = Other.getValue();
bool ThisSigned = Val.isSigned();
bool OtherSigned = OtherVal.isSigned();
unsigned OtherScale = Other.getScale();
unsigned OtherWidth = OtherVal.getBitWidth();

unsigned CommonWidth = std::max(Val.getBitWidth(), OtherWidth);

// Prevent overflow in the event the widths are the same but the scales differ
CommonWidth += std::abs(static_cast<int>(getScale() - OtherScale));

ThisVal = ThisVal.extOrTrunc(CommonWidth);
OtherVal = OtherVal.extOrTrunc(CommonWidth);

unsigned CommonScale = std::max(getScale(), OtherScale);
ThisVal = ThisVal.shl(CommonScale - getScale());
OtherVal = OtherVal.shl(CommonScale - OtherScale);

if (ThisSigned && OtherSigned) {
if (ThisVal.sgt(OtherVal))
return 1;
else if (ThisVal.slt(OtherVal))
return -1;
} else if (!ThisSigned && !OtherSigned) {
if (ThisVal.ugt(OtherVal))
return 1;
else if (ThisVal.ult(OtherVal))
return -1;
} else if (ThisSigned && !OtherSigned) {
if (ThisVal.isSignBitSet())
return -1;
else if (ThisVal.ugt(OtherVal))
return 1;
else if (ThisVal.ult(OtherVal))
return -1;
} else {
// !ThisSigned && OtherSigned
if (OtherVal.isSignBitSet())
return 1;
else if (ThisVal.ugt(OtherVal))
return 1;
else if (ThisVal.ult(OtherVal))
return -1;
}

return 0;
}

APFixedPoint APFixedPoint::getMax(const FixedPointSemantics &Sema) {
bool IsUnsigned = !Sema.isSigned();
auto Val = llvm::APSInt::getMaxValue(Sema.getWidth(), IsUnsigned);
if (IsUnsigned && Sema.hasUnsignedPadding())
Val = Val.lshr(1);
return APFixedPoint(Val, Sema);
}

APFixedPoint APFixedPoint::getMin(const FixedPointSemantics &Sema) {
auto Val = llvm::APSInt::getMinValue(Sema.getWidth(), !Sema.isSigned());
return APFixedPoint(Val, Sema);
}

} // namespace clang
9 changes: 4 additions & 5 deletions clang/lib/Sema/SemaExpr.cpp
Expand Up @@ -26,6 +26,7 @@
#include "clang/AST/ExprOpenMP.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/FixedPoint.h"
#include "clang/Basic/PartialDiagnostic.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
Expand Down Expand Up @@ -3363,16 +3364,14 @@ ExprResult Sema::ActOnNumericConstant(const Token &Tok, Scope *UDLScope) {

bool isSigned = !Literal.isUnsigned;
unsigned scale = Context.getFixedPointScale(Ty);
unsigned ibits = Context.getFixedPointIBits(Ty);
unsigned bit_width = Context.getTypeInfo(Ty).Width;

llvm::APInt Val(bit_width, 0, isSigned);
bool Overflowed = Literal.GetFixedPointValue(Val, scale);
bool ValIsZero = Val.isNullValue() && !Overflowed;

// Do not use bit_width since some types may have padding like _Fract or
// unsigned _Accums if PaddingOnUnsignedFixedPoint is set.
auto MaxVal = llvm::APInt::getMaxValue(ibits + scale).zextOrSelf(bit_width);
if (Literal.isFract && Val == MaxVal + 1)
auto MaxVal = Context.getFixedPointMax(Ty).getValue();
if (Literal.isFract && Val == MaxVal + 1 && !ValIsZero)
// Clause 6.4.4 - The value of a constant shall be in the range of
// representable values for its type, with exception for constants of a
// fract type with a value of exactly 1; such a constant shall denote
Expand Down
16 changes: 15 additions & 1 deletion clang/test/Frontend/fixed_point_declarations.c
@@ -1,5 +1,4 @@
// RUN: %clang -ffixed-point -S -emit-llvm %s -o - --target=x86_64-linux | FileCheck %s
// RUN: %clang -ffixed-point -S -emit-llvm %s -o - --target=x86_64-scei-ps4-ubuntu-fast | FileCheck %s

// Primary fixed point types
signed short _Accum s_short_accum; // CHECK-DAG: @s_short_accum = {{.*}}global i16 0, align 2
Expand Down Expand Up @@ -111,3 +110,18 @@ long _Fract long_fract_eps = 0x1p-31lr; // CHECK-DAG: @long_
unsigned short _Fract u_short_fract_eps = 0x1p-8uhr; // CHECK-DAG: @u_short_fract_eps = {{.*}}global i8 1, align 1
unsigned _Fract u_fract_eps = 0x1p-16ur; // CHECK-DAG: @u_fract_eps = {{.*}}global i16 1, align 2
unsigned long _Fract u_long_fract_eps = 0x1p-32ulr; // CHECK-DAG: @u_long_fract_eps = {{.*}}global i32 1, align 4

// Zero
short _Accum short_accum_zero = 0.0hk; // CHECK-DAG: @short_accum_zero = {{.*}}global i16 0, align 2
_Accum accum_zero = 0.0k; // CHECK-DAG: @accum_zero = {{.*}}global i32 0, align 4
long _Accum long_accum_zero = 0.0lk; // CHECK-DAG: @long_accum_zero = {{.*}}global i64 0, align 8
unsigned short _Accum u_short_accum_zero = 0.0uhk; // CHECK-DAG: @u_short_accum_zero = {{.*}}global i16 0, align 2
unsigned _Accum u_accum_zero = 0.0uk; // CHECK-DAG: @u_accum_zero = {{.*}}global i32 0, align 4
unsigned long _Accum u_long_accum_zero = 0.0ulk; // CHECK-DAG: @u_long_accum_zero = {{.*}}global i64 0, align 8

short _Fract short_fract_zero = 0.0hr; // CHECK-DAG: @short_fract_zero = {{.*}}global i8 0, align 1
_Fract fract_zero = 0.0r; // CHECK-DAG: @fract_zero = {{.*}}global i16 0, align 2
long _Fract long_fract_zero = 0.0lr; // CHECK-DAG: @long_fract_zero = {{.*}}global i32 0, align 4
unsigned short _Fract u_short_fract_zero = 0.0uhr; // CHECK-DAG: @u_short_fract_zero = {{.*}}global i8 0, align 1
unsigned _Fract u_fract_zero = 0.0ur; // CHECK-DAG: @u_fract_zero = {{.*}}global i16 0, align 2
unsigned long _Fract u_long_fract_zero = 0.0ulr; // CHECK-DAG: @u_long_fract_zero = {{.*}}global i32 0, align 4

0 comments on commit a677942

Please sign in to comment.