ebevhan added inline comments.

================
Comment at: lib/Sema/SemaExpr.cpp:1248
+  bool RHSFixed = RHSType->isFixedPointType();
+
+  if (LHSFixed && RHSFixed) {
----------------
ebevhan wrote:
> leonardchan wrote:
> > ebevhan wrote:
> > > leonardchan wrote:
> > > > ebevhan wrote:
> > > > > I don't see how these semantics work properly. The specification 
> > > > > requires that operations be done in the full precision of both types. 
> > > > > You cannot convert the types before performing the operation like 
> > > > > this, since the operation will not be done in full precision in that 
> > > > > case.
> > > > > 
> > > > > The operator semantics of Embedded-C require the operand types of 
> > > > > binary operators to be different. It's only when you've performed the 
> > > > > operation that you are allowed to convert the result to the resulting 
> > > > > type.
> > > > Initially the idea was to convert both sides to fixed point types, then 
> > > > perform standard binary operations between the fixed point types.
> > > > 
> > > > For the example, a `fract * int` would have the int converted to a 
> > > > fixed point type by left shifting it by the scale of the fract, 
> > > > multiplying, then right shifting by the scale again to get the 
> > > > resulting fract. The only unhandled thing is overflow, but the 
> > > > precision of the fract remains the same. The operands would also be 
> > > > casted up beforehand so there was enough space to store the result, 
> > > > which was casted down back to the original fract after performing the 
> > > > right shift by the scale.
> > > > 
> > > > Operations between fixed point types would follow a similar process of 
> > > > casting both operands to the higher rank fixed point type, and 
> > > > depending on the operation, more underlying shifting and casting would 
> > > > be done to retain full precision of the higher ranked type.
> > > > 
> > > > Though I will admit that I did not realize until now that multiplying a 
> > > > fixed point type by an integer does not require shifting the integer.
> > > I see how you've reasoned; this is how C normally works. The `fract` is 
> > > of higher rank than `int` and therefore is the 'common type' of the 
> > > operation. However, even though it is higher rank there is no guarantee 
> > > that you can perform the operation without overflowing. And overflow 
> > > matters here; the spec says that it must be done in the full precision 
> > > (integral + fractional) of both types.
> > > 
> > > > The only unhandled thing is overflow, but the precision of the fract 
> > > > remains the same. The operands would also be casted up beforehand so 
> > > > there was enough space to store the result, which was casted down back 
> > > > to the original fract after performing the right shift by the scale.
> > > 
> > > The precision remains the same (and while it doesn't have to be the same 
> > > to perform an operation, it makes the implementation more regular; things 
> > > like addition and subtraction 'just work'), but you cannot perform a 
> > > conversion to `fract` *before* the operation itself, since if you do, 
> > > there's nothing to 'cast up'. Casting up is needed for things like `fract 
> > > * fract` to prevent overflow, but for `fract * int` you need to cast to a 
> > > type that can fit both all values of the int and all values of the fract, 
> > > and *then* you can cast up before doing the multiplication.
> > > 
> > > > Operations between fixed point types would follow a similar process of 
> > > > casting both operands to the higher rank fixed point type, and 
> > > > depending on the operation, more underlying shifting and casting would 
> > > > be done to retain full precision of the higher ranked type.
> > > 
> > > This might work, but I feel there could be edge cases. The E-C 
> > > fixed-point ranks are very odd as they don't reflect reality; `short 
> > > _Accum` cannot be considered strictly 'above' `long _Fract`, but the 
> > > former has a higher rank than the latter. Depending on how the types are 
> > > specified for a target, implicit casts between fixed-point types might 
> > > inadvertantly discard bits, even though the spec says that operations 
> > > must be done in full precision.
> > I see, so just to confirm, something like a `fract * int` would not result 
> > in any implicit casting between either operand, but any special arithmetic, 
> > like intermediate storage types or saturation handling, would be handled by 
> > the underlying IR?
> > 
> > So should really no conversions/implicit type casting should be performed 
> > here and instead all handling of arithmetic operations should happen 
> > somewhere during the codegen stage?
> > 
> > I see, so just to confirm, something like a fract * int would not result in 
> > any implicit casting between either operand, but any special arithmetic, 
> > like intermediate storage types or saturation handling, would be handled by 
> > the underlying IR?
> 
> Yes, for operations which require precision that cannot be provided by any of 
> the existing types, there must be an 'invisible' implicit conversion to a 
> type which can represent all of the values of either operand. This conversion 
> cannot be represented in the AST as it is today.
> 
> The simplest solution is indeed to not have any implicit cast at all in the 
> AST and resolve these conversions when needed (CodeGen and consteval are the 
> locations I can think of), but ultimately it feels a bit dirty... I think 
> that the best solution AST-wise is to define a completely new type class 
> (perhaps FullPrecisionFixedPointType) that represents a fixed-point type with 
> arbitrary width, scale, signedness and saturation. Then you can define 
> ImplicitCasts to an instance of this type that can fit both the `int` and the 
> `fract`. I don't know if adding this is acceptable upstream, though.
> 
> I think all of these rules must apply to fixed-fixed operations as well; a 
> `short accum * long fract` must be done as a type that does not exist, 
> similar to fixed-int. It's not clear how saturation should work here either...
> 
> I also noticed now that the spec says in regards to comparison operators, 
> `When comparing fixed-point values with fixed-point values or integer values, 
> the values are compared directly; the values of the operands are not 
> converted before the comparison is made.` I'm not sure what this means.
In any case, to clarify, I think there are two paths to consider. Either:

  - Add a new type class to the type system that encapsulates an 
arbitrary-precision fixed-point type that can be used for implicit casts when 
operating on fixed-point and integer types. This is in my opinion the cleaner 
solution, since it retains invariants on the types of operators and simplifies 
any logic that deals with operators; or,
  - Leave the operands of these operations uncasted. This is in some way 
simpler, since it doesn't require adding a whole new type, but it complicates 
other parts of the code. Anything that wants to deal with fixed-point operators 
will need to know how to do fixed-point conversion as well, which isn't a very 
good separation of responsibility IMO. It also breaks the C invariant of 
operands of arithmetic types being in a common type, which might be surprising 
to people.




Repository:
  rC Clang

https://reviews.llvm.org/D46915



_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to