Walter Bright:

Data flow analysis can figure that example out.

Sorry, my mistake, what I am discussing about should not need much flow analysis. Here x and y are immutable:


void main(in string[] args) {
    import std.stdio, std.conv;

    assert(args.length == 3);
    immutable ubyte ux = args[1].to!ubyte;
    immutable ubyte uy = args[2].to!ubyte;
    immutable int x = ux;
    immutable int y = uy;

    immutable int result = x * y;
    writeln("Product: ", result);
}


In the current D compiler x and y keep a value range of ubytes (despite they are ints), so here D knows the expression "x * y" can't overflow, so even this is accepted with no casts needed:

immutable ushort result = x * y;



Now let's convert that code with SInt (x and y are still immutable, unlike in my precedent post):


void main(in string[] args) {
    import std.stdio, std.conv, std.experimental.safeintegral;

    assert(args.length == 3);
    immutable ubyte ux = args[1].to!ubyte;
    immutable ubyte uy = args[2].to!ubyte;
    immutable SInt x = ux;
    immutable SInt y = uy;

    immutable SInt result = x * y;
    assert(!result.overflow);
    writeln("Product: ", result);
}


What's I'd like is a way for x and y (that are of the library-defined type SInt) to keep the value range of an ubyte. So the compiler is able to rewrite this:

immutable SInt result = x * y;

removing the call to muls() and replacing it with a regular multiplication, that is faster, even when no inlining happens. (To do this SInt needs two different "instantiations" of its opBinary("*") ).


If it can't for a more complex one, you can do:

  assert(x >= 0 && x <= 255);
  assert(y >= 0 && y <= 255);

For such simple situations I don't want user annotations. The only code needed to make this request work should be already written inside the implementation of SInt and similar user-defined types.


And assume() and assert() are two different things, used for different purposes. Do not give the same name to two so different features, if you want to keep a language sane.

Info about assume in Microsoft C++:
http://msdn.microsoft.com/en-us/library/1b3fsfxw.aspx


The optimizer can certainly use asserts to provide semantic information (even though the dmd one doesn't at the moment).

This is not a good idea. That's the job for assume(), not for assert.

In general assert() verifies something is true, and if it's false, the program just raises an assert error, that is even recoverable. An assert leaves a condition testing inside the binary in debug builds.

An assume() doesn't need to leave a test condition inside the binary, even in debug builds. It doesn't raise run-time errors. It's just a way to tell the compiler that some predicate is true, and the optimization stages of the compiler have to try to use this information to optimize the code.

You can't mix or replace the two things, because a mistake in an assert() doesn't cause your program to burn, it just raises a compile-time error. A programmer mistake in an assume() burns your house.

assert() can be used freely in your code, to make sure you have not done a mistake. assume() is only for special situations where you know something is true, that the compiler can't prove by itself.


Again, I know you like reading about new languages and language features. I think you'd enjoy that even more supplemented with a book on how compilers work internally, in particular, how data flow analysis works and what it can do.

I will read more about that topic.

Bye,
bearophile

Reply via email to