www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Make -preview=intpromote not suck

reply Steven Schveighoffer <schveiguy gmail.com> writes:
For those who don't know, the current compiler does not integer promote 
for negation. This means that typeof(-x) is typeof(x). This also means 
that for things like short and byte:

short x = short.min; // -32768
int y = -x; // still -32768
ulong z = -x; // 18446744073709518848

In C (and in common sense), y and z are 32768.

So we have a new system, which integer-promotes a value from short or 
byte to int *before* the negation.

However, this perfectly valid code will no longer work:

short x2 = -x;

Instead, you must *cast* the -x to short (and to get rid of the 
deprecation, you must also cast the x to int before using - on it).

The annoying thing here is, this *buys you nothing*. Before the new 
integer promotion rules, if x was short.min, x2 is short.min. After the 
new integer promotion rules, with a cast, x2 is *still* short.min.

I think this one problem is going to cause us to never turn on the 
intpromote rule by default. It will be insanely disruptive.

I have an idea to make this better. The true cases where integer 
promotion is a problem is when you use the negation of a smaller type 
implicitly as a larger type. Other than that, the differing rules 
produce the same results.

I propose that the VRP state has a flag added indicating that the range 
came from a negation directly (a cast or any other operations will 
eliminate the flag). In the case where a value of a larger type VRP 
range is assigned to a value that holds all of the range EXCEPT the 
topmost value, and the negation flag is set, the assignment is allowed. 
Otherwise, if it is assigned to a larger type, and the type's minimum is 
in the VRP, then trigger the deprecation.

How this might look:

short x2 = -x;

Without -preview=intpromote:

-x has type short with VRP range short.min to short.max inclusive, and 
negation flag set.
Assigning to a short works, because the VRP allows it. No warnings/errors.

With -preview=intpromote:

-x has type int with VRP range short.min + 1 to short.max + 1 inclusive, 
and negation flag set.
Assigning to a short is allowed because of the new rule (if negation was 
involved, then it's allowed to assign if the upper range is only one 
higher than allowed).

int i = -x;

Without -preview=intpromote:

-x has type short with VRP range short.min to short.max inclusive, and 
negation flag set.
Assigning to an int works, but triggers the deprecation because of the 
negation flag, type being promoted implicitly, and the VRP includes 
short.min.

With -preview=intpromote:

-x has type int with VRP range short.min + 1 to short.max + 1 inclusive, 
and negation flag set.
Assigning to an int works, because int can hold all the values.

Does this make sense? I think it makes for a lot better targeted 
diagnostic and problem fix.

-Steve
Oct 26 2020
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 26 October 2020 at 13:06:09 UTC, Steven Schveighoffer 
wrote:
 For those who don't know, the current compiler does not integer 
 promote for negation. This means that typeof(-x) is typeof(x). 
 This also means that for things like short and byte:

 short x = short.min; // -32768
 int y = -x; // still -32768
 ulong z = -x; // 18446744073709518848

 In C (and in common sense), y and z are 32768.

 So we have a new system, which integer-promotes a value from 
 short or byte to int *before* the negation.

 However, this perfectly valid code will no longer work:

 short x2 = -x;
The obvious solution is to not allow modular arithmetics for signed integers (like most languages) and trap on overflow in debug builds. I really dislike the whole idea of using VRP for typing. The code should behave the same way whether the compiler can figure out the value or not. Such special casing will lead to problems down the road.
Oct 26 2020
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 26 October 2020 at 13:06:09 UTC, Steven Schveighoffer 
wrote:
 [snip]

 -Steve
Wouldn't it be simpler if integer promoting expressions were implicitly castable to types they were promoted from, meaning: ``` short a = 1, b = 2; short c = a+b; //fine, int promoted from short implicitly castable back auto i = a+b; //int, because the type is still int if not casted short d = cast(short)i; //cast required here, promotion reversibility applies only for expressions byte e = cast(byte)(a+b); //cast required because byte is smaller than short ``` Won't solve both your problem and a whole host of others with `byte`s and `short`s? It should not be too hard to implement either, as array literals already do the same. They are implicitly castable to static arrays of their own size, while their type (dynamic array) is not.
Oct 27 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/27/20 7:13 PM, Dukc wrote:
 On Monday, 26 October 2020 at 13:06:09 UTC, Steven Schveighoffer wrote:
 [snip]
Wouldn't it be simpler if integer promoting expressions were implicitly castable to types they were promoted from, meaning: ``` short a = 1, b = 2; short c = a+b; //fine, int promoted from short implicitly castable back auto i = a+b; //int, because the type is still int if not casted short d = cast(short)i; //cast required here, promotion reversibility applies only for expressions byte e = cast(byte)(a+b); //cast required because byte is smaller than short ``` Won't solve both your problem and a whole host of others with `byte`s and `short`s? It should not be too hard to implement either, as array literals already do the same. They are implicitly castable to static arrays of their own size, while their type (dynamic array) is not.
I don't know if this is OK or correct. For example short c = a * b; => lots of truncation gonna happen. Note that C allows this, and D does not, on purpose. I wanted to focus on the problem that *currently* compiles, will *not* compile after the change, and *behaves exactly the same* even with mitigation (casting). In other words, we would require lots of mindless busywork (and/or break lots of old code) for zero benefit. Essentially, I like the idea of the intpromote preview. But this one problem is going to derail its adoption. -Steve
Oct 28 2020
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 28 October 2020 at 14:52:28 UTC, Steven 
Schveighoffer wrote:
 I don't know if this is OK or correct. For example

 short c = a * b; => lots of truncation gonna happen.
This sort of thing is still allowed with -preview=intpromote, for types that are int-sized or larger: int a = int.max, b = int.max; int c = a*b; // compiles and truncates The most principled way to approach this is to either *always* require a cast for possibly-truncating operations, or to *never* require one. As it stands, `-preview=intpromote` is an unsatisfying half-measure that both requires casts when they are not necessary, and fails to require them when they may be necessary.
Oct 28 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/28/20 11:13 AM, Paul Backus wrote:
 On Wednesday, 28 October 2020 at 14:52:28 UTC, Steven Schveighoffer wrote:
 I don't know if this is OK or correct. For example

 short c = a * b; => lots of truncation gonna happen.
This sort of thing is still allowed with -preview=intpromote, for types that are int-sized or larger:     int a = int.max, b = int.max;     int c = a*b; // compiles and truncates
Yeah, I never liked that aspect of integer promotion. In terms of practicality, int is probably a reasonable place to limit this for addition (it's very rare for adding 2 integers lead to an overflow, much less so than 2 shorts), but not multiplication.
 The most principled way to approach this is to either *always* require a 
 cast for possibly-truncating operations, or to *never* require one. As 
 it stands, `-preview=intpromote` is an unsatisfying half-measure that 
 both requires casts when they are not necessary, and fails to require 
 them when they may be necessary.
That ship may have sailed though. The goal is also different. I want to *avoid* breaking existing code, except in the case where it differs from the expected behavior. The exception in all of this is -int.min, which is still not going to work right. I also wonder how much D coders would tolerate such rules. I've used more restrictive languages which don't allow implicit conversions in many cases between integer types, and it can be painful. -Steve
Oct 28 2020
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Wednesday, 28 October 2020 at 17:34:10 UTC, Steven 
Schveighoffer wrote:
 That ship may have sailed though. The goal is also different. I 
 want to *avoid* breaking existing code, except in the case 
 where it differs from the expected behavior. The exception in 
 all of this is -int.min, which is still not going to work right.
Just add a switch that compiles with runtime overflow checks for signed integers (that prints file and line number when it traps). Then run it on available codebases and get an impression of where the issues are.
Oct 28 2020
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Wednesday, 28 October 2020 at 17:34:10 UTC, Steven 
Schveighoffer wrote:
 That ship may have sailed though. The goal is also different. I 
 want to *avoid* breaking existing code, except in the case 
 where it differs from the expected behavior. The exception in 
 all of this is -int.min, which is still not going to work right.
My idea should not break any code, because for every expression that compiles now the type is still going to be the same. That's exactly why I want short+short to still be int if not assigned back to short.
 I also wonder how much D coders would tolerate such rules. I've 
 used more restrictive languages which don't allow implicit 
 conversions in many cases between integer types, and it can be 
 painful.
The problem is not casting strictness in general. If I'm dealing with 32-bit variables, I agree that I want them to trunctate only via an explicit cast. The problem is the implicit integer promotion, when I'm not using any 32 or 64-bit. I do not want to explicitly cast every single expression back to the original type just because my arithmetic variables have shorter than normal lengths. That is --very-- ugly.
Oct 28 2020