www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Dead Code Elimination

reply libxmoc <libxmoc gmail.com> writes:
I noticed that dmd doesn't do DCE, my executable is quite large 
for what it does. Any plan to add that? I tried to do it myself, 
a simple bool in DSymbol + a visitor (yoinked it from the unused 
import PR) to scan things, and it shrank build time by ~15% and 
executable went down to 3.5mb from 5.5mb.

Surely someone smarter than me could do even better.
Oct 18
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 19/10/2026 4:33 AM, libxmoc wrote:
 I noticed that dmd doesn't do DCE, my executable is quite large for what 
 it does. Any plan to add that? I tried to do it myself, a simple bool in 
 DSymbol + a visitor (yoinked it from the unused import PR) to scan 
 things, and it shrank build time by ~15% and executable went down to 
 3.5mb from 5.5mb.
 
 Surely someone smarter than me could do even better.
Doesn't surprise me. DCE can only work on shared libraries, executables, and local to object file symbols (mostly private but also not limited to that i.e. ModuleInfo must emit). The way dmd has traditionally handled it is to emit static libraries and let the linker deal with it.
Oct 18
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/18/2026 8:33 AM, libxmoc wrote:
 I noticed that dmd doesn't do DCE
Yes, it does. Use the -O switch.
Oct 18
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 19/10/2026 7:36 PM, Walter Bright wrote:
 On 10/18/2026 8:33 AM, libxmoc wrote:
 I noticed that dmd doesn't do DCE
Yes, it does. Use the -O switch.
dmd -O ```d void main() { } void foo() { } ``` ```asm.text._Dmain segment assume CS:.text._Dmain _Dmain: push RBP mov RBP,RSP xor EAX,EAX pop RBP ret .text._Dmain ends .text.void onlineapp.foo() segment assume CS:.text.void onlineapp.foo() void onlineapp.foo(): push RBP mov RBP,RSP pop RBP ret add [RAX],AL .text.void onlineapp.foo() ends ```
Oct 18
parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/18/2026 11:45 PM, Richard (Rikki) Andrew Cattermole wrote:
 On 19/10/2026 7:36 PM, Walter Bright wrote:
 On 10/18/2026 8:33 AM, libxmoc wrote:
 I noticed that dmd doesn't do DCE
Yes, it does. Use the -O switch.
dmd -O ```d void main() { } void foo() { } ``` ```asm.text._Dmain    segment     assume    CS:.text._Dmain _Dmain:         push    RBP         mov    RBP,RSP         xor    EAX,EAX         pop    RBP         ret .text._Dmain    ends .text.void onlineapp.foo()    segment     assume    CS:.text.void onlineapp.foo() void onlineapp.foo():         push    RBP         mov    RBP,RSP         pop    RBP         ret         add    [RAX],AL .text.void onlineapp.foo()    ends ```
Current version of dmd: _Dmain: 0000: 31 C0 xor EAX,EAX 0002: C3 ret _D4dead3fooFZv: 0000: C3 ret
Oct 21
prev sibling parent reply libxmoc <libxmoc gmail.com> writes:
On Monday, 19 October 2026 at 06:36:33 UTC, Walter Bright wrote:
 On 10/18/2026 8:33 AM, libxmoc wrote:
 I noticed that dmd doesn't do DCE
Yes, it does. Use the -O switch.
Even with -O it doesn't work: ```D import bar; extern(C) void main() { assert(1); } ``` ```D module bar; void removeme() { assert(0); } ``` ``` $ dmd -O -i -betterC -vasm app.d main: 0000: 31 C0 xor EAX,EAX 0002: C3 ret _D3bar8removemeFZv: 0000: 55 push RBP 0001: 48 8B EC mov RBP,RSP 0004: 48 8D 0D FC FF FF FF lea RCX,[0FFFFFFFCh][RIP] 000b: BA 05 00 00 00 mov EDX,5 0010: 48 8D 35 FC FF FF FF lea RSI,[0FFFFFFFCh][RIP] 0017: 48 8D 3D FC FF FF FF lea RDI,[0FFFFFFFCh][RIP] 001e: E8 00 00 00 00 call L0 0023: 5D pop RBP 0024: C3 ret ``` I disassembled both app and app.o, and `removeme` exists in both
Oct 21
parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/21/2026 12:10 AM, libxmoc wrote:
 I disassembled both app and app.o, and `removeme` exists in both
It's the job of the linker to remove uncalled functions. The compiler does not know what other code the result may be linked with, and what functions that other code calls.
Oct 21
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
```d
int foo() {
     int x = 3;
     if (x == 2)
         return 4;
     return 0;
}
```


dmd -c -vasm -m32 dead.d:
```
_D4dead3fooFZi:
0000:   B8 03 00 00 00           mov       EAX,3
0005:   83 F8 02                 cmp       EAX,2
0008:   75 02                    jne       Lc
000a:   40                       inc       EAX
000b:   C3                       ret
000c:   31 C0                    xor       EAX,EAX
000e:   C3                       ret

```

dmd -c -vasm -O -m32 dead.d:

```
_D4dead3fooFZi:
0000:   31 C0                    xor       EAX,EAX
0002:   C3                       ret
```
Oct 18
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Monday, 19 October 2026 at 06:46:13 UTC, Walter Bright wrote:
 dmd -c -vasm -O -m32 dead.d:
So is it just 32 bit only?
Oct 19
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 19/10/2026 8:04 PM, Alexandru Ermicioi wrote:
 On Monday, 19 October 2026 at 06:46:13 UTC, Walter Bright wrote:
 dmd -c -vasm -O -m32 dead.d:
So is it just 32 bit only?
For instruction/block DCE, no. dmd -O -m64 ```asm int onlineapp.foo(): push RBP mov RBP,RSP xor EAX,EAX pop RBP ret ```
Oct 19
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/19/2026 12:04 AM, Alexandru Ermicioi wrote:
 On Monday, 19 October 2026 at 06:46:13 UTC, Walter Bright wrote:
 dmd -c -vasm -O -m32 dead.d:
So is it just 32 bit only?
See https://github.com/dlang/dmd/pull/24074 The alwaysframe is specific to OSX64. But it does eliminate the dead code due to a DFA optimization called "constant propagation". There's a persistent belief that DMD does not do data flow analysis optimizations. DMD does rely on the linker to remove unused functions, because the separate compilation model means one doesn't not know if a function gets called or not. Only the linker has the full information.
Oct 19
parent reply Adam Wilson <flyboynw gmail.com> writes:
On Monday, 19 October 2026 at 07:50:10 UTC, Walter Bright wrote:
 On 10/19/2026 12:04 AM, Alexandru Ermicioi wrote:
 On Monday, 19 October 2026 at 06:46:13 UTC, Walter Bright 
 wrote:
 dmd -c -vasm -O -m32 dead.d:
So is it just 32 bit only?
See https://github.com/dlang/dmd/pull/24074 The alwaysframe is specific to OSX64. But it does eliminate the dead code due to a DFA optimization called "constant propagation". There's a persistent belief that DMD does not do data flow analysis optimizations.
Has something doesn't mean it works very well. See Rikki's example.
Oct 21
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 21/10/2026 8:24 PM, Adam Wilson wrote:
 On Monday, 19 October 2026 at 07:50:10 UTC, Walter Bright wrote:
 On 10/19/2026 12:04 AM, Alexandru Ermicioi wrote:
 On Monday, 19 October 2026 at 06:46:13 UTC, Walter Bright wrote:
 dmd -c -vasm -O -m32 dead.d:
So is it just 32 bit only?
See https://github.com/dlang/dmd/pull/24074 The alwaysframe is specific to OSX64. But it does eliminate the dead code due to a DFA optimization called "constant propagation". There's a persistent belief that DMD does not do data flow analysis optimizations.
Has something doesn't mean it works very well. See Rikki's example.
In this case I understand it to have been out of scope. DCE'ing symbols has requirements that C doesn't provide. In this case there is no harm in it being missing, but there is harm if it wasn't designed correctly (see -allinst).
Oct 21
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/21/2026 12:24 AM, Adam Wilson wrote:
 Has something doesn't mean it works very well. See Rikki's example.
Rikki's example is not a flow analysis issue. Besides, it's been corrected.
Oct 21
prev sibling parent reply user1234 <user1234 12.de> writes:
On Sunday, 18 October 2026 at 15:33:17 UTC, libxmoc wrote:
 I noticed that dmd doesn't do DCE, my executable is quite large 
 for what it does. Any plan to add that? I tried to do it 
 myself, a simple bool in DSymbol + a visitor (yoinked it from 
 the unused import PR) to scan things, and it shrank build time 
 by ~15% and executable went down to 3.5mb from 5.5mb.

 Surely someone smarter than me could do even better.
It's hard to say when something is used, simple example: ```d void unused(){} void main() { import core.stdc.stdio; printf("%p\n",&unused); } ``` same problem with virtual functions. Otherwise what you call DCE is rather WPO (whole program optimization).
Oct 21
next sibling parent user1234 <user1234 12.de> writes:
On Wednesday, 21 October 2026 at 09:32:25 UTC, user1234 wrote:
 On Sunday, 18 October 2026 at 15:33:17 UTC, libxmoc wrote:
 I noticed that dmd doesn't do DCE, my executable is quite 
 large for what it does. Any plan to add that? I tried to do it 
 myself, a simple bool in DSymbol + a visitor (yoinked it from 
 the unused import PR) to scan things, and it shrank build time 
 by ~15% and executable went down to 3.5mb from 5.5mb.

 Surely someone smarter than me could do even better.
It's hard to say when something is used, simple example: [...]
I should have said, more briefly, that this brings the question of aliasing.
Oct 21
prev sibling parent reply Paolo Invernizzi <paolo.invernizzi gmail.com> writes:
On Wednesday, 21 October 2026 at 09:32:25 UTC, user1234 wrote:
 On Sunday, 18 October 2026 at 15:33:17 UTC, libxmoc wrote:
 I noticed that dmd doesn't do DCE, my executable is quite 
 large for what it does. Any plan to add that? I tried to do it 
 myself, a simple bool in DSymbol + a visitor (yoinked it from 
 the unused import PR) to scan things, and it shrank build time 
 by ~15% and executable went down to 3.5mb from 5.5mb.

 Surely someone smarter than me could do even better.
It's hard to say when something is used, simple example: ```d void unused(){} void main() { import core.stdc.stdio; printf("%p\n",&unused); } ``` same problem with virtual functions. Otherwise what you call DCE is rather WPO (whole program optimization).
Not an answer on the compiler side, but related, since today djs-vscode (0.2.0) shows unused code in the editor. It walks the semantic AST of the whole program from `main` (the same thing `-i` compiles) and greys out every declaration it cannot reach: functions, methods, templates, aggregates, enums, mutable globals. Hovering tells you why ("unreferenced", "only referenced from unreachable code", ...). Because the walk runs after semantic analysis, string mixins are already expanded and `__traits` resolved, so a function named only inside a mixin counts as used. And user1234's example works as it should: `&unused` is a use, so `unused` is not greyed. Screenshot: https://raw.githubusercontent.com/DeepGlance/djs-vscode/main/extension/media/unused.png Hover: https://raw.githubusercontent.com/DeepGlance/djs-vscode/main/extension/media/unused-hover.png Caveats: it is "unused in this build", so code used only under a `version` that is not compiled, or only in a `unittest` when you compile without `-unittest`, is kept rather than greyed; `extern(C)` and exported symbols count as roots; separately compiled libraries are out of sight. It does not touch the binary, it only shows you what the linker would not need. Paolo
Oct 21
parent user1234 <user1234 12.de> writes:
On Wednesday, 21 October 2026 at 14:59:20 UTC, Paolo Invernizzi 
wrote:
 On Wednesday, 21 October 2026 at 09:32:25 UTC, user1234 wrote:
 On Sunday, 18 October 2026 at 15:33:17 UTC, libxmoc wrote:
 I noticed that dmd doesn't do DCE, my executable is quite 
 large for what it does. Any plan to add that? I tried to do 
 it myself, a simple bool in DSymbol + a visitor (yoinked it 
 from the unused import PR) to scan things, and it shrank 
 build time by ~15% and executable went down to 3.5mb from 
 5.5mb.

 Surely someone smarter than me could do even better.
It's hard to say when something is used, simple example: [...]
Not an answer on the compiler side, but related, since today djs-vscode (0.2.0) shows unused code in the editor. [...] Paolo
I see what you mean. One thing however: you can forgive an error from your your code editor. But an error in the backend production will be way more frustrating. I have nothing against the idea of libxmoc, I wanted to note that this should be made with care. Maybe in a first time this could be tried with a compiler warning.
Oct 21