[RDF] Question about function argument registers on X86

Hi,

Given the function:

long simpleExample(long x) {

return x;

}

RDF produces the following data-flow graph:

DFG dump:[

f1: Function: simpleExample

b2: — %bb.0 — preds(0): succs(0):

p8: phi [+d9DIH:0000000000000001(,u15"):]

p10: phi [+d11DIL:0000000000000001(,u14"):]

p12: phi [+d13HDI:0000000000000001(,u5"):]

s3: COPY [d4(,u7):, u5"(+d13):, u14"(+d11):, u15"(+d9):]

s6: RET [u7<RAX>!(d4):]

]

Why is the argument in RDI split into three lanes that do not fully span RDI? It would seem more natural to just have a single phi for RDI.

I think the RDF code responsible for this behavior is located here: https://github.com/llvm/llvm-project/blob/55d887b833646baeea0e3371fd2cbbd7550a8d4d/llvm/lib/CodeGen/RDFGraph.cpp#L903, but I admit I do not fully understand what is going on.

Thanks in advance,

Scott Constable

Hi Scott,

It used to be the case early on that we’d create an entry for the register as it was specified, but I’ve been running into all sorts of complications when calculating liveness. Specifically, when defs/uses of “large” registers were intermingled with defs/uses of their sub-registers, various “interesting” scenarios showed up. They had to do with representing unions/intersections of registers in terms of actual registers[1]—that was the reason for switching to tracking register units instead (see RegisterAggr class).

The consequence of that is that the graph looks more cluttered, but things are easier to express accurately.

[1] This was before lane masks became popular. Say we have

AX = def

AH = clobber

Here AX is still “defined” since some part of it is defined. Now, if we know that AX is “defined” then what can we say about it after executing “AL = clobber”? Turns out that this is difficult to answer, because we’d have to know the history of how AX was modified, which isn’t always analyzed in full: (RDF stop def traversal at a PHI node).

Thank you very much for the explanation, Krzysztof. There is still one part that I am missing. In the example below, RDF generates defs for DIL (bits 0:7 of RDI), DIH (bits 8:15 of RDI), and HDI (bits 16:31 of RDI). Hence bits 32:63 are unaccounted. Could this cause any issues? For example, suppose the program writes to RDI, and then clobbers EAX, but then the upper half of RDI is still defined.

Scott

The reason is that storing to EDI actually overwrites the upper 32 bits of RDI, so the EDI and RDI are considered identical in terms of register units.

Right! That really should have occurred to me ;). Thanks again.

Scott

Hi Krzysztof,

I have been looking at more examples of RDF graphs, and it seems that this register lane splitting is only being applied to function arguments. Anywhere else in the function where where a 64-bit GPR is being def’ed, I only see a single def for that GPR. As an example, in simpleFunction() below there is one def for RAX instead of three def’s for HAX, AXH, and AXL.

Could you clarify this behavior?

The motivation for my inquiry is that I am working on an analysis that benefits from an SSA property, namely, that each use has a unique def (possibly a phi). But in RDF this property does not hold for function arguments, so the analysis becomes more tricky. As an experiment, I changed the behavior of RDF to generate only a single phi for each argument, and now my analysis works as expected (and I’m not seeing any crashes, etc.). The code I changed is in DataFlowGraph::build():

MachineRegisterInfo &MRI = MF.getRegInfo();

MachineBasicBlock &EntryB = *EA.Addr->getCode();

assert(EntryB.pred_empty() && “Function entry block has predecessors”);

  • for (std::pair<unsigned,unsigned> P : MRI.liveins())

  • LiveIns.insert(RegisterRef(P.first));

if (MRI.tracksLiveness()) {

  • for (auto I : EntryB.liveins())

  • LiveIns.insert(RegisterRef(I.PhysReg, I.LaneMask));

  • }

Hi Scott,

Hmm, you’re right. I guess the original reasons for this may now be lost in antiquity… One reason I can think of why we’d want to do this would be to ensure that we don’t have overlapping input registers. Back before subregister liveness tracking was implemented in codegen, many instructions that use/def a subregister would have implicit uses/defs of the super-registers. I’m not sure if that ever had any impact on how function live-ins were represented, so it’s just a speculation.

Feel free to create a patch, I’ll run some tests on it in our downstream repo. If it works, it should be ok to commit it.