You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Improve preferencing and code generation for FMA #12984
As pointed out in dotnet/coreclr#25387, there are improvements to be made in the handling of the FMA intrinsics:
Lowering should bias the containment such that the overwritten operand is a last use if possible.
The register allocator should preference the overwritten operand.
If all the operands are in registers, the code generator should choose the encoding based on whether the target register matches one of the input registers.
This probably doesn't prioritize over the other work in progress, but I believe it is a fairly straightforward issue to fix, so perhaps it would be a good task for someone wanting to gain expertise in the backend of the JIT. But perhaps change to Future.
Hi, @CarolEidt. I'm interested in this issue and would like to work on it. Could you please help me to confirm if my understanding of the requirement is correct? Thanks!
What we want to improve here is to generate the best fit FMA instruction for situations that the computation result is written into one of the three input operands. To achieve this, we need to add checks and bias containment in lowering. And also do corresponding works in LSRA and Codegen phases.
Here are the three places I'm planning to add code in:
Hi, @kunalspathak. Thanks for the confirmation!
I have one follow up question about what is the classical method to check whether one of the operand is overwritten. I was able to do this by checking if one of the operand's lclNum (GetLclNum()) equals to this value of the next node(node->gtNext). Is there a more decent way to get the variable that the result is written into instead of getting the next node?
IIRC, most of these issues were root caused to #6358. That is, to allow the register allocator to set multiple operands as "optional" for the cases where the instruction is either commutative (e.g. Add, Multiply, etc) or where it has multiple forms (e.g. FusedMultiplyAdd).
There are several notes in both of the related issues that provide some additional analysis into the issue and potential avenues for fixing it.
Sorry for the delay in response (I'm now retired). You've got the right general idea. If you've got an expression like STORE_LCL_VAR(FMA(op1,op2,op3)) where one of the operands is a LCL_VAR with the same lclNum as the STORE_LCL_VAR then you want to preference that operand to that lclVar Interval. Unfortunately, it seems that NodesAreEquivalentLeaves() doesn't handle STORE_LCL_VAR, so you'd need to adapt it to do that, or simply check those lclNums directly.
Reacted by Tanner Gooding, Clinton Ingram, Gert Driesen, weilinwa and John Tur
@tannergooding@kunalspathak, I implemented a solution by comparing the lclNum of the overwritten with the three ops and choose FMA accordingly if there is a match.
The idea is that only the 3rd case in these three types of computation should be generated to 231 form.
a = a*b+c
b = a*b+c
c = a*b+c
The code is looking like this. Is this on the right track?
I agree. This looks like a good improvement over the codegen we had. Kunal is probably the best to comment if the LSRA changes are correct or will run into any issues.
We detect such redundant moves in arm64 using IsRedundantLdStr](
@kunalspathak, we don't have an equivalent in x86/x64 AFAIK. We only recently added minimal redundant move detection in .NET 6
// For ldr/str pair next to each other, check if the current load or store is needed or is
// the value already present as of previous instruction.
//
// ldr x1, [x2, #56]
// str x1, [x2, #56] <-- redundant
//
// OR
//
// str x1, [x2, #56]
// ldr x1, [x2, #56] <-- redundant
// Arguments:
// ins - The current instruction
// dst - The current destination
// src - The current source
// imm - Immediate offset
// size - Operand size
// fmt - Format of instruction
// Return Value:
// true if previous instruction already has desired value in register/memory location.
) and should probably extend it for xarch.
I have a commit to extend this to xarch. The commit will essentially mirror what we do for Register->Register movs but with slight modifications to customize it for mem<->reg mov.
There are a couple of further optimizations to be revisited for both this and the existing IsRedundantMov() implementation(These are already mentioned as comments in existing impl)
neither will work for sse on targets with avx encoding since it cannot be determined the current way it's implemented if the upper bits have been modified.
Currently, we check if both instructions(curr and predecessor) are equivalent. We can extend it for some other cases like aligned/unaligned(movups/movaps for eg.)
Activity
BruceForstall commentedon Apr 29, 2020
@CarolEidt Should this remain a 5.0 issue, or should it be moved to Future?
CarolEidt commentedon Apr 29, 2020
This probably doesn't prioritize over the other work in progress, but I believe it is a fairly straightforward issue to fix, so perhaps it would be a good task for someone wanting to gain expertise in the backend of the JIT. But perhaps change to Future.
BruceForstall commentedon Apr 29, 2020
I went ahead and moved it to Future, but we can always choose to bring it back.
weilinwa commentedon Jul 6, 2021
Hi, @CarolEidt. I'm interested in this issue and would like to work on it. Could you please help me to confirm if my understanding of the requirement is correct? Thanks!
What we want to improve here is to generate the best fit FMA instruction for situations that the computation result is written into one of the three input operands. To achieve this, we need to add checks and bias containment in lowering. And also do corresponding works in LSRA and Codegen phases.
Here are the three places I'm planning to add code in:
BruceForstall commentedon Jul 6, 2021
@kunalspathak @tannergooding
kunalspathak commentedon Jul 6, 2021
@weilinwa - That looks right.
weilinwa commentedon Jul 7, 2021
Hi, @kunalspathak. Thanks for the confirmation!
I have one follow up question about what is the classical method to check whether one of the operand is overwritten. I was able to do this by checking if one of the operand's lclNum (GetLclNum()) equals to this value of the next node(node->gtNext). Is there a more decent way to get the variable that the result is written into instead of getting the next node?
tannergooding commentedon Jul 7, 2021
We also have #12212 and #11260 as related issues.
IIRC, most of these issues were root caused to #6358. That is, to allow the register allocator to set multiple operands as "optional" for the cases where the instruction is either commutative (e.g.
Add,Multiply, etc) or where it has multiple forms (e.g.FusedMultiplyAdd).There are several notes in both of the related issues that provide some additional analysis into the issue and potential avenues for fixing it.
kunalspathak commentedon Jul 7, 2021
There might be better way to do this, but one of the method that I can think of is
NodesAreEquivalentLeaves():runtime/src/coreclr/jit/lower.cpp
Line 6257 in d4b98b9
CarolEidt commentedon Jul 9, 2021
Sorry for the delay in response (I'm now retired). You've got the right general idea. If you've got an expression like
STORE_LCL_VAR(FMA(op1,op2,op3))where one of the operands is aLCL_VARwith the same lclNum as theSTORE_LCL_VARthen you want to preference that operand to that lclVarInterval. Unfortunately, it seems thatNodesAreEquivalentLeaves()doesn't handleSTORE_LCL_VAR, so you'd need to adapt it to do that, or simply check those lclNums directly.kunalspathak commentedon Jul 9, 2021
Nice to see you @CarolEidt ! :)
weilinwa commentedon Jul 12, 2021
@CarolEidt, thanks for your response!
I will go ahead to do the implementation then.
weilinwa commentedon Jul 23, 2021
@tannergooding @kunalspathak, I implemented a solution by comparing the lclNum of the overwritten with the three ops and choose FMA accordingly if there is a match.
The idea is that only the 3rd case in these three types of computation should be generated to 231 form.
The code is looking like this. Is this on the right track?
weilinwa commentedon Jul 23, 2021
Here are some of the results I got using the test cases from those related issues:
Original codegen:
New codegen:
weilinwa commentedon Jul 23, 2021
Original codegen:
New codegen:
kunalspathak commentedon Jul 23, 2021
@weilinwa - Thanks, this looks pretty good.
weilinwa commentedon Jul 28, 2021
@kunalspathak @tannergooding, I also have a question about instructions like
Are these two instructions in sequence redundant? I saw this type of instructions in different places. Should we consider to optimize them?
kunalspathak commentedon Jul 28, 2021
We should understand why these are getting generated. We detect such redundant moves in arm64 using IsRedundantLdStr](
runtime/src/coreclr/jit/emitarm64.cpp
Lines 15626 to 15648 in cd1b4cf
tannergooding commentedon Aug 6, 2021
I agree. This looks like a good improvement over the codegen we had. Kunal is probably the best to comment if the LSRA changes are correct or will run into any issues.
@kunalspathak, we don't have an equivalent in x86/x64 AFAIK. We only recently added minimal redundant move detection in .NET 6
DeepakRajendrakumaran commentedon Sep 22, 2021
I have a commit to extend this to xarch. The commit will essentially mirror what we do for Register->Register movs but with slight modifications to customize it for mem<->reg mov.
There are a couple of further optimizations to be revisited for both this and the existing IsRedundantMov() implementation(These are already mentioned as comments in existing impl)
BruceForstall commentedon Oct 30, 2022
Issue appears fixed; closing