Photo by John Gamell on Unsplash
Introduction
In the previous article, two files related to calling convention are introduced: <Arch>CallingConv.td and <Arch>ISelLowering.cpp. However, in <Arch>ISelLowering.cpp, we only discuss LowerFormalArguments(). The other two functions, LowerReturn() and LowerCall() would be discussed in this article and we still take the source code of Sparc architecture as an example.
Source Code Analysis
1. LowerReturn()
This function deals with return values of a callee function. Similar to LowerFormalArguments(), it would use the following code to get the location information of return values.
CCInfo.AnalyzeReturn(Outs, RetCC_Sparc32);
Here, Outs represents the set of return values and RetCC_Sparc32 is a function auto-generated according to SparcCallingConv.td. AnalyzeReturn() would iterate each return value and use the information obtained from RetCC_Sparc32 to choose the correct register. The results would be put in the container RVLocs.
Then, a for loop would iterate RVLocs and generate a CopyToReg node for each return value. The behavior CopyToReg of is to copy the value from the virtual register to the physical register.
Finally, a SPISD::RET_FLAG node would be generated and it could be seen as the return instruction in the SelectionDAG stage.
2. LowerCall()
LowerCall() deals with functions parameters and return values in a caller function. There are three steps:
- Function parameters
As mentioned in the previous article, LowerFormalArguments() would move the function parameters from physical registers or the stack to virtual registers. LowerCall() would do the opposite. It would generate SDNodes to pass function parameters from virtual registers to physical registers or the stack.
First, it would use the following code to get the location information of each parameter and the information would be stored in a container ArgLocs.
CCInfo.AnalyzeCallOperands(Outs, CC_Sparc32);
Second, it would iterate each parameter in ArgLocs and there would be two cases:
- VA.isRegLoc() returns true
This means the parameter has to be passed through a register and a CopyToReg node would be generated to do this. The behavior of CopyToReg is to copy parameters from virtual registers to physical registers. For Sparc, these physical registers are O0 to O5.
- VA.isMemLoc() returns true
This means the parameter has to be passed through the stack. First, the location in the stack would be calculated. For example, for Sparc, the location of the 8th parameter would be %sp + 96. After calculating the location, a store node would be generated to store the paramater from virtual register to this location in the stack.
- Generate SPISD::CALL node
After passing parameters, a SPISD::CALL node would be generated and it could be seen as the call instruction in the SelectionDAG stage.
- Return values
Similar to LowerReturn(), LowerCall() would use the information obtained from RetCC_Sparc32 and generate CopyFromReg nodes to copy return values from physical registers to virtual registers.
Ok, the above is the mechanism of LowerReturn() and LowerCall(). In the following, we would use an example to see the SDNodes generated from LowerCall().
Experiment
First, for convenience, we change the number of registers from 6 to 1 in CC_Sparc32.
def CC_Sparc32 : CallingConv<[
...
// i32 f32 arguments get passed in integer registers if there is space.
CCIfType<[i32, f32], CCAssignToReg<[I0]>>,
...
]>;
Then, prepare an LLVM IR code which contains a main() and a foo() which represent the caller and the callee, respecively. foo() has two parameters, %m and %n, and it would return 0. According to the previous analysis, it is expected that %m would be passed through the register and %n would be passed through the stack.
define i32 @foo(i32 %m, i32 %n) nounwind uwtable {
entry:
%m.addr = alloca i32, align 4
%n.addr = alloca i32, align 4
store i32 %m, i32* %m.addr, align 4
store i32 %n, i32* %n.addr, align 4
ret i32 0
}
define i32 @main() nounwind uwtable {
entry:
%a = alloca i32, align 4
%b = alloca i32, align 4
store i32 0, i32* %a, align 4
store i32 5, i32* %b, align 4
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%call = call i32 @foo(i32 %0, i32 %1)
ret i32 0
}
Then, we use llc to compile this IR code and dump the SelectionDAG nodes of main(). Considering the layout, I split the picture into two parts. First, let's look at function parameters.
As you can see, there is a CopyToReg node which is marked by a red box and it would copy the parameter %m from a virtual register to the physical register O0. On the other hand, the store node which is marked by a blue box would store %n from a virtual register to the stack.
Second, let's look at the return value:
Here, the green box is the SPISD::CALL node and the CopyFromReg node which is marked by an orange box would copy the return value from the physical register O0 to a virtual register.Conclusion
Ok, the above is a brief introduction to the calling convention of LLVM backend. Although <Arch>CallingConv.td and <Arch>ISelLowering.cpp have already handled most of the things, there are still some things not handled in these two files, such as the actual location of the frame index or prologue and epilogue. This would be handled in other hook functions, such as eliminateFrameIndex(), emitPrologue() and emitEpilogue(). Maybe we will discuss this in another article~~
沒有留言:
張貼留言