Function Call Lowering in LLVM Backend

338 阅读1分钟

Premiere

int f(int a, int b) { return a + b; }
int g() { return f(1, 2); }

In function g, f is the callee function with actual arguments 1 and 2, whereas g is the caller function. Therefore, to properly invoke f, g needs to know three things:

  1. How to set up the calling environment.
  2. Prepare inputs for f.
  3. Get outputs from f.

While, f needs to know :

  1. Prepare environment.
  2. Get inputs prepared by g.
  3. Set outputs for g.

Those information constitue the Calling Convention of a Platform, i.e. (ISA + OS + ABI).

Caller Part

  1. Save caller saved register.
  2. pass actual argument on stack or registers.
  3. jump to subroutine.
  4. get results from stack or registers.

This calling sequence is implemented in following method in LLVM.

SDValue TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
                    SmallVectorImpl<SDValue> &InVals) const override;

The InVals should be set with call result. That is, if the result is return on stack, then InVals should contain a Load from stack to a virtual register; if the result is return at registers, the InVals should contain CopyFromReg to copy the return registers to virtual registers.

The CLI contains all the call information, such as inputs, outputs, callee function etc.

Callee Part

  1. set up function frame
  2. Save Callee saved register.
  3. get actual argument on stack or registers.
  4. Do its own operations.
  5. set outputs on stack or registers.
  6. Clean up function frame.

This is implemented in following methods in LLVM.

void TargetFrameLowering::determineCalleeSaves(MachineFunction &MF, 
                            BitVector &SavedRegs,
                            RegScavenger *RS) const override;

bool TargetFrameLowering::spillCalleeSavedRegisters(MachineBasicBlock &MBB,
                                 MachineBasicBlock::iterator MI,
                                 ArrayRef<CalleeSavedInfo> CSI,
                                 const TargetRegisterInfo *TRI) const override;

void TargetFrameLowering::emitPrologue(MachineFunction &MF, 
                                      MachineBasicBlock &MBB) const override;

SDValue TargetLowering::LowerFormalArguments(SDValue Chain, 
                               CallingConv::ID CallConv,
                               bool IsVarArg,
                               const SmallVectorImpl<ISD::InputArg> &Ins,
                               const SDLoc &dl, SelectionDAG &DAG,
                               SmallVectorImpl<SDValue> &InVals) const override;

bool TargetLowering::CanLowerReturn(CallingConv::ID CallConv,
                      MachineFunction &MF,
                      bool isVarArg,
                      const SmallVectorImpl<ISD::OutputArg> &Outs,
                      LLVMContext &Context) const override;

SDValue TargetLowering::LowerReturn(SDValue Chain, 
                      CallingConv::ID CallConv, bool IsVarArg,
                      const SmallVectorImpl<ISD::OutputArg> &Outs,
                      const SmallVectorImpl<SDValue> &OutVals, const SDLoc &dl,
                      SelectionDAG &DAG) const override;

void TargetFrameLowering::emitEpilogue(MachineFunction &MF, 
                                       MachineBasicBlock &MBB) const override;

In LowerFormalArguments(), first thing to do is to analyze the Ins to see where the incoming arguments stored, then construct corresponding instructions (Load or CopyFromReg) to get those arguments into virtual register for further processing in InVals.

In LowerReturn(), it is similar, but in reversed way, that is to analyze the Outs to see where the outputs stored, then construct corresponding instructions (Store or CopyToReg) to set those outputs into memory or registers  in OutVals.