When using Reflaxe, we bypass Haxe's optimization phase entirely. This is an intentional architectural design that gives us full control over code generation at the cost of implementing our own optimizations.
Source Code → Parser → Typer → TypedExpr → FILTERS/OPTIMIZER → DCE → Generator → Target Code
↑ ↑ ↑
onAfterTyping onGenerate (never reached
(we hook here) (before_dce) with Reflaxe)
- Parsing: Source → AST
- Typing: AST → TypedExpr (fully typed)
- onAfterTyping: Last point for additional typing
- Filters: Various AST transformations
- onGenerate: Actually called "before_dce" internally
- DCE: Dead Code Elimination removes unused code
- Optimizer: Static analyzer (const propagation, etc.)
- Generator: Target-specific code generation
Source Code → Parser → Typer → TypedExpr → REFLAXE COMPILER → Target Code
↑
We intercept here
(bypass all optimizations)
| We Get | We Miss |
|---|---|
| ✅ Fully typed AST | ❌ Dead Code Elimination |
| ✅ Type information | ❌ Static analyzer optimizations |
| ✅ Complete control | ❌ Const propagation |
| ✅ Direct generation | ❌ Loop unrolling |
| ✅ Custom patterns | ❌ Tail recursion optimization |
// From reflaxe.CPP/src/cxxcompiler/CompilerInit.hx
ReflectCompiler.AddCompiler(new Compiler(), {
manualDCE: true, // ← Explicit manual DCE requirement
// ... other options
});// From reflaxe/src/reflaxe/ReflectCompiler.hx
Context.onAfterTyping(onAfterTyping); // Get TypedExpr here
Context.onAfterGenerate(onAfterGenerate); // Just starts compilationFrom the Haxe manual and source:
- onGenerate is misleadingly named - it's actually "before_dce"
- DCE runs after typing but before target generation
- Optimizations are target-specific (JS optimizer, C++ optimizer, etc.)
- TypedExpr contains all typing artifacts, even if unused
This is intentional, not a limitation:
- Full Control: Generate exactly the target code we want
- Idiomatic Output: Create natural-looking target language code
- Custom Patterns: Implement target-specific optimizations
- Trade-off: We handle optimizations ourselves
When Haxe compiles:
switch (spec) {
case Repo(config): // config never used
// empty body
}We receive:
TBlock([
TEnumParameter(spec, Repo, 0), // Extract parameter
TLocal(g) // Reference temp variable
])
This is expected behavior because:
- Haxe generates this for type validation
- DCE would normally remove it
- We receive it before DCE runs
- We must handle the cleanup ourselves
Since we can't use Haxe's optimizer, we implement:
-
Reflaxe Preprocessors (when applicable):
options.expressionPreprocessors = [ SanitizeEverythingIsExpression({}), RemoveConstantBoolIfs, // etc. ];
-
Compilation-Time Detection (our approach):
// Detect and skip orphaned patterns during compilation if (isOrphanedEnumParameterPattern(expressions, i)) { i += 2; // Skip both expressions continue; }
To verify this architecture:
- Check if
-dce fullaffects our output (it doesn't) - Observe TypedExpr contains unoptimized patterns
- Note other Reflaxe compilers use
manualDCE: true - See that preprocessors exist specifically for this gap
Given this architecture:
- Always assume unoptimized AST: The TypedExpr is typed but not optimized
- Handle cleanup at compilation: Don't rely on post-processing
- Use AST analysis: Detect patterns at the structural level
- Document optimizations: Each optimization we implement should be documented
- Test thoroughly: Without Haxe's optimizer, we must validate output quality
- Haxe Manual: Dead Code Elimination
- Reflaxe.CPP Implementation
- Reflaxe Core Architecture
- Haxe Compiler Phases Discussion
The lack of automatic optimization in Reflaxe is by design, not a bug or limitation. It's the price we pay for the ability to generate idiomatic, controlled target code. Understanding this architecture is crucial for compiler development.