July 28, 2014
On Monday, 28 July 2014 at 09:46:34 UTC, bearophile wrote:
>     uint y = 3_000_000_000;
>     writeln(x, " ", y);
>     writeln(x * y);

D promotes int to uint, right? Which is a bad idea. It should promote to long, right?

>     bool overflow = false;
>     immutable r1 = muls(x, y, overflow);

Why does muls accept this without an explicit cast to signed operands long? If the result is signed, the operands should be signed?

>     overflow = false;
>     immutable r2 = mulu(x, y, overflow);

Well, it overflows because you multiply 0xfffffffff with 3000000000 if it promotes int to uint, but the result would still be incorrect without an overflow if the values were -1 and 1. You would get 0xffffffff without an overflow/underflow.

(I might get it all wrong here, I didn't run the code.)

However, if you manually remove overflow checks, you probably need to check that you still get vectorized output with auto-vectorization. Maybe it is better that the backend deals with it. Dunno.
July 28, 2014
Ola Fosheim Grøstad:

> D promotes int to uint, right? Which is a bad idea. It should promote to long, right?

The purpose of safe integral operations like muls() is to detect overflow bugs at run-time. If they don't detect bugs, they are not useful.

Bye,
bearophile
July 28, 2014
"John Colvin"  wrote in message news:iguetbdxlyilavlizqry@forum.dlang.org...

> To what extent can a compiler use assertions? Can it work backwards from an assert to affect previous code?
>
> void foo(int a)
> {
>      enforce(a & 1);
>      assert(a & 1);
> }

The assert is dead code, because it will never be reached if (a & 1) is false.

> void bar()
> {
>      assert(a & 1);
>      enforce(a & 1);
> }

The throw inside enforce is dead code, because it will never be reached if (a & 1) is false.

The compiler is free to remove dead code, because it doesn't change the program's behaviour. 

July 28, 2014
On Monday, 28 July 2014 at 11:13:28 UTC, bearophile wrote:
> The purpose of safe integral operations like muls() is to detect overflow bugs at run-time. If they don't detect bugs, they are not useful.

Yes, but if you define the parameters to be long, then it will work. Right?


July 28, 2014
> The purpose of safe integral operations like muls() is to detect overflow bugs at run-time. If they don't detect bugs, they are not useful.

I think it detects the overflow correctly even if you use the (uint, int) argument pair, because the usual conversions are used, and I think the mulu is called, that returns the overflow boolean as true.

Bye,
bearophile
July 28, 2014
On Monday, 28 July 2014 at 12:08:39 UTC, Daniel Murphy wrote:
> "John Colvin"  wrote in message news:iguetbdxlyilavlizqry@forum.dlang.org...
>
>> To what extent can a compiler use assertions? Can it work backwards from an assert to affect previous code?
>>
>> void foo(int a)
>> {
>>     enforce(a & 1);
>>     assert(a & 1);
>> }
>
> The assert is dead code, because it will never be reached if (a & 1) is false.
>
>> void bar()
>> {
>>     assert(a & 1);
>>     enforce(a & 1);
>> }
>
> The throw inside enforce is dead code, because it will never be reached if (a & 1) is false.
>
> The compiler is free to remove dead code, because it doesn't change the program's behaviour.

Ok. What about this:

int c;

void foo(int a)
{
    if(a < 0) c++;
    assert(a > 0);
}

I presume that cannot be optimised away entirely to:

void foo(int a) {}

?
July 28, 2014
On Monday, 28 July 2014 at 12:52:07 UTC, John Colvin wrote:
> On Monday, 28 July 2014 at 12:08:39 UTC, Daniel Murphy wrote:
>> "John Colvin"  wrote in message news:iguetbdxlyilavlizqry@forum.dlang.org...
>>
>>> To what extent can a compiler use assertions? Can it work backwards from an assert to affect previous code?
>>>
>>> void foo(int a)
>>> {
>>>    enforce(a & 1);
>>>    assert(a & 1);
>>> }
>>
>> The assert is dead code, because it will never be reached if (a & 1) is false.
>>
>>> void bar()
>>> {
>>>    assert(a & 1);
>>>    enforce(a & 1);
>>> }
>>
>> The throw inside enforce is dead code, because it will never be reached if (a & 1) is false.
>>
>> The compiler is free to remove dead code, because it doesn't change the program's behaviour.
>
> Ok. What about this:
>
> int c;
>
> void foo(int a)
> {
>     if(a < 0) c++;
>     assert(a > 0);
> }
>
> I presume that cannot be optimised away entirely to:
>
> void foo(int a) {}
>
> ?

sorry, I mean

void foo(int a)
{
    assert(a > 0);
}

of course you can't optimise away the check.
July 28, 2014
"John Colvin"  wrote in message news:zzmgkwlzggrrtdtjbico@forum.dlang.org...
> Ok. What about this:
>
> int c;
>
> void foo(int a)
> {
>     if(a < 0) c++;
>     assert(a > 0);
> }
>
> I presume that cannot be optimised away entirely to:
>
> ...
>
> void foo(int a)
> {
>      assert(a > 0);
> }
>
> of course you can't optimise away the check.

No, because that would change the observable behaviour.

The compiler can do stuff like:

uint x = ...;
assert(x < 256);
ubyte y = x & 0xFF;

becomes

uint x = ...;
assert(x < 256);
ubyte y = x;

or

assert(y == 8);
auto n = x / y;

becomes

assert(y == 8);
auto n = y >> 3;

Allowing things like this:

assert(x  < 256);
ubyte y = x; // no mask/cast required

is technically possible but questionable.

You could also (in theory) propagate range information via an out contract:

int myMathFunc(int x)
out(result)
{
   assert(result >= 0 && result < 17);
}
body
{
   < some incomprehensible mathmatical stuff that the compiler can't understand >
}

void main()
{
   auto v = myMathFunc(99);
   ubyte b = v; // According to the out contract, v must fit in a ubyte
} 

July 28, 2014
On Monday, 28 July 2014 at 12:53:20 UTC, John Colvin wrote:
>> int c;
>>
>> void foo(int a)
>> {
>>    if(a < 0) c++;
>>    assert(a > 0);
>> }
>>
>> I presume that cannot be optimised away entirely to:
>>
>> void foo(int a) {}
>>
>> ?
>
> sorry, I mean
>
> void foo(int a)
> {
>     assert(a > 0);
> }
>
> of course you can't optimise away the check.

Please guys, you should not change code-gen based on asserts. They are not proofs, they are candidates for formal verification of correctness. They are in essence embedded break-point checks. If you allow asserts to affect codegen then it becomes a very unsafe feature. It's like having a undetected bug in a unit-test introduce bugs in the released program. 8-I

Anyway, without a barrier, you probably could do code motion if you know that you will reach HALT and that is considered illegal. For system programming language reaching HALT should probably be considered legal and you cannot do code motion based on that. In essence, when and where you can move code depends on barriers…

But since you don't actually get HALT from assert() in release mode, it makes no sense to argue the point either…
July 28, 2014
"Ola Fosheim Grøstad" " wrote in message news:vqlvigvgcplkuohudsju@forum.dlang.org...

> Please guys, you should not change code-gen based on asserts. They are not proofs, they are candidates for formal verification of correctness. They are in essence embedded break-point checks. If you allow asserts to affect codegen then it becomes a very unsafe feature. It's like having a undetected bug in a unit-test introduce bugs in the released program. 8-I

The compiler is allowed to not check assertions in release mode.  This is because a program that would fail an assertion is a broken program, and by specifying -release you are telling the compiler to assume all assertions pass.  I don't see any reason the compiler shouldn't be allowed to change code-gen based on asserts.

One murky area is that assert(0) is currently used to mean both 'unreachable' and 'unimplemented'.  It's unclear what the compiler is allowed to do with an assert(0) in release mode.