ebevhan added inline comments.
================ Comment at: clang/lib/Sema/SemaExpr.cpp:1304 + RHSTy = ResultTy; + } + ---------------- rjmccall wrote: > ebevhan wrote: > > rjmccall wrote: > > > leonardchan wrote: > > > > rjmccall wrote: > > > > > leonardchan wrote: > > > > > > rjmccall wrote: > > > > > > > leonardchan wrote: > > > > > > > > rjmccall wrote: > > > > > > > > > ebevhan wrote: > > > > > > > > > > rjmccall wrote: > > > > > > > > > > > leonardchan wrote: > > > > > > > > > > > > rjmccall wrote: > > > > > > > > > > > > > ebevhan wrote: > > > > > > > > > > > > > > rjmccall wrote: > > > > > > > > > > > > > > > Hmm. So adding a signed integer to an unsigned > > > > > > > > > > > > > > > fixed-point type always converts the integer to > > > > > > > > > > > > > > > unsigned? That's a little weird, but only > > > > > > > > > > > > > > > because the fixed-point rule seems to be the > > > > > > > > > > > > > > > other way. Anyway, I assume it's not a bug in > > > > > > > > > > > > > > > the spec. > > > > > > > > > > > > > > `handleFixedPointConversion` only calculates the > > > > > > > > > > > > > > result type of the expression, not the calculation > > > > > > > > > > > > > > type. The final result type of the operation will > > > > > > > > > > > > > > be the unsigned fixed-point type, but the > > > > > > > > > > > > > > calculation itself will be done in a signed type > > > > > > > > > > > > > > with enough precision to represent both the signed > > > > > > > > > > > > > > integer and the unsigned fixed-point type. > > > > > > > > > > > > > > > > > > > > > > > > > > > > Though, if the result ends up being negative, the > > > > > > > > > > > > > > final result is undefined unless the type is > > > > > > > > > > > > > > saturating. I don't think there is a test for the > > > > > > > > > > > > > > saturating case (signed integer + unsigned > > > > > > > > > > > > > > saturating fixed-point) in the SaturatedAddition > > > > > > > > > > > > > > tests. > > > > > > > > > > > > > > `handleFixedPointConversion` only calculates the > > > > > > > > > > > > > > result type of the expression, not the calculation > > > > > > > > > > > > > > type. > > > > > > > > > > > > > > > > > > > > > > > > > > Right, I understand that, but the result type of the > > > > > > > > > > > > > expression obviously impacts the expressive range of > > > > > > > > > > > > > the result, since you can end up with a negative > > > > > > > > > > > > > value. > > > > > > > > > > > > > > > > > > > > > > > > > > Hmm. With that said, if the general principle is to > > > > > > > > > > > > > perform the operation with full precision on the > > > > > > > > > > > > > original operand values, why are unsigned fixed-point > > > > > > > > > > > > > operands coerced to their corresponding signed types > > > > > > > > > > > > > *before* the operation? This is precision-limiting > > > > > > > > > > > > > if the unsigned representation doesn't use a padding > > > > > > > > > > > > > bit. That seems like a bug in the spec. > > > > > > > > > > > > > Hmm. With that said, if the general principle is to > > > > > > > > > > > > > perform the operation with full precision on the > > > > > > > > > > > > > original operand values, why are unsigned fixed-point > > > > > > > > > > > > > operands coerced to their corresponding signed types > > > > > > > > > > > > > *before* the operation? This is precision-limiting if > > > > > > > > > > > > > the unsigned representation doesn't use a padding > > > > > > > > > > > > > bit. That seems like a bug in the spec. > > > > > > > > > > > > > > > > > > > > > > > > Possibly. When the standard mentions converting from > > > > > > > > > > > > signed to unsigned fixed point, the only requirement > > > > > > > > > > > > involved is conservation of magnitude (the number of > > > > > > > > > > > > integral bits excluding the sign) > > > > > > > > > > > > > > > > > > > > > > > > ``` > > > > > > > > > > > > when signed and unsigned fixed-point types are mixed, > > > > > > > > > > > > the unsigned type is converted to the corresponding > > > > > > > > > > > > signed type, and this should go without loss of > > > > > > > > > > > > magnitude > > > > > > > > > > > > ``` > > > > > > > > > > > > > > > > > > > > > > > > This is just speculation, but I'm under the impression > > > > > > > > > > > > that not as much "attention" was given for unsigned > > > > > > > > > > > > types as for signed types since "`In the embedded > > > > > > > > > > > > processor world, support for unsigned fixed-point data > > > > > > > > > > > > types is rare; normally only signed fixed-point data > > > > > > > > > > > > types are supported`", but was kept anyway since > > > > > > > > > > > > unsigned types are used a lot. > > > > > > > > > > > > > > > > > > > > > > > > ``` > > > > > > > > > > > > However, to disallow unsigned fixed-point arithmetic > > > > > > > > > > > > from programming languages (in general, and from C in > > > > > > > > > > > > particular) based on this observation, seems overly > > > > > > > > > > > > restrictive. > > > > > > > > > > > > ``` > > > > > > > > > > > > > > > > > > > > > > > > I also imagine that if the programmer needs more > > > > > > > > > > > > precision, the correct approach would be to cast up to > > > > > > > > > > > > a type with a higher scale. The standard seems to make > > > > > > > > > > > > an effort to expose as much in regards to the > > > > > > > > > > > > underlying fixed point types as possible: > > > > > > > > > > > > > > > > > > > > > > > > ``` > > > > > > > > > > > > it should be possible to write fixed-point algorithms > > > > > > > > > > > > that are independent of the actual fixed-point hardware > > > > > > > > > > > > support. This implies that a programmer (or a running > > > > > > > > > > > > program) should have access to all parameters that > > > > > > > > > > > > define the behavior of the underlying hardware (in > > > > > > > > > > > > other words: even if these parameters are > > > > > > > > > > > > implementation-defined). > > > > > > > > > > > > ``` > > > > > > > > > > > > > > > > > > > > > > > > So the user would at least know that unsigned types may > > > > > > > > > > > > not have the same scale as their corresponding signed > > > > > > > > > > > > types if the hardware handles them with different > > > > > > > > > > > > scales. > > > > > > > > > > > > > > > > > > > > > > > > Also added test for signed integer + unsigned > > > > > > > > > > > > saturating fixed point. > > > > > > > > > > > As long as we maintain the same typing behavior, does the > > > > > > > > > > > standard permit us to just Do The Right Thing here and do > > > > > > > > > > > the extended arithmetic with the original unsigned > > > > > > > > > > > operand? I'm sure there are some cases where we would > > > > > > > > > > > produce a slightly different value than an implementation > > > > > > > > > > > that does the coercion before the operation, but that > > > > > > > > > > > might be permitted under the standard, and as you say, it > > > > > > > > > > > would only affect some situations that it doesn't seem > > > > > > > > > > > the standard has given much thought to. > > > > > > > > > > The coercion of unsigned to signed is likely done for > > > > > > > > > > pragmatic reasons; if you have a signed and unsigned type > > > > > > > > > > of the same rank, operating on them together won't require > > > > > > > > > > any 'extra bits'. This means that if your hardware has > > > > > > > > > > native support for the operations, you won't end up in a > > > > > > > > > > situation where you really need 33 or 34 bits (for a 32 bit > > > > > > > > > > value) to actually perform the operation. This is one of > > > > > > > > > > the benefits of these kinds of implicit conversions. > > > > > > > > > > > > > > > > > > > > It probably makes the implementation a bit easier as well, > > > > > > > > > > since you don't have to consider cases where both sides > > > > > > > > > > have different signedness. The loss of the extra bit of > > > > > > > > > > precision from the unsigned operand is probably not > > > > > > > > > > considered to be very important. As Leonard said, if you > > > > > > > > > > wanted higher precision you can simply use the next rank of > > > > > > > > > > types. ...Of course, they seem to have forgotten that that > > > > > > > > > > exact issue with differing signedness also applies to > > > > > > > > > > operations with fixed-point and integer operands. > > > > > > > > > > > > > > > > > > > > Personally, I think the spec could be a bit more > > > > > > > > > > restrictive with how it handles these conversions, as the > > > > > > > > > > way they are now makes it really hard to generate code > > > > > > > > > > sensibly for machines that have native support for these > > > > > > > > > > kinds of operations; in many cases you'll end up requiring > > > > > > > > > > a couple bits too many or a couple bits too few to fit in a > > > > > > > > > > sensible register size, and ensuring that the scales match > > > > > > > > > > what your hardware can handle is also a challenge. > > > > > > > > > > The coercion of unsigned to signed is likely done for > > > > > > > > > > pragmatic reasons; if you have a signed and unsigned type > > > > > > > > > > of the same rank, operating on them together won't require > > > > > > > > > > any 'extra bits'. This means that if your hardware has > > > > > > > > > > native support for the operations, you won't end up in a > > > > > > > > > > situation where you really need 33 or 34 bits (for a 32 bit > > > > > > > > > > value) to actually perform the operation. This is one of > > > > > > > > > > the benefits of these kinds of implicit conversions. > > > > > > > > > > > > > > > > > > Yeah, I can see that. I think it really depends on the > > > > > > > > > target. If you have hardware support, and you're using an > > > > > > > > > unpadded representation for unsigned fixed-point values, then > > > > > > > > > preserving the extra bit on mixed-signedness operations will > > > > > > > > > sometimes prevent you from using the hardware optimally. On > > > > > > > > > the other hand, if you don't have hardware support, > > > > > > > > > preserving the extra bit is usually easier than not: e.g., > > > > > > > > > IIUC, multiplying a 32-bit signed _Fract with a 32-bit > > > > > > > > > unpadded unsigned _Fract means just doing a 64-bit > > > > > > > > > multiplication and dropping the low 32 bits, whereas doing > > > > > > > > > the conversion first actually requires extra operations to > > > > > > > > > ensure that the low bit doesn't contribute to the result. > > > > > > > > > And a target with hardware support is probably going to use a > > > > > > > > > padded format for unsigned fixed-point precisely so that it > > > > > > > > > can take advantage of the hardware. > > > > > > > > > > > > > > > > > > > ...Of course, they seem to have forgotten that that exact > > > > > > > > > > issue with differing signedness also applies to operations > > > > > > > > > > with fixed-point and integer operands. > > > > > > > > > > > > > > > > > > Right. > > > > > > > > For signed-unsigned multiplication, we could do that without > > > > > > > > having to do the conversion first, but in the case of addition > > > > > > > > and subtraction, we would still need to drop a bit with > > > > > > > > unpadded unsigned types so both have the same scales. > > > > > > > > > > > > > > > > I don't want to make too many assumptions about hardware > > > > > > > > support, but I imagine that if one supports 32 bit signed > > > > > > > > addition, and unsigned types did not have padding, it would be > > > > > > > > better instead to LSHR on unsigned fixed point type then do 32 > > > > > > > > bit signed addition rather than SEXT+SHL on the signed fixed > > > > > > > > point type then do 33 bit signed addition. > > > > > > > > [I}n the case of addition and subtraction, we would still need > > > > > > > > to drop a bit with unpadded unsigned types so both have the > > > > > > > > same scales. > > > > > > > > > > > > > > Right, but I don't think it matters because you're dropping that > > > > > > > bit either way: with the specified semantics, you do the > > > > > > > conversion beforehand which drops the bit, and with > > > > > > > full-precision semantics, the bit isn't going to be representable > > > > > > > and dropping it during the computation is equivalent to either > > > > > > > rounding down (addition) or up (subtraction), either of which is > > > > > > > allowed. > > > > > > > > > > > > > > > I don't want to make too many assumptions about hardware > > > > > > > > support, but I imagine that if one supports 32 bit signed > > > > > > > > addition, and unsigned types did not have padding, it would be > > > > > > > > better instead to LSHR on unsigned fixed point type then do 32 > > > > > > > > bit signed addition rather than SEXT+SHL on the signed fixed > > > > > > > > point type then do 33 bit signed addition. > > > > > > > > > > > > > > My point is just that (1) the 33-bit signed addition can just be > > > > > > > a 32-bit signed addition given that you ultimately have to > > > > > > > produce a 32-bit result and (2) targets with hardware support are > > > > > > > unlikely to use an unpadded unsigned type anyway. Although I > > > > > > > suppose (2) is a bit questionable because hardware support could > > > > > > > be added well after an ABI has been settled on. > > > > > > Ok. So should anything be changed here then if both semantics work? > > > > > > Currently we still do the 32 bit addition for signed and unsigned > > > > > > types. > > > > > Well, most of what we're doing here is trying to lay good groundwork > > > > > for all operations, and "both semantics work" is only true for > > > > > same-rank addition, which is why I'm asking whether we should > > > > > consider what semantics we actually want in general. > > > > I think the semantics for all binary arithmetic operators (+, -, *, /) > > > > can be simplified to: > > > > > > > > 1. If one operand is a signed fixed point type and the other an > > > > unsigned type, convert the unsigned type to a signed type (which may > > > > involve reducing the scale). > > > > 2. Find the common type of both operands that can hold both the larger > > > > scale and magnitude of the 2 operands, and cast them to this common > > > > type. > > > > 3. Perform the binary operation. > > > > 4. Cast to result type. > > > > > > > > I think the semantics for addition are already settled with these > > > > semantics, and I believe subtraction shares the same semantics as those > > > > for addition. As you said before, the extra bit would not even matter > > > > with full precision since it would be trimmed anyway when converting > > > > from the common type to the result, and rounding can be either up or > > > > down. > > > > > > > > Signed integer addition/subtraction with an unsigned fixed point type > > > > with these semantics will only lead to undefined behavior if the > > > > difference in the values between both types is negative since a > > > > negative value cannot be represented by an unsigned fixed point type > > > > (unless the result is saturating). > > > > > > > > For multiplication and division, the result could technically be > > > > calculated also without having to cast between both sides, although for > > > > using the fixed point mul/div intrinsics, both operands would need to > > > > be casted to a common type with the same scale anyway. If a bit does > > > > end up getting dropped when casting an operand from unsigned to signed > > > > in (1), that dropped bit would not be representable when casting to the > > > > result anyway and the standard still allows us to return a rounded > > > > result. > > > > > > > > When multiplying or dividing between a fixed point and integer though, > > > > we could just perform a regular integer mul/div if the result is > > > > non-saturating without having to perform the steps above. For > > > > saturating mul/div with an int, it might be easier to cast both sides > > > > to a common type then call the saturating fixed point mul/div > > > > intrinsics. In either case, I do not think rounding will be an issue > > > > here since we can still calculate the result precisely and the result > > > > will be either rounded up or down. > > > > > > > > These semantics do not apply to comparison/relational operands, where > > > > if we do compare operands of different types or ranks, we would just > > > > need to find the common type between the 2 and perform the comparison. > > > > As you said before, the extra bit would not even matter with full > > > > precision since it would be trimmed anyway when converting from the > > > > common type to the result, and rounding can be either up or down. > > > > > > Note that this is only true if the unsigned value has a scale that's > > > greater than or equal to the scale of the result type, which is true for > > > same-rank addition but not necessarily with mixed-rank. > > > > > > > For multiplication and division, the result could technically be > > > > calculated also without having to cast between both sides, although for > > > > using the fixed point mul/div intrinsics, both operands would need to > > > > be casted to a common type with the same scale anyway. If a bit does > > > > end up getting dropped when casting an operand from unsigned to signed > > > > in (1), that dropped bit would not be representable when casting to the > > > > result anyway and the standard still allows us to return a rounded > > > > result. > > > > > > In a `_Fract` multiplication (or a mixed-rank multiplication), the extra > > > bit of precision can affect higher bits in the result. Consider an 8-bit > > > format where you're multiplying unsigned `000.00001` with signed > > > `+010.0000`: if you do this multiplication in full precision, the result > > > is `+000.0001`, but if you do the conversion first, the result is `0`. > > > > > > On some level, it just seems really strange to me that the spec calls for > > > doing arbitrary-precision arithmetic and then converting to the result > > > type for every situation except a signed/unsigned mismatch. > > Wait, wait... You might be on to something. > > > > The spec says in 4.1.4: > > > In order to get useful and attainable results, the usual arithmetic > > > conversions do not apply to the combination of an integer type and a > > > fixed-point type, or the combination of two fixed-point types. > > > In these cases: > > > 1. the result of the operation is calculated using the values of the two > > > operands, with their full precision; > > > 2. if one operand has signed fixed-point type and the other operand has > > > unsigned fixed-point type, then the unsigned fixed-point operand is > > > converted to its corresponding signed fixed-point type and the resulting > > > type is the type of the converted operand; > > > 3. the result type is the type with the highest rank, [...] > > > > (1) comes before (2); not after. In other words, shouldn't the true result > > of the operation be computed **before** the unsigned-signed correction? > > Point 2 is just worded really oddly, with 'operand is converted' and > > 'resulting type'. > > > > It says that "the unsigned fixed-point operand is converted to its > > corresponding signed fixed-point type and the resulting type is the type of > > the converted operand", but this doesn't mean that the resulting converted > > operand is actually used, only its type is. This means that for the purpose > > of result type calculation in (3), the converted operand type from (2) will > > be used as that operand's type, rather than the unsigned type itself. Or? > > > > The wording here is very hard to interpret. > Sure, I think you could make an argument for (2) only being meant to > participate in determining the result type. At the very least, that seems > more consistent, especially with the behavior for fixed-point + integer > operations. Do you have any existing contact with the committee? > Do you have any existing contact with the committee? I don't, no. Do you know who to contact regarding this sort of thing? For the record, I just tested this with avr-gcc, which supports the E-C fixed-point (without padding on unsigned): ``` // 0.00390625 is 2*-8. short _Accum calc = (unsigned short _Accum)0.00390625uk + (signed short _Accum)2.0k; ``` This produces: ``` calc: .byte 0 .byte 1 ``` If the unsigned was converted to signed (Q8.7) first, it should have had its bit stripped. Repository: rC Clang CHANGES SINCE LAST ACTION https://reviews.llvm.org/D53738/new/ https://reviews.llvm.org/D53738 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits