[PATCH] D48661: [Fixed Point Arithmetic] Fixed Point Constant

Bevin Hansson via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Fri Jun 29 05:00:57 PDT 2018


ebevhan added a comment.

I also think it would be good with some unit tests for this class once the functionality and interface is nailed down.



================
Comment at: include/clang/Basic/FixedPoint.h:31
+  SatNoPadding,
+};
+
----------------
rjmccall wrote:
> I figured you'd want this to be a struct which include the scale, width, signed-ness, and saturating-ness; and then `APFixedPoint` can just store one of these next to a bare `APInt`.
That's what I was expecting too, similar to the APFloat version.

What width would it contain? The width with or without padding? Probably the one with padding as the APInt itself has the width without padding.


================
Comment at: include/clang/Basic/FixedPoint.h:53
+  // Change the width and scale of this fixed point number.
+  // Return true if overflow occurrd and false otherwise.
+  bool rescaleAndResize(unsigned DstWidth, unsigned DstScale);
----------------
occurred


================
Comment at: lib/AST/ASTContext.cpp:10320
+
+  if (DstWidth > SrcWidth) Val_ = Val_.extend(DstWidth);
+
----------------
leonardchan wrote:
> ebevhan wrote:
> > If you do rescaling before and after resizing, you can use `extOrTrunc` instead.
> I think having this line just be `Val_ = Val_.extOrTrunc(DstWidth);` would allow for truncation to occur first before potentially right shifting.
True, but I meant something more along the lines of
```
if(DstScale < Scale)
  Val = Val >> (Scale - DstScale);
Val = Val.extOrTrunc(DstWidth);
if(DstScale > Scale)
  Val = Val << (DstScale - Scale);
```
But I think you've changed it a bit so I need to have a look at the revised version.


================
Comment at: lib/Basic/FixedPoint.cpp:20
+
+llvm::APSInt ShrToZero(const llvm::APSInt &Val, unsigned Amt) {
+  if (Val < 0)
----------------
I'm unsure if this operation is useful for anything but converting fixed-point to integer. Make this a member of APFixedPoint, drop the arguments and give it a name that makes it clearer that it is a round-to-zero integer conversion? `toIntegerRoundToZero`?


================
Comment at: lib/Basic/FixedPoint.cpp:53
+    // We can overflow here
+    unsigned ShiftAmt = DstScale - Scale;
+    if (Val < 0 && Val.countLeadingOnes() >= ShiftAmt)
----------------
I think saturation can be modeled a bit better. It should be possible to do the overflow check/saturation once instead of twice, and also have it fit in better with the other conversions.

Saturation on a value is essentially checking whether all bits above and including a certain bit are all the same, and 'clamping' to either the minimum (the mask) or maximum (inverse of the mask) if not.
```
// Saturate Value to SatWidth. This will clamp Value at the signed min/max of a value that is SatWidth long.
Saturate(SatWidth):
  Mask = highBits(Width, SatWidth + 1)
  Masked = Value & Mask
  Result = Value
  if (Masked == Mask || Masked == 0) {
    if (Masked.isNegative())
      Result = Mask
    else
      Result = ~Mask
  }
```
This cannot be done on the value in the source scale, since upscaling after clamping would produce an incorrect value - we would shift in 0's from the right even though we should have saturated all bits completely. Also, we cannot upscale without first extending or we might shift out bits on the left that could affect saturation.

One thing to note is that (I'm ***pretty sure*** this is the case) fractional bits cannot affect saturation. This means that we can find a common scale, then perform the saturation operation, and then resize, and the value should just fall into place. Basically:
  # Upscale if necessary, but extend first to ensure that we don't drop any bits on the left. Do this by resizing to `SrcWidth - SrcScale + std::max(SrcScale, DstScale)` and then upscaling normally by `DstScale - SrcScale`.
  # Downscale if necessary by `SrcScale - DstScale`. (I think; in our downstream, we saturate first and then downscale, but we also assume that saturation can only occur on _Fract, and doing saturation first makes the saturation width calculation a bit messier because it will be a `max` expression. I'm unsure if the order can be changed.)
  # At this point, the scale of the value should be `DstScale`. Saturate with `Saturate(DstWidth)`.
  # Now the value should be in range for the new width, and will be at the right scale as well. Resize with `extOrTrunc(DstWidth)`.
  # (Also; if the result was negative and the dest type is unsigned, just make the result zero here instead.)

Note that the order of operations is different than what I've mentioned before with non-saturated conversion (downscale if needed, resize, upscale if needed), but it works because we only do the upscale as a resize+upscale. Somewhere in here you also need to change the signedness of the value, but I'm not entirely certain where. Likely after 4.

Everything I've mentioned here is semi-conjectural, since our implementation has different type widths than the defaults in this one, we can only saturate on _Fract, and our unsigned types have padding. It's possible that there are some assumptions that cause this method to fail for unsigned without padding, or perhaps for some other type conversion, but I haven't come up with a counterexample yet.


================
Comment at: lib/Basic/FixedPoint.cpp:61
+  } else if (DstScale < Scale) {
+    Val = ShrToZero(Val, Scale - DstScale);
+  }
----------------
This is a bit tricky. The spec does indeed say that conversion to integer must round toward zero, but does not say that conversion between fixed-point types must. If we have these routines perform conversion like this, we would likely have to emit extra code (either an addition or the two negations) during fixed-to-fixed conversion to match the consteval behavior.

I think fixed-to-fixed conversion should keep using a normal shift, and reserve the round-to-zero shift for conversion to integer.


================
Comment at: lib/Basic/FixedPoint.cpp:89
+  if (DstIsSaturated && Overflowed) {
+    if (IsNegative) {
+      // Use min
----------------
You can use getMax and getMin here to simplify the logic, I think?

As I mention in my other comment I think that the saturation logic can be more strongly integrated into the conversion.


================
Comment at: lib/Basic/FixedPoint.cpp:114
+  if (Scale < OtherScale)
+    CommonWidth += OtherScale - Scale;
+  else if (Scale > OtherScale)
----------------
```
if (Scale != OtherScale)
  CommonWidth += std::abs(OtherScale - Scale)
```
They need to be `int`, of course.



================
Comment at: lib/Basic/FixedPoint.cpp:122
+  unsigned CommonScale = std::max(Scale, OtherScale);
+  if (Scale < CommonScale) ThisVal = ThisVal.shl(CommonScale - Scale);
+  if (OtherScale < CommonScale)
----------------
Are the if's needed? If `CommonScale == Scale` or `OtherScale`, `shl` will do nothing.


Repository:
  rC Clang

https://reviews.llvm.org/D48661





More information about the cfe-commits mailing list