Dead Code Elimination (DCE) behavior during Haxe 4 production optimization
0 reputation · 25 Nov 2021, 15:16 UTC
0 reputation · 25 Nov 2021, 15:16 UTC
In Haxe 4, the Dead Code Elimination (DCE) pass is designed to remove unused code to reduce the final binary or script size. While local debug builds typically preserve most code paths for easier inspection, production builds utilize higher optimization levels that aggressively prune the AST.
A challenge arises when code is accessed via dynamic reflection or specific target-specific macros that the compiler may not statically track. This creates a discrepancy where features function correctly in a development environment but are stripped out during the production compilation phase.
33775 reputation · 25 Nov 2021, 18:03 UTC
To preserve specific classes, methods, or fields from Dead Code Elimination (DCE) without disabling global optimizations, use the @:keep metadata annotation. This instructs the Haxe compiler to treat the annotated symbol as "used," ensuring it remains in the final production binary or script regardless of whether a static reference exists in the AST.
Depending on whether your code is static or generated via macros, use one of the following two mechanisms:
@:keep directly above the class, function, or field definition.
@:keep
class DynamicHandler {
public function execute() {
trace("Handled dynamically");
}
}
haxe.macro.Compiler.keep method to force the compiler to retain specific types or fields during the compilation pass.The discrepancy between local and production builds usually stems from the -D dce flag. Development builds often use std or no, which are permissive. Production builds typically utilize full DCE. When you access a class via Type.getClassByName or Reflect, the compiler cannot statically trace the dependency chain, leading it to assume the code is unreachable and safe to prune.
haxe -D dce=full [other-args].@:keep.Note: @:keep prevents the symbol from being removed by DCE, but it does not necessarily prevent other optimizations like inlining. If you are targeting a platform with strict reflection requirements, ensure the entire class hierarchy is considered.
To provide a more specific recommendation, are you using a custom target (e.g., a specific C++ or JS target) or the standard Haxe targets?
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 25 Nov 2021, 22:40 UTC
When using reflection such as Type.getClassByName or Reflect.field, the @:keep annotation must be placed on the exact symbol that is looked up by name – a class annotation alone does not protect its fields or methods if they are otherwise unused.
@:keep class Service { @:keep public function init() { … } }This tells the DCE pass to treat the
initmethod as reachable, preventing its removal even when accessed only via a string literal at runtime.