9.5 KiB
How to Add Peephole Optimization Patterns
This guide shows you how to add new optimization patterns to the StinkyTofu compiler.
Quick Start
Three steps:
- Write pattern in '.pattern' file
- Run 'make'
- Done!
Example: Adding a Multiply-Add Fusion
Step 1: Write the Pattern
Edit 'src/transforms/asm/PeepholePatterns.pattern':
''' //===----------------------------------------------------------------------===// // Pattern: Multiply-Add Fusion (F32) // // Transforms: // v_mul_f32 v0, a, b // v_add_f32 v2, v0, c // Into: // v_fma_f32 v2, a, b, c //===----------------------------------------------------------------------===//
pattern MulAddFusion_F32 { match { $mul = v_mul_f32 $tmp, $a, $b $add = v_add_f32 $dst, $tmp, $c }
constraints { HasOneUse($tmp) }
rewrite { $new_const = AddConstants($fma_const, $add_const) // Pattern system automatically handles the transformation } } '''
Step 2: Build
'''bash cd build make -j$(nproc) '''
What happens:
- Pattern is parsed
- C++ matcher code is generated
- Pattern is automatically registered
- Pass will try your pattern on every instruction
Step 3: Test
Your pattern is now active! It will match and transform automatically.
''' [PeepholePass] Loaded 3 pattern(s) [Peephole] Applied MulAddFusion_F32 '''
Pattern Syntax
Basic Structure
''' pattern PatternName { match { // Instructions to match }
constraints { // Conditions that must be true }
rewrite { // How to transform (currently automatic) } } '''
Match Section
Syntax: ''' $instruction_var = opcode $dest, $src1, $src2, ... '''
Rules:
- First operand is always the destination register
- Pattern variables start with '$'
- Instructions are matched in temporal order (first executed first)
Example: ''' match { // First: FMA instruction $fma = v_fma_f32 $result, $a, $b, $const1
// Second: ADD instruction using FMA result $add = v_add_f32 $output, $const2, $result } '''
Constraints Section
Available constraints:
| Constraint | Meaning | Example |
|---|---|---|
| 'HasOneUse($reg)' | Register used exactly once | 'HasOneUse($tmp)' |
| 'IsConstant($reg)' | Register is a literal | 'IsConstant($const)' |
| 'SameValue($r1, $r2)' | Registers have same value | 'SameValue($a, $b)' |
| 'DifferentValue($r1, $r2)' | Registers differ | 'DifferentValue($x, $y)' |
Example: ''' constraints { HasOneUse($tmp) // $tmp only used by next instruction IsConstant($c1) // $c1 must be a literal value IsConstant($c2) // $c2 must be a literal value } '''
Rewrite Section
Currently, rewrites are automatic for fusion patterns:
- Constants are folded
- Instructions are modified in-place
- Dead instructions are removed
Built-in constant folding: ''' rewrite { $new = AddConstants($c1, $c2) // c1 + c2 $new = SubConstants($c1, $c2) // c1 - c2 $new = MulConstants($c1, $c2) // c1 * c2 $new = NegateConstant($c) // -c } '''
Complete Examples
Example 1: Add+FMA Fusion (F32)
Before: '''asm v_fma_f32 v0, a, b, 1.0 v_add_f32 v2, 1.0, v0 '''
After: '''asm v_fma_f32 v2, a, b, 2.0 # v0 eliminated! '''
Pattern: ''' pattern AddFMAFusion_F32 { match { $fma = v_fma_f32 $fma_result, $a, $b, $fma_const $add = v_add_f32 $add_dst, $add_const, $fma_result }
constraints { HasOneUse($fma_result) IsConstant($fma_const) IsConstant($add_const) }
rewrite { $new_const = AddConstants($fma_const, $add_const) } } '''
Example 2: Add+FMA Fusion (F16)
Before: '''asm v_fma_f16 v0, a, b, 0.5 v_add_f16 v1, 0.5, v0 '''
After: '''asm v_fma_f16 v1, a, b, 1.0 '''
Pattern: ''' pattern AddFMAFusion_F16 { match { $fma = v_fma_f16 $fma_result, $a, $b, $fma_const $add = v_add_f16 $add_dst, $add_const, $fma_result }
constraints { HasOneUse($fma_result) IsConstant($fma_const) IsConstant($add_const) }
rewrite { $new_const = AddConstants($fma_const, $add_const) } } '''
Tips & Best Practices
1. Always Use 'HasOneUse' for Intermediate Values
''' [x] Good: constraints { HasOneUse($tmp) // Safe to eliminate $tmp }
[ ] Bad: // Missing HasOneUse - might eliminate value that's used elsewhere! '''
2. Check for Constants When Folding
''' [x] Good: constraints { IsConstant($c1) IsConstant($c2) } rewrite { $result = AddConstants($c1, $c2) }
[ ] Bad: // Missing IsConstant - can't fold non-constant values! '''
3. Name Variables Descriptively
''' [x] Good: $fma_inst = v_fma_f32 $fma_result, $a, $b, $const
[ ] Bad: $i1 = v_fma_f32 $r1, $r2, $r3, $r4 '''
4. Add Comments Explaining the Optimization
''' //===----------------------------------------------------------------------===// // Pattern: NegateNegate // // Eliminates double negation: -(-x) => x // Benefit: Eliminates 2 instructions, frees registers //===----------------------------------------------------------------------===// '''
5. Order Patterns by Specificity
More specific patterns should come before general ones: ''' // Specific: mul by constant 1.0 pattern MulByOne { ... }
// General: any multiplication pattern MulFusion { ... } '''
6. Patterns Work Across Non-Consecutive Instructions
The pattern system uses def-use analysis, so patterns don't require strict consecutiveness:
''' [x] This works fine: v_fma_f32 v0, v1, v2, 1.0 v_mul_f32 v5, v6, v7 // Intervening instruction v_add_f32 v0, 1.0, v0 // Still matches and fuses! '''
The pattern matcher automatically verifies that:
- Dependencies are correct (FMA comes before ADD)
- Intermediate registers haven't been overwritten
''' [ ] This correctly doesn't match: v_fma_f32 v0, v1, v2, 1.0 v_mul_f32 v0, v6, v7 // Overwrites v0! v_add_f32 v3, 1.0, v0 // Uses v0 from v_mul, not v_fma '''
7. In-Place Operations Are Handled Correctly
Patterns work even when destination == source:
''' [x] In-place fusion works: v_fma_f32 v0, v1, v2, 1.0 // Defines v0 v_add_f32 v0, 1.0, v0 // Uses v0, writes v0 => v_fma_f32 v0, v1, v2, 2.0 // Correctly fused! '''
The pattern system uses position-aware def-use analysis to find the correct previous definition.
Testing Your Pattern
1. Write Test IR
Create test input: '''asm v_fma_f32 v0, a, b, 1.0 v_add_f32 v2, 1.0, v0 '''
2. Run the Pass
'''cpp auto pass = createPeepholeOptimizationPass(); pass->run(module); '''
3. Check Output
Expected output: '''asm v_fma_f32 v2, a, b, 2.0 '''
Console should show: ''' [PeepholePass] Loaded N pattern(s) [Peephole] Applied YourPatternName '''
Troubleshooting
Pattern Not Matching?
Check:
- Instruction opcodes are correct (e.g., 'v_fma_f32', not 'v_fma')
- Operand order is correct (destination first)
- Constraints are satisfied
- Instructions appear in the right order
Debug: '''bash
Check generated code
cat build/PeepholePatterns.inc | grep "YourPatternName"
Verbose build
make VERBOSE=1 | grep pattern '''
Build Errors?
Common issues:
- Syntax error in '.pattern' file -> Check pattern grammar
- Undefined opcode -> Verify opcode exists in GFX ISA
- Missing constraint -> Add required constraints
Fix: '''bash
Clean rebuild
cd build rm PeepholePatterns.inc make tablegen make '''
What's Automatic
You don't need to:
- Write C++ matcher code
- Write C++ rewrite code
- Register patterns manually
- Update the pass
- Handle def-use analysis
- Remove dead instructions
The system handles all of this automatically! [x]
What You Control
You do control:
- Which instructions to match
- What constraints to check
- How constants are folded
- Pattern priority (order in file)
Advanced: Pattern Variants
Same Pattern for Multiple Types
Create separate patterns for each type: ''' pattern AddFMAFusion_F32 { ... v_fma_f32 ... } pattern AddFMAFusion_F16 { ... v_fma_f16 ... } pattern AddFMAFusion_F64 { ... v_fma_f64 ... } '''
Pattern Families
Group related patterns: ''' //===----------------------------------------------------------------------===// // FMA Fusion Family //===----------------------------------------------------------------------===//
pattern AddFMAFusion_F32 { ... } pattern MulAddFusion_F32 { ... } pattern SubFMAFusion_F32 { ... } '''
File Locations
| File | Purpose |
|---|---|
| 'src/transforms/asm/PeepholePatterns.pattern' | Your patterns go here |
| 'build/PeepholePatterns.inc' | Generated C++ code (auto) |
| 'src/transforms/asm/PeepholeOptimizationPass.cpp' | Generic pass (never edit) |
| 'tools/tablegen/GenPatterns.cpp' | Code generator (rarely edit) |
Summary
To add a pattern:
- Edit 'PeepholePatterns.pattern'
- Run 'make'
- Done!
Pattern structure: ''' pattern Name { match { ... } // Instructions to find constraints { ... } // Conditions to check rewrite { ... } // How to transform } '''
The system automatically:
- Parses your pattern
- Generates efficient C++ code
- Registers your pattern
- Applies it during compilation
You just write the pattern! !
Next Steps
- See Architecture Overview for system architecture
- See Pattern Grammar Reference for complete syntax reference
- See 'src/transforms/asm/PeepholePatterns.pattern' for real examples
Questions?
Common patterns are already implemented. Just copy and modify them for your needs!