Problem Statement
Global copy propagation was removed due to a correctness bug that manifested in loop-based Wasm code. The optimization is valuable for reducing variable allocations and improving code quality, but the original implementation lacked proper data-flow safety analysis.
Historical Context
- Original implementation was disabled for multi-block functions to prevent incorrect variable substitution
- Bug: Variables captured via
Assign (e.g., v20 = Assign(v10)) followed by redefinition of the source (e.g., v10 = result) would cause later uses of the captured variable to reference the NEW value instead of the captured OLD value
- Test case:
test_fib_i64 returned 512 instead of 55 due to this bug
- Decision: Remove the pass entirely rather than attempt to patch the complex logic
Objective
Re-introduce the global copy propagation optimization with a correct and efficient implementation that properly handles:
- Single-block functions (safe case - no loops)
- Multi-block functions with loops (requires data-flow analysis)
- Variable dependency chains across assignments
- Proper use-def chains in SSA form
Detailed Requirements
R1: Single-Block Functions (Phase 1 - LOW RISK)
Restore global copy propagation for single-block functions where there are no control-flow edges between the Assign and its uses.
Algorithm:
- Collect all
Assign { dest, src } pairs where dest has exactly one definition
- Verify
src is not redefined anywhere in the block
- Chase transitive chains to find the root variable
- Replace all uses of
dest with the root
- Remove now-dead Assign instructions
Test Coverage Required:
const_assign_coalesced
binop_assign_coalesced
chain_coalesced
fibo_b0_pattern
dead_local_pruned_after_coalesce
multi_use_src_global_prop_removes_both
R2: Multi-Block Functions (Phase 2 - REQUIRES DATA-FLOW ANALYSIS)
Implement proper data-flow analysis for multi-block functions (loops) to safely perform global copy propagation.
Safety Constraint:
For each Assign { dest, src } at position P in block B, only substitute dest with its root if:
- Every variable in the dependency chain (dest → ... → root) is NOT redefined in B after position P
- If the variable is redefined in B after P, there exists no use of
dest after that redefinition within the same block
Recommended Approach:
- Build a use-def chain for each variable
- For each Assign candidate, compute the set of variables in its dependency chain
- Check each variable in the chain:
- Find all definitions of that variable across all blocks
- If any definition occurs after the Assign in the same block, mark as unsafe
- If the variable is a loop-carried variable (appears in phi nodes), require additional analysis
- Only perform substitution if all safety checks pass
Test Coverage Required:
global_copy_prop_chain_across_blocks
global_copy_prop_multiple_uses_across_blocks
forward_cross_block_use_propagated
cross_block_all_copies_resolved
R3: Integration
- Update
copy_prop::eliminate() to conditionally call global_copy_prop only after safety analysis
- Ensure backward coalescing and forward propagation still work independently
- Run optimization multiple iterations (current 2-iteration pattern in optimize_ir)
Acceptance Criteria
Technical Notes
Use-Def Analysis
For safe multi-block global copy propagation, implement a use-def chain that tracks:
- Definition points:
Assign { dest, src } instructions
- Use points: reads of each variable
- Cross-block dependencies: phi nodes representing loop-carried variables
- Redefinition sites: where a variable is reassigned
SSA Form Assumption
The implementation can assume strict SSA form:
- Each variable is defined exactly once
- Phi nodes represent values from different control-flow paths
- After phi lowering, phi nodes become Assign instructions in predecessor blocks
Data-Flow Lattice
Consider using a simple lattice:
variable ∈ {Unsafe, MaybeUnsafe, Safe}
Unsafe: Redefined after Assign in same block → don't substitute globally
MaybeUnsafe: Loop-carried variable → requires additional analysis
Safe: No redefinition after Assign → safe to substitute
Implementation Strategy
Phase 1 (Restore single-block):
- Un-ignore single-block tests
- Re-enable global_copy_prop for single-block functions
- Verify all single-block tests pass
Phase 2 (Add multi-block support):
- Implement use-def chain analysis in a new helper function
- Create a predicate function:
is_safe_to_substitute(dest, src, func) -> bool
- Update global_copy_prop to check safety before substitution
- Un-ignore multi-block tests
- Iterate on safety analysis until all tests pass
Phase 3 (Verification):
- Run full test suite
- Benchmark code generation quality
- Document implementation approach
- Code review
Related Code
/workspaces/herkos/crates/herkos-core/src/optimizer/copy_prop.rs - Main implementation
/workspaces/herkos/crates/herkos-core/src/optimizer/utils.rs - Helper utilities (instr_dest, for_each_use, etc.)
- Commit 5f40d55: "refactor: remove global copy propagation optimization"
- Commit 7791567: "fix: disable global copy propagation for multi-block functions"
References
Risk Assessment
Low Risk (Phase 1): Single-block functions are straightforward and well-tested
Medium Risk (Phase 2): Multi-block analysis requires careful verification to avoid data-flow errors
Problem Statement
Global copy propagation was removed due to a correctness bug that manifested in loop-based Wasm code. The optimization is valuable for reducing variable allocations and improving code quality, but the original implementation lacked proper data-flow safety analysis.
Historical Context
Assign(e.g.,v20 = Assign(v10)) followed by redefinition of the source (e.g.,v10 = result) would cause later uses of the captured variable to reference the NEW value instead of the captured OLD valuetest_fib_i64returned 512 instead of 55 due to this bugObjective
Re-introduce the global copy propagation optimization with a correct and efficient implementation that properly handles:
Detailed Requirements
R1: Single-Block Functions (Phase 1 - LOW RISK)
Restore global copy propagation for single-block functions where there are no control-flow edges between the Assign and its uses.
Algorithm:
Assign { dest, src }pairs wheredesthas exactly one definitionsrcis not redefined anywhere in the blockdestwith the rootTest Coverage Required:
const_assign_coalescedbinop_assign_coalescedchain_coalescedfibo_b0_patterndead_local_pruned_after_coalescemulti_use_src_global_prop_removes_bothR2: Multi-Block Functions (Phase 2 - REQUIRES DATA-FLOW ANALYSIS)
Implement proper data-flow analysis for multi-block functions (loops) to safely perform global copy propagation.
Safety Constraint:
For each
Assign { dest, src }at position P in block B, only substitutedestwith its root if:destafter that redefinition within the same blockRecommended Approach:
Test Coverage Required:
global_copy_prop_chain_across_blocksglobal_copy_prop_multiple_uses_across_blocksforward_cross_block_use_propagatedcross_block_all_copies_resolvedR3: Integration
copy_prop::eliminate()to conditionally call global_copy_prop only after safety analysisAcceptance Criteria
HERKOS_OPTIMIZE=1 cargo testpasses all tests (1000+ tests)test_fib_i64returns correct value (55 for fib(10)) with optimization enabledTechnical Notes
Use-Def Analysis
For safe multi-block global copy propagation, implement a use-def chain that tracks:
Assign { dest, src }instructionsSSA Form Assumption
The implementation can assume strict SSA form:
Data-Flow Lattice
Consider using a simple lattice:
Unsafe: Redefined after Assign in same block → don't substitute globallyMaybeUnsafe: Loop-carried variable → requires additional analysisSafe: No redefinition after Assign → safe to substituteImplementation Strategy
Phase 1 (Restore single-block):
Phase 2 (Add multi-block support):
is_safe_to_substitute(dest, src, func) -> boolPhase 3 (Verification):
Related Code
/workspaces/herkos/crates/herkos-core/src/optimizer/copy_prop.rs- Main implementation/workspaces/herkos/crates/herkos-core/src/optimizer/utils.rs- Helper utilities (instr_dest, for_each_use, etc.)References
Risk Assessment
Low Risk (Phase 1): Single-block functions are straightforward and well-tested
Medium Risk (Phase 2): Multi-block analysis requires careful verification to avoid data-flow errors