I periodically stumble upon strange COPYs and KILLs that have more that just one def and one use operands. What do the extra operands mean? It doesn’t seem to be documented anywhere…
Take this commit. I don’t understand how adding an implicit use of the super-register solves the problem. Quote:
%R0<def> = KILL %R0, %R0_R1<imp-def> ; <-- this tells R1 is redefined
%R1<def> = KILL %R1, %R0_R1<imp-def>, %R0_R1<imp-use> ; this value of this R1
; is believed to come
; from the previous
; instruction
Now we would generate:
%R0<def> = KILL %R0, %R0_R1<imp-def>, %R0_R1<imp-use> ; This tells R1 needs to
; reach this point
%R1<def> = KILL %R1, %R0_R1<imp-def>, %R0_R1<imp-use>
The %R0_R1 is still redefined by the first instruction (isn’t it?) and the second instruction reads the new (clobbered) value of %R0_R1. I seem to miss the point
Could someone please explain how this works?
An extra question. Suppose I have a load of %R0_R1 from the stack, and I know that the %R0 part is undefined (because I stored an undef value to the stack earlier). It is somehow possible to flag that %R0 half reads an undefined value? Does it even make sense to do this?
This all had to do with how register liveness was tracked. That code has evolved significantly over the years, and what was needed back then may no longer apply now.
After register allocation there aren’t any explicit sub-registers in register operands, but the sub-/super-register relationships still existed and needed to be accounted for. Right now physical register liveness is based on register units, plus sub-register liveness allows for tracking (virtual) sub-registers separately from their super-registers. I don’t remember exactly the details of physical register tracking back then, but when you had a definition of R0, and you wanted to use the super-register R0_R1, you needed to explicitly indicate (on the definition of R0) what it meant for the state of R0_R1. So, if you wanted to define the sub-registers R0 and R1 separately, while using R0_R1 as a whole, you needed to be explicit about the liveness of R0_R1. For example
... = %R0_R1 ; assume R0_R1 is live here
... ; unrelated stuff
%R1 = COPY %R99 ; what does this do to R0_R1?
... = %R0_R1 ; is R0 defined or undef?
There were some implicit assumptions made (which I don’t remember now), but if you wanted such code to work correctly, you had to append additional tracking information for R0_R1:
... = %R0_R1 ; assume R0_R1 is live here
... ; unrelated stuff
%R1 = COPY %R99, %R0_R1<imp-def>, %R0_R1<imp-use> ; R0 and R0_R1 still live before and after
; this instruction
... = %R0_R1 ; R0 defined, keeps original value
; R0_R1 defined
The “documentation” is in llvm/lib/CodeGen/LiveInterval[s].cpp, I’m not sure if there is any in plain English though.
You still usually require extra implicit physical register operands to correctly express the liveness requirements of super registers. Subregister liveness can help reduce the set of implicit operands required and these operand sets are often more conservative than is required. The MIR expression of sub/super registers hasn’t changed.
Thanks,
So %R0_R1<imp-def>, %R0_R1<imp-use> doesn’t mean “kill the old value, define a new one”, but rather “preserve the value”? This is quite counterintuitive and raises further questions like “what if the implicit def is marked as dead / what if the implicit use marked as undef?” and also %R0_R1 = COPY %R99, %R0<imp-def>, %R0<imp-use>
What does this do to %R0? Does it preserve its value or overwrites with a new one? Or does it not matter what value %R0 holds as long as it is alive?
I’ll look into LiveIntervals, again (not easy stuff!), but any additional information is greatly appreciated.
There is some nuance there—there is a value number, and there is the actual content of the register. Having %R0_R1<imp-def>, %R0_R1<imp-use> on a definition of R1 will make sure that the live range of R0 doesn’t have any “holes” where someone could insert a def-use range for it. This makes sure that the contents of R0 are unchanged.
From the point of view of liveness tracking (done with the use of value numbering) the VN for R0 may as well become different, but the tracking code may recognize this and keep it the same. Strictly speaking, what you wrote is correct: Rn<imp-def>, Rn<imp-use> means kill-then-def. What I’m not sure about is where the super-register liveness shows up, since physical liveness is based on register units, but as Matt stated above, someone is keeping track of it. I haven’t looked at that code in a while…