[llvm-dev] [RDF] Question about function argument registers on X86

36 views
Skip to first unread message

Constable, Scott D via llvm-dev

unread,
Jan 22, 2022, 4:23:58 PM1/22/22
to llvm...@lists.llvm.org

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 [+d9<DIH:0000000000000001>(,,u15"):]

p10: phi [+d11<DIL:0000000000000001>(,,u14"):]

p12: phi [+d13<HDI:0000000000000001>(,,u5"):]

s3: COPY [d4<RAX>(,,u7):, u5"<RDI>(+d13):, u14"<RDI>(+d11):, u15"<RDI>(+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

 

Krzysztof Parzyszek via llvm-dev

unread,
Jan 24, 2022, 2:48:06 PM1/24/22
to Constable, Scott D, via llvm-dev

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).

 

 

--

Krzysztof Parzyszek  kpar...@quicinc.com   AI tools development

 

From: llvm-dev <llvm-dev...@lists.llvm.org> On Behalf Of Constable, Scott D via llvm-dev
Sent: Saturday, January 22, 2022 3:24 PM
To: llvm...@lists.llvm.org
Subject: [llvm-dev] [RDF] Question about function argument registers on X86

 

WARNING: This email originated from outside of Qualcomm. Please be wary of any links or attachments, and do not enable macros.

Constable, Scott D via llvm-dev

unread,
Jan 24, 2022, 4:11:32 PM1/24/22
to Krzysztof Parzyszek, via llvm-dev

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

Krzysztof Parzyszek via llvm-dev

unread,
Jan 24, 2022, 4:19:29 PM1/24/22
to Constable, Scott D, via llvm-dev

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.

 

 

--

Krzysztof Parzyszek  kpar...@quicinc.com   AI tools development

 

From: Constable, Scott D <scott.d....@intel.com>
Sent: Monday, January 24, 2022 3:11 PM
To: Krzysztof Parzyszek <kpar...@quicinc.com>
Cc: via llvm-dev <llvm...@lists.llvm.org>
Subject: RE: [RDF] Question about function argument registers on X86

 

WARNING: This email originated from outside of Qualcomm. Please be wary of any links or attachments, and do not enable macros.

Constable, Scott D via llvm-dev

unread,
Jan 24, 2022, 4:22:56 PM1/24/22
to Krzysztof Parzyszek, via llvm-dev

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

Constable, Scott D via llvm-dev

unread,
Jan 26, 2022, 10:57:48 AM1/26/22
to Krzysztof Parzyszek, via llvm-dev

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));

-  }

-

-  // Add function-entry phi nodes for the live-in registers.

-  //for (std::pair<RegisterId,LaneBitmask> P : LiveIns) {

-  for (auto I = LiveIns.rr_begin(), E = LiveIns.rr_end(); I != E; ++I) {

-    RegisterRef RR = *I;

-    NodeAddr<PhiNode*> PA = newPhi(EA);

-    uint16_t PhiFlags = NodeAttrs::PhiRef | NodeAttrs::Preserving;

-    NodeAddr<DefNode*> DA = newDef(PA, RR, PhiFlags);

-    PA.Addr->addMember(DA, *this);

+    for (auto I = MRI.livein_begin(), E = MRI.livein_end(); I != E; ++I) {

+      RegisterRef RR(I->first);

+      NodeAddr<PhiNode*> PA = newPhi(EA);

+      uint16_t PhiFlags = NodeAttrs::PhiRef | NodeAttrs::Preserving;

+      NodeAddr<DefNode*> DA = newDef(PA, RR, PhiFlags);

+      PA.Addr->addMember(DA, *this);

+    }

   }

 

Thanks,

 

Scott Constable

Krzysztof Parzyszek via llvm-dev

unread,
Jan 26, 2022, 11:16:58 AM1/26/22
to Constable, Scott D, via llvm-dev

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.

Reply all
Reply to author
Forward
0 new messages