digitalmars.D.bugs - [Issue 13492] New: Last Postblit call optimization
- via Digitalmars-d-bugs (128/128) Sep 18 2014 https://issues.dlang.org/show_bug.cgi?id=13492
https://issues.dlang.org/show_bug.cgi?id=13492 Issue ID: 13492 Summary: Last Postblit call optimization Product: D Version: D2 Hardware: All OS: All Status: NEW Severity: enhancement Priority: P1 Component: DMD Assignee: nobody puremagic.com Reporter: wazar.leollone yahoo.com Now r-values aren't postblitted into functions: struct Foo { this(int) { writeln("ctor"); } this(this) { writeln("postblit"); } ~this() { writeln("dtor"); } } void bar(Foo arg); bar(Foo(42)); //Ok. Do not call postblit, only constructor However, if bar pass arg forth, redundant postblit calls are possible. Let see the next example: We have an AA implementation similar current D AA. struct AA(Key, Value) { this(T...)(T args) { buckets.length = T.length; foreach (i; Step2Tuple!(T.length)) { alias key = args[i]; alias value = args[i+1]; size_t key_hash = hashOf(key); size_t idx = key_hash % T.length; auto e = new Entry(key_hash, key, value, impl.buckets[idx]); buckets[idx] = e; } } ... private static struct Entry { size_t hash; Key key; Value value; Entry* next; } Entry*[] buckets; } Also we have factory method aaLiteral, which constructs our AA (compiler replaces AA literals with aaLiteral call): //[key1: value1, key2: value2] -> aaLiteral(Key, Value)(key1, value1, key2, value2); AA!(Key, Value) aaLiteral(Key, Value, T...)(auto ref T args) { return AA!(Key, Value)(args); } Now we call aaLiteral with rvalue args: auto aa = aaLiteral!(Foo, int)(Foo(1), 1, Foo(2), 2); When Foo(1) passed to aaLiteral, postblit isn't called. However, when we pass it further (from aaLiteral to AA.__ctor) and (from AA.__ctor to Entry.__ctor) postblit is called. Passing Foo by ref can't solve our problem, because anyway we need to do copy of argument when we are initializing Entry. Now let see, how compiler calls constructors, postblits and destructors: When we pass argument to a function, caller function calls postblit for the arg (except case, when the argument is r-value). Callee function uses this argument, calls postblit when pass it further, and it calls dtor before finishing: void test2(Foo); Foo global; struct Bar { Foo f; } Bar* globalbar; void test(Foo arg) { test2(arg); // call arg.__postblit() global = arg; // call arg.__postblit() globalbar = new Bar(arg); // arg.__postblit() //arg.__dtor() } However we can remove last potblit call and last dtor call, is postblitted object isn't changed after the last postblit call. void test(Foo arg) { test2(arg); // call arg.__postblit() global = arg; // call arg.__postblit() globalbar = new Bar(arg); // no postblit call // no dtor call } This optimization has a pitfalls. If test function passed arg by ref anywhere or takes pointer to the arg, we should reject this optimization: void test3(ref Foo arg); void test4(Foo* arg); void test(Foo arg) { test2(arg); // call arg.__postblit() test3(arg); //escapes the arg reference test4(&arg); //escapes the arg reference global = arg; // call arg.__postblit() globalbar = new Bar(arg); // we must call arg.__postblit() ... // and arg.__dtor() } Resume: If 1. function has a non-ref VarDeclaration (including argument) 2. this VarDeclaration passed to anywhere by value (with postblit call) 3. this VarDeclaration isn't used after last postblit call 4. reference to this VarDeclaration doesn't escape a function (as ref argument, or as pointer) then we can remove last postblit call and final destructor call. This optimization isn't modify the language and can 90% simulate ะก++ r-value reference (remaining 10% is a copying object to stack instead of pass a pointer). --
Sep 18 2014