Skip to content

Commit 2491ae3

Browse files
Identify phi
1 parent 70bf6f4 commit 2491ae3

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

25 KB
Loading
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# GSA Analysis
2+
## Introduction
3+
In Static Single Assignment (SSA) form, every variable is assigned exactly once, and ϕ (phi) functions are introduced to merge values coming from different control flow paths. While SSA is powerful, it does not explicitly encode the control-flow decisions that determine which value is actually chosen at runtime.
4+
5+
**Gated Single Assignment (GSA)** was introduced as an extension of SSA to make these control-flow decisions explicit. Instead of a single generic ϕ merge, GSA introduces specialized gates:
6+
- The **μ (mu) gate** appears at loop headers. It chooses between an initial value coming from outside the loop and a value produced inside the loop. The decision is driven by the loop’s condition: if the loop is starting, the initial value is used; if the loop is iterating, the loop value is used.
7+
- The **γ (gamma) gate** replaces a ϕ at control-flow merges. It selects between a true value and a false value depending on a condition signal (like at the end of an if–else). In hardware, this becomes a multiplexer.
8+
9+
For Dynamatic’s **Fast Token Delivery (FTD)** algorithm, having the program represented in GSA form is required. The MLIR cf dialect already provides ϕ-gates in SSA form, but these must be translated into their GSA equivalents. During this translation, every block argument(pottential ϕ) in the control-flow is rewritten as either a μ or a γ gate.
10+
11+
### Example
12+
Consider the following control-flow graph and its corresponding `cf_dyn_transformed.mlir` code.
13+
- bb1 and bb3 both receive arguments from multiple predecessors. Implicit ϕ-gates are therefore placed in these blocks.
14+
15+
- The first argument of bb1 (%0) chooses between the initial value %c0 from bb0 and the loop-carried value %8 from bb3. This corresponds to a μ function.
16+
17+
- The second argument of bb1 (%1) is also updated inside the loop, so it too becomes a μ function.
18+
19+
- The argument of bb3 (%7) comes from two mutually exclusive control-flow paths (bb1 or bb2). This corresponds to a γ function.
20+
21+
![CFG](./Figures/if_loop_add_CFG.png)
22+
23+
```
24+
module {
25+
func.func @if_loop_add(%arg0: memref<1000xf32> {handshake.arg_name = "a"}, %arg1: memref<1000xf32> {handshake.arg_name = "b"}) -> f32 {
26+
%c0 = arith.constant {handshake.name = "constant2"} 0 : index
27+
%cst = arith.constant {handshake.name = "constant3"} 0.000000e+00 : f32
28+
cf.br ^bb1(%c0, %cst : index, f32) {handshake.name = "br0"}
29+
^bb1(%0: index, %1: f32): // 2 preds: ^bb0, ^bb3
30+
%cst_0 = arith.constant {handshake.name = "constant4"} 0.000000e+00 : f32
31+
%2 = memref.load %arg0[%0] {handshake.mem_interface = #handshake.mem_interface<MC>, handshake.name = "load2"} : memref<1000xf32>
32+
%3 = memref.load %arg1[%0] {handshake.mem_interface = #handshake.mem_interface<MC>, handshake.name = "load3"} : memref<1000xf32>
33+
%4 = arith.subf %2, %3 {handshake.name = "subf0"} : f32
34+
%5 = arith.cmpf oge, %4, %cst_0 {handshake.name = "cmpf0"} : f32
35+
cf.cond_br %5, ^bb2, ^bb3(%1 : f32) {handshake.name = "cond_br0"}
36+
^bb2: // pred: ^bb1
37+
%6 = arith.addf %1, %4 {handshake.name = "addf0"} : f32
38+
cf.br ^bb3(%6 : f32) {handshake.name = "br1"}
39+
^bb3(%7: f32): // 2 preds: ^bb1, ^bb2
40+
%c1000 = arith.constant {handshake.name = "constant5"} 1000 : index
41+
%c1 = arith.constant {handshake.name = "constant6"} 1 : index
42+
%8 = arith.addi %0, %c1 {handshake.name = "addi0"} : index
43+
%9 = arith.cmpi ult, %8, %c1000 {handshake.name = "cmpi0"} : index
44+
cf.cond_br %9, ^bb1(%8, %7 : index, f32), ^bb4 {handshake.name = "cond_br1"}
45+
^bb4: // pred: ^bb3
46+
return {handshake.name = "return0"} %7 : f32
47+
}
48+
}
49+
```
50+
### Translation Process
51+
The conversion from SSA to GSA is done in three main steps:
52+
53+
1. Identify implicit ϕ gates introduced by SSA form.
54+
55+
2. Convert ϕ gates into μ gates
56+
57+
3. Convert remaining ϕ gates into γ gates.
58+
59+
## Identify Implicit ϕ Gates
60+
In the `convertSSAToGSA` function, the first step is to convert all block arguments in the IR into ϕ gates, carefully extracting information about their producers and senders. Later, these ϕ gates are transformed into either γ or μ gates. In this section, we focus on the details of this first step.
61+
62+
Note: If there is only one block in the region being checked, nothing needs to be done since there is no possibility of multiple assignments.
63+
64+
In pseudo-code, the process looks like this:
65+
```
66+
For each block in the region:
67+
For each argument of the block:
68+
→ treat this argument as a potential ϕ.
69+
70+
For each predecessor of the block:
71+
Identify the branch terminator that jumps into the block.
72+
Extract the value passed to the argument.
73+
74+
If the value is a block argument and its parent block has predecessors(so its parent is not bb0):
75+
→ this value is itself the output of another ϕ.
76+
Record it as a “missing phi” to be connected later.
77+
Else:
78+
→ the value is a plain input and can be added directly.
79+
80+
In both cases, check if the value is already recorded:
81+
- `isBlockArgAlreadyPresent` checks block arguments.
82+
- `isValueAlreadyPresent` checks plain SSA values.
83+
84+
If the value is new:
85+
- Wrap it in a `gateInput` structure.
86+
- If it is a missing phi:
87+
* Add it to `phisToConnect` (records phis that need reconnection later).
88+
* Add it to `operandsMissPhi` (helps `isBlockArgAlreadyPresent` detect duplicates).
89+
- Add the predecessor block to the `senders` list of this gate input.
90+
- Add the gate input to `gateInputList` (the global list of all gate inputs).
91+
- Add the gate input to `operands` (the inputs of the current ϕ).
92+
93+
After all predecessors are processed:
94+
If `operands` is not empty (the ϕ has at least one input):
95+
→ create the ϕ gate and associate it with the block.
96+
```
97+
After all ϕ gates are created, the final step is to connect the missing inputs recorded in phisToConnect.
98+
99+
## Convert ϕ Gates into μ Gates
100+
101+
## Convert ϕ Gates into γ Gates

0 commit comments

Comments
 (0)