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.