OpPhi is not a performance function. It’s a common tool for static single assignment (SSA) languages when dealing with conditional branching.
In SSA, each variable a function creates can only ever be written to once. But if you want to conceptually have a conditional branch that writes to a variable based on that condition, that seems impossible with SSA. You would have to have two writes to the same variable, which SSA does not allow.
The way SSA languages works around this is with the “Phi” operation. In the two branches, each branch writes to a different ResultID. After the branches, the Phi function writes to a third ResultID, using data read from ResultIDs generated in the two branches. Which one’s fills data fills in the third ResultID is determined by which branch was taken at runtime. In effect, the Phi instruction merges results in the two branches.
SPIR-V’s way of handling Phi operations deals with blocks. You use a conditional branch instruction to jump into some number of blocks. At the end of both blocks, you use an unconditional branch to jump into the same subsequent block. Within that block, you execute OpPhi instructions to merge the results from the conditional branch together.
So OpPhi being at the start of a block also means that OpPhi instructions are always the first thing you do after a conditional branch. This avoids situations where the compiler has to look ahead in the branching structure to see where you’re merging conditional branching results.
It isn’t there to “remove the need for loads/stores”. It’s a tool that makes SSA work.