https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64380
Bug ID: 64380 Summary: Missed optimization: smarter dead store elimination in dtors Product: gcc Version: 5.0 Status: UNCONFIRMED Severity: minor Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: petschy at gmail dot com Some of the stores are eliminated in dtors already, if the values are not used later. But if there are function calls after the stores, then they are not eliminated. I see the reason for this, but some functions are special, eg free(), operator delete and others with the same semantics: they won't crawl back and access these variables, so if the vars are not used locally, and no other functions are called, the stores could be eliminated. This would be useful eg for classes where there is a user callable function that releases some/all resources, while keeping the instance alive, and the dtor calls the same function to release all resources. In this latter case, stores that are otherwise needed to have a proper state can be omitted since the instance is being destroyed, anyway. This is a minor issue probably, since the program shouldn't spend most of its time running dtors. However, some function attribute symmetric in spirit to 'malloc' would be nice: eg 'free': this would mean that if called, it won't reach back to the variables of the calling scope, either through its arguments or through global variables, so those stores could be safely eliminated that are otherwise dead. g++-5.0.0 -v Using built-in specs. COLLECT_GCC=g++-5.0.0 COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-unknown-linux-gnu/5.0.0/lto-wrapper Target: x86_64-unknown-linux-gnu Configured with: ../configure --enable-languages=c,c++ --disable-multilib --program-suffix=-5.0.0 Thread model: posix gcc version 5.0.0 20141222 (experimental) (GCC) Compliled with g++-5.0.0 -g -O3 -Wall 20141222-dtor-deadstore.cpp Dump of assembler code for function test_ra(Foo*): 0x00000000004005f0 <+0>: push %rbp 0x00000000004005f1 <+1>: push %rbx 0x00000000004005f2 <+2>: mov %rdi,%rbp 0x00000000004005f5 <+5>: sub $0x8,%rsp # two stores before the loop 0x00000000004005f9 <+9>: movl $0x1,0x10(%rdi) 0x0000000000400600 <+16>: movl $0x2,0x14(%rdi) 0x0000000000400607 <+23>: mov 0x8(%rdi),%rdi 0x000000000040060b <+27>: test %rdi,%rdi 0x000000000040060e <+30>: je 0x400620 <test_ra(Foo*)+48> 0x0000000000400610 <+32>: mov (%rdi),%rbx 0x0000000000400613 <+35>: callq 0x4004d0 <_ZdlPv@plt> 0x0000000000400618 <+40>: test %rbx,%rbx 0x000000000040061b <+43>: mov %rbx,%rdi 0x000000000040061e <+46>: jne 0x400610 <test_ra(Foo*)+32> # two stores after the loop, so far so good 0x0000000000400620 <+48>: movq $0x0,0x8(%rbp) 0x0000000000400628 <+56>: movl $0x3,0x18(%rbp) 0x000000000040062f <+63>: add $0x8,%rsp 0x0000000000400633 <+67>: pop %rbx 0x0000000000400634 <+68>: pop %rbp 0x0000000000400635 <+69>: retq Dump of assembler code for function test_dtor(Foo*): # two stores before the lop in the dtor. these won't ever be read again # could be eliminated 0x0000000000400640 <+0>: movl $0x1,0x10(%rdi) 0x0000000000400647 <+7>: movl $0x2,0x14(%rdi) 0x000000000040064e <+14>: mov 0x8(%rdi),%rdi 0x0000000000400652 <+18>: test %rdi,%rdi 0x0000000000400655 <+21>: je 0x400671 <test_dtor(Foo*)+49> 0x0000000000400657 <+23>: push %rbx 0x0000000000400658 <+24>: nopl 0x0(%rax,%rax,1) 0x0000000000400660 <+32>: mov (%rdi),%rbx 0x0000000000400663 <+35>: callq 0x4004d0 <_ZdlPv@plt> 0x0000000000400668 <+40>: test %rbx,%rbx 0x000000000040066b <+43>: mov %rbx,%rdi 0x000000000040066e <+46>: jne 0x400660 <test_dtor(Foo*)+32> # no stores here, the ones after 'delete' were eliminated successfully 0x0000000000400670 <+48>: pop %rbx 0x0000000000400671 <+49>: repz retq Dump of assembler code for function test_dtor2(Foo*): 0x0000000000400680 <+0>: push %rbp 0x0000000000400681 <+1>: push %rbx 0x0000000000400682 <+2>: mov %rdi,%rbp 0x0000000000400685 <+5>: sub $0x8,%rsp # 4 dead stores in the src, the one to the same addr is eliminated 0x0000000000400689 <+9>: movl $0xc0,(%rdi) 0x000000000040068f <+15>: movl $0x1,0x10(%rdi) 0x0000000000400696 <+22>: movl $0x2,0x14(%rdi) 0x000000000040069d <+29>: mov 0x8(%rdi),%rdi 0x00000000004006a1 <+33>: test %rdi,%rdi 0x00000000004006a4 <+36>: je 0x4006c0 <test_dtor2(Foo*)+64> 0x00000000004006a6 <+38>: nopw %cs:0x0(%rax,%rax,1) 0x00000000004006b0 <+48>: mov (%rdi),%rbx 0x00000000004006b3 <+51>: callq 0x4004d0 <_ZdlPv@plt> 0x00000000004006b8 <+56>: test %rbx,%rbx 0x00000000004006bb <+59>: mov %rbx,%rdi 0x00000000004006be <+62>: jne 0x4006b0 <test_dtor2(Foo*)+48> # stores after 'delete' are eliminated 0x00000000004006c0 <+64>: add $0x8,%rsp 0x00000000004006c4 <+68>: mov %rbp,%rdi 0x00000000004006c7 <+71>: pop %rbx 0x00000000004006c8 <+72>: pop %rbp 0x00000000004006c9 <+73>: jmpq 0x4004d0 <_ZdlPv@plt> End of assembler dump. ----8<----8<----8<--- struct Node { Node* next; char* cur; char beg[0]; }; struct Base { int b0; int b1; ~Base() { Clear(); } void Clear() { b0 = 0xb0; // OK, these are dead store eliminated (DSE) b1 = 0xb1; } }; struct Foo : Base { Node* nodes; int m1; int m2; int m3; ~Foo() { ReleaseAll(false); } void ReleaseAll(bool k) { m1 = 1; // should be DSE'd if called from the dtor, but it's not m2 = 2; // ditto Node* n = nodes; while (n) { Node* t = n->next; if (!t && k) { n->cur = n->beg; break; } delete n; n = t; } nodes = n; // OK, DSE'd if called from the dtor m3 = 3; // ditto } }; void test_ra(Foo* f) { f->ReleaseAll(false); } void test_dtor(Foo* f) { f->~Foo(); } void test_dtor2(Foo* f) { f->b0 = 0xc0; // should be DSE'd, but it's not f->m1 = 42; // OK, DSE'd via the ReleaseAll(): m1=1 store delete f; } int main() { }