CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
488 lines
9.5 KiB
Markdown
488 lines
9.5 KiB
Markdown
# How to Add Peephole Optimization Patterns
|
|
|
|
This guide shows you how to add new optimization patterns to the StinkyTofu compiler.
|
|
|
|
## Quick Start
|
|
|
|
**Three steps:**
|
|
1. Write pattern in '.pattern' file
|
|
2. Run 'make'
|
|
3. 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:**
|
|
- [x] Pattern is parsed
|
|
- [x] C++ matcher code is generated
|
|
- [x] Pattern is automatically registered
|
|
- [x] 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:**
|
|
1. [x] Instruction opcodes are correct (e.g., 'v_fma_f32', not 'v_fma')
|
|
2. [x] Operand order is correct (destination first)
|
|
3. [x] Constraints are satisfied
|
|
4. [x] 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:
|
|
- [x] Which instructions to match
|
|
- [x] What constraints to check
|
|
- [x] How constants are folded
|
|
- [x] 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:**
|
|
1. Edit 'PeepholePatterns.pattern'
|
|
2. Run 'make'
|
|
3. 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](architecture.md) for system architecture
|
|
- See [Pattern Grammar Reference](pattern-grammar.md) 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!
|