On Tuesday, 16 February 2016 at 10:09:19 UTC, Jonathan M Davis wrote:
On Tuesday, 16 February 2016 at 09:45:34 UTC, ZombineDev wrote:
On Tuesday, 16 February 2016 at 08:03:31 UTC, Jonathan M Davis wrote:
...
Given that very few optimizations can be made based solely on const (and I'm not sure that the compiler currently does any of them) ...

I think that the fact that we have shared can help a lot in the area of optimization. Taking a non-shared const parameter should be as good as taking an immutable one (if the function pure-like - only does computation based on the parameter and doesn't escape it).
Or am I missing something?

The fact that variables are thread-local by default in D definitely helps, but it's still far from sufficient. If you have

    auto result = func(foo);

where foo is const, for the compiler to know that foo was not mutated by the call to func, it has to know that there's no way that func could have accessed a mutable reference to the object that foo refers to. The fact that we have shared means that it doesn't have to worry about other, arbitrary code mutating foo while func is called, but it still has to be able to determine that neither func no anything that it accesses is able to mutate foo.

If func is pure, that gets us a good chunk of the way, because then the only way that func can access a mutable reference to foo is via its arguments. If it's just foo, then - unless I'm missing something - that should be enough, since foo would not be able to access a mutable reference to itself except via a non-pure function, which wouldn't be callable from func if it's pure. But if func is a member function, or if it took addition, mutable arguments, e.g.

    auto result = func(foo, bar);

then the compiler has to know that func can't access a mutable foo via the other arguments - and if they're not const, that's hard to do.

In general, I think that the compiler pretty much has to be able to see where foo was created and that no mutable references _can_ exist that func would have access to. e.g.

    const foo = new Foo(42);
    auto result = func(foo, bar);

or

    auto mutableFoo = new Foo(42);
    const foo = mutableFoo;
    auto result = func(foo, bar);

would be enough. But without a lot of code flow analysis, it doesn't take much for it to be too much for the compiler to figure out, and unless it has access to the bodies of the functions that are being called and does deep inspection of what they do and call, simply calling a function (especially a non-pure one) very quickly makes it so that the compiler doesn't know enough to guarantee that func can't possibly access foo via a mutable reference to the same data. e.g. in

    auto mutableFoo = new Foo(42);
    auto result1 = otherFunc(mutableFoo, bar);
    const foo = mutableFoo;
    auto result2 = func(foo, bar);

otherFunc might have given bar access to mutableFoo which it then accessed inside of func.

So, yes, the combination of non-shared, pure, and const does allow for some optimizations, but it really doesn't take much to make it so that the compiler can't guarantee that const object hasn't been changed by a function call without it doing a lot of deep analysis that compilers just don't normally do.

- Jonathan M Davis

I agree with everything you said. What I meant was that **in theory** const + thread-local can give similar guarantees as immutable (if the compiler can perform the necessary analysis). That means it's more of an implementation problem than a langauge problem. Given that separate compilation and .di header-only libraries are far more rare in D that in C++ (D has faster compilation and templates with attribute inference provide even more incentive), whole program optimization seems more easily doable in D. After all the Rust compiler has proven that immutable borrowing (of a mutable object) is not hard to enforce, so I don't see a hard obstacle for this to be implemented in D. At least not an obstacle comming from the language design.

Reply via email to