Troubleshooting Haxe Dead Code Elimination (DCE)
Learn how to diagnose and fix Haxe Dead Code Elimination (DCE) failures. This guide covers compiler flags, reflection leaks, macro interference, and how to verify binary reduction.
07 Oct 2026, 12:15 UTC

The Problem: Bloated Binaries Despite DCE
When compiling Haxe projects for production, Dead Code Elimination (DCE) is intended to strip unused classes and methods to reduce binary size and improve load times. However, developers often find that specific classes or functions remain in the output even when they are not explicitly called in the source code. This typically happens because the Haxe compiler cannot mathematically prove that a symbol is unused, often due to dynamic access or configuration gaps.
Diagnostic Matrix: Why Code Persists
Use this table to match your symptoms with the likely cause of the DCE failure.
| Symptom | Likely Cause | Diagnostic Indicator |
|---|---|---|
| Entire libraries remain in output | Incorrect Compiler Flag | -dce none or -dce standard in build script |
| Specific classes persist despite no calls | Reflection/Dynamic Access | Presence of Reflect.field or Type.createInstance |
| Externs are missing or partially kept | Incorrect Metadata | Missing @:expose or @:dce(keep) on API boundaries |
| Code persists after deletion from source | Incremental Cache | Presence of .haxe or obj folders from previous builds |
Step-by-Step Resolution Path
1. Verify Compiler Flags
Haxe offers different levels of DCE. If you are using -dce standard, the compiler is conservative and will keep symbols it suspects might be needed. To maximize removal, you must use the most aggressive setting.
- Action: Update your build command or
.hxmlfile. - Command: Use
-dce full(or-dce yesin Haxe 4.2+). - Risk: Full DCE may remove code accessed via string-based reflection, causing runtime crashes.
2. Identify Reflection Leaks
The Haxe compiler performs static analysis. If your code uses Reflect or Type APIs, the compiler assumes any class that could be targeted by those calls must be kept.
Search your codebase for the following patterns:
// This prevents DCE from removing any class that might be passed as a string
var instance = Type.createInstance(classNameString, {});
// This prevents DCE from removing fields within a class
var value = Reflect.field(obj, fieldNameString);
If these patterns are used, the compiler cannot safely prune the targeted classes. To fix this, avoid string-based lookups where possible, or accept that these classes must remain.
3. Inspect Macro Expansions
Macros execute during compilation and can generate code that references symbols. Even if you don't see a call to a function in your .hx files, a macro might be injecting a reference to it.
- Check: Review the output of your macros. If a macro uses
Context.getLocalClasses()or generates a call to a utility class, that utility class is marked as "used."
4. Clear Build Artifacts
Incremental compilation caches can occasionally retain references to symbols that no longer exist in the source. This is common when refactoring large class hierarchies.
- Action: Run
haxe -cleanor manually delete the.haxeandobjdirectories. - Verification: Perform a fresh compile and check if the symbol still exists in the output.
Configuration Example: Protecting Necessary Externs
When using DCE, you may encounter the opposite problem: code that should be kept (because it is called by an external JavaScript or C++ library) is removed. Use @:dce(keep) to manually override the compiler's decision.
class NativeApi {
// This method is called by an external JS event listener
// Without @:dce(keep), the Haxe compiler removes it because no Haxe code calls it.
@:dce(keep)
public static function onExternalEvent(data: Dynamic): Void {
trace("Event received: " + data);
}
}
Verifying the Result
To confirm that DCE is actually working and not just failing silently, follow this comparison process:
- Compile with
-dce noneand record the file size of the output (e.g.,main.js). - Compile with
-dce fulland record the file size. - Open the
-dce fulloutput file in a text editor and search for the name of a class you know is unused. If the search returns no results, the symbol was successfully eliminated. - Run your full test suite. If you encounter
null object referenceormethod not founderrors, you have over-pruned and must apply@:dce(keep)to the missing symbols.
Escalation Criteria
If the binary size remains unchanged after switching to -dce full, clearing caches, and removing reflection, the issue likely lies in the target-specific backend. Note that DCE effectiveness varies by target; JavaScript and C++ typically offer the most aggressive pruning. If you are targeting a less common backend, verify the target's specific documentation regarding dead code stripping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.