Dead Code Elimination Limits in Haxe for JavaScript Target
0 reputation · 01 Jul 2020, 05:05 UTC
0 reputation · 01 Jul 2020, 05:05 UTC
We want to confirm that the Haxe compiler's dead‑code elimination correctly removes an unused class when targeting JavaScript, while preserving classes that might be needed via dynamic or reflection access.
The compiler performs reachability analysis per target. JavaScript and C++ backends typically prune unreachable symbols, but strings used in dynamic field access or untyped calls can keep code alive. Build flags such as -D noUnused or target attributes like @:keep may alter the outcome.
It is unclear whether the current Haxe release (e.g., 4.2.x) will eliminate a class that is only referenced through reflection, and how to force its removal or retention reliably.
34775 reputation · 01 Jul 2020, 07:26 UTC
In Haxe 4.2.x the default dead‑code elimination (‑dce std) removes a class only when the compiler can prove that neither the class name nor any of its static members appear as a compile‑time constant in the generated code.
If a class is referenced solely through a reflection call that uses a string literal – e.g. Reflect.field(null, "MyClass") or Type.getClass(MyClass) – the static analyzer treats that literal as a potential use and therefore conservatively keeps the class and its static fields in the output.
@:keep metadata on a class (or on individual fields/methods) forces the compiler to retain that symbol even when no other reference exists.-dce none disables the elimination pass entirely, guaranteeing that all classes appear in the output; -dce full is more aggressive than the default but still respects string‑literal reflections.class Unused { static function test() return 1; } and a Main that does not reference Unused.haxe -main Main -js out.js.out.js and search for the string Unused (or test). If the class was eliminated, neither the class definition nor the literal will appear.Reflect.field(null, "Unused"); somewhere in Main and recompile; the class definition and its static members will now be present in the output.@:keep (e.g. @:keep class Unused { ... }) and recompile without any reflection; the class will remain in out.js.If you are using a non‑default DCE level (e.g. -dce full or -dce none) please let me know, as that changes whether the class is kept or removed independent of reflection.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 01 Jul 2020, 09:05 UTC
One clarification on verification: because Haxe 4.2.x’s JavaScript DCE treats any string literal resembling a class name as a potential reflection root, a class can stay alive even when no typed import references it. To confirm removal, compile with -dce full and grep the output for both the fully qualified name and the short class name; if either string appears, something—possibly an untyped expression or Type.resolveClass call—is still anchoring the module.
If you need the class to survive for dynamic access, @:keep is the explicit retention mechanism. Avoid relying on generic defines to force pruning; DCE behavior can shift between Haxe releases, so re-run this diff after any compiler upgrade.