llvm.org GIT mirror llvm / 40bcb88
[COFF, ARM64] Fix ABI implementation of struct returns Summary: Refer the ABI doc at: https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=vs-2019#return-values Related clang patch: D60349 Reviewers: rnk, efriedma, TomTan, ssijaric Reviewed By: rnk, efriedma Subscribers: mstorsjo, javed.absar, kristof.beyls, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D60348 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@359934 91177308-0d34-0410-b5e6-96231b3b80d8 Mandeep Singh Grang 1 year, 5 months ago
7 changed file(s) with 195 addition(s) and 2 deletion(s). Raw diff Collapse all Expand all
8989 /// Return true if this argument has the sret attribute.
9090 bool hasStructRetAttr() const;
9191
92 /// Return true if this argument has the inreg attribute.
93 bool hasInRegAttr() const;
94
9295 /// Return true if this argument has the returned attribute.
9396 bool hasReturnedAttr() const;
9497
142142 bool Argument::hasStructRetAttr() const {
143143 if (!getType()->isPointerTy()) return false;
144144 return hasAttribute(Attribute::StructRet);
145 }
146
147 bool Argument::hasInRegAttr() const {
148 return hasAttribute(Attribute::InReg);
145149 }
146150
147151 bool Argument::hasReturnedAttr() const {
3333 CCIfBigEndian
3434 CCBitConvertToType>>,
3535
36 // An SRet is passed in X8, not X0 like a normal pointer parameter.
36 // In AAPCS, an SRet is passed in X8, not X0 like a normal pointer parameter.
37 // However, on windows, in some circumstances, the SRet is passed in X0 or X1
38 // instead. The presence of the inreg attribute indicates that SRet is
39 // passed in the alternative register (X0 or X1), not X8:
40 // - X0 for non-instance methods.
41 // - X1 for instance methods.
42
43 // The "sret" attribute identifies indirect returns.
44 // The "inreg" attribute identifies non-aggregate types.
45 // The position of the "sret" attribute identifies instance/non-instance
46 // methods.
47 // "sret" on argument 0 means non-instance methods.
48 // "sret" on argument 1 means instance methods.
49
50 CCIfInReg
51 CCIfSRet>>>>,
52
3753 CCIfSRet>>,
3854
3955 // Put ByVal arguments directly on the stack. Minimum size and alignment of a
32073207 }
32083208 }
32093209
3210 // On Windows, InReg pointers must be returned, so record the pointer in a
3211 // virtual register at the start of the function so it can be returned in the
3212 // epilogue.
3213 if (IsWin64) {
3214 for (unsigned I = 0, E = Ins.size(); I != E; ++I) {
3215 if (Ins[I].Flags.isInReg()) {
3216 assert(!FuncInfo->getSRetReturnReg());
3217
3218 MVT PtrTy = getPointerTy(DAG.getDataLayout());
3219 unsigned Reg =
3220 MF.getRegInfo().createVirtualRegister(getRegClassFor(PtrTy));
3221 FuncInfo->setSRetReturnReg(Reg);
3222
3223 SDValue Copy = DAG.getCopyToReg(DAG.getEntryNode(), DL, Reg, InVals[I]);
3224 Chain = DAG.getNode(ISD::TokenFactor, DL, MVT::Other, Copy, Chain);
3225 break;
3226 }
3227 }
3228 }
3229
32103230 unsigned StackArgSize = CCInfo.getNextStackOffset();
32113231 bool TailCallOpt = MF.getTarget().Options.GuaranteedTailCallOpt;
32123232 if (DoesCalleeRestoreStack(CallConv, TailCallOpt)) {
34023422 // X86) but less efficient and uglier in LowerCall.
34033423 for (Function::const_arg_iterator i = CallerF.arg_begin(),
34043424 e = CallerF.arg_end();
3405 i != e; ++i)
3425 i != e; ++i) {
34063426 if (i->hasByValAttr())
34073427 return false;
3428
3429 // On Windows, "inreg" attributes signify non-aggregate indirect returns.
3430 // In this case, it is necessary to save/restore X0 in the callee. Tail
3431 // call opt interferes with this. So we disable tail call opt when the
3432 // caller has an argument with "inreg" attribute.
3433
3434 // FIXME: Check whether the callee also has an "inreg" argument.
3435 if (i->hasInRegAttr())
3436 return false;
3437 }
34083438
34093439 if (getTargetMachine().Options.GuaranteedTailCallOpt)
34103440 return canGuaranteeTCO(CalleeCC) && CCMatch;
39233953 const SmallVectorImpl &Outs,
39243954 const SmallVectorImpl &OutVals,
39253955 const SDLoc &DL, SelectionDAG &DAG) const {
3956 auto &MF = DAG.getMachineFunction();
3957 auto *FuncInfo = MF.getInfo();
3958
39263959 CCAssignFn *RetCC = CallConv == CallingConv::WebKit_JS
39273960 ? RetCC_AArch64_WebKit_JS
39283961 : RetCC_AArch64_AAPCS;
39613994 Flag = Chain.getValue(1);
39623995 RetOps.push_back(DAG.getRegister(VA.getLocReg(), VA.getLocVT()));
39633996 }
3997
3998 // Windows AArch64 ABIs require that for returning structs by value we copy
3999 // the sret argument into X0 for the return.
4000 // We saved the argument into a virtual register in the entry block,
4001 // so now we copy the value out and into X0.
4002 if (unsigned SRetReg = FuncInfo->getSRetReturnReg()) {
4003 SDValue Val = DAG.getCopyFromReg(RetOps[0], DL, SRetReg,
4004 getPointerTy(MF.getDataLayout()));
4005
4006 unsigned RetValReg = AArch64::X0;
4007 Chain = DAG.getCopyToReg(Chain, DL, RetValReg, Val, Flag);
4008 Flag = Chain.getValue(1);
4009
4010 RetOps.push_back(
4011 DAG.getRegister(RetValReg, getPointerTy(DAG.getDataLayout())));
4012 }
4013
39644014 const AArch64RegisterInfo *TRI = Subtarget->getRegisterInfo();
39654015 const MCPhysReg *I =
39664016 TRI->getCalleeSavedRegsViaCopy(&DAG.getMachineFunction());
9090 /// other stack allocations.
9191 bool CalleeSaveStackHasFreeSpace = false;
9292
93 /// SRetReturnReg - sret lowering includes returning the value of the
94 /// returned struct in a register. This field holds the virtual register into
95 /// which the sret argument is passed.
96 unsigned SRetReturnReg = 0;
97
9398 /// Has a value when it is known whether or not the function uses a
9499 /// redzone, and no value otherwise.
95100 /// Initialized during frame lowering, unless the function has the noredzone
164169 unsigned getVarArgsFPRSize() const { return VarArgsFPRSize; }
165170 void setVarArgsFPRSize(unsigned Size) { VarArgsFPRSize = Size; }
166171
172 unsigned getSRetReturnReg() const { return SRetReturnReg; }
173 void setSRetReturnReg(unsigned Reg) { SRetReturnReg = Reg; }
174
167175 unsigned getJumpTableEntrySize(int Idx) const {
168176 auto It = JumpTableEntryInfo.find(Idx);
169177 if (It != JumpTableEntryInfo.end())
0 ; FIXME: Add tests for global-isel/fast-isel.
1
2 ; RUN: llc < %s -mtriple=arm64-windows | FileCheck %s
3
4 ; Returns <= 8 bytes should be in X0.
5 %struct.S1 = type { i32, i32 }
6 define dso_local i64 @"?f1"() {
7 entry:
8 ; CHECK-LABEL: f1
9 ; CHECK: str xzr, [sp, #8]
10 ; CHECK: mov x0, xzr
11
12 %retval = alloca %struct.S1, align 4
13 %a = getelementptr inbounds %struct.S1, %struct.S1* %retval, i32 0, i32 0
14 store i32 0, i32* %a, align 4
15 %b = getelementptr inbounds %struct.S1, %struct.S1* %retval, i32 0, i32 1
16 store i32 0, i32* %b, align 4
17 %0 = bitcast %struct.S1* %retval to i64*
18 %1 = load i64, i64* %0, align 4
19 ret i64 %1
20 }
21
22 ; Returns <= 16 bytes should be in X0/X1.
23 %struct.S2 = type { i32, i32, i32, i32 }
24 define dso_local [2 x i64] @"?f2"() {
25 entry:
26 ; CHECK-LABEL: f2
27 ; CHECK: stp xzr, xzr, [sp], #16
28 ; CHECK: mov x0, xzr
29 ; CHECK: mov x1, xzr
30
31 %retval = alloca %struct.S2, align 4
32 %a = getelementptr inbounds %struct.S2, %struct.S2* %retval, i32 0, i32 0
33 store i32 0, i32* %a, align 4
34 %b = getelementptr inbounds %struct.S2, %struct.S2* %retval, i32 0, i32 1
35 store i32 0, i32* %b, align 4
36 %c = getelementptr inbounds %struct.S2, %struct.S2* %retval, i32 0, i32 2
37 store i32 0, i32* %c, align 4
38 %d = getelementptr inbounds %struct.S2, %struct.S2* %retval, i32 0, i32 3
39 store i32 0, i32* %d, align 4
40 %0 = bitcast %struct.S2* %retval to [2 x i64]*
41 %1 = load [2 x i64], [2 x i64]* %0, align 4
42 ret [2 x i64] %1
43 }
44
45 ; Arguments > 16 bytes should be passed in X8.
46 %struct.S3 = type { i32, i32, i32, i32, i32 }
47 define dso_local void @"?f3"(%struct.S3* noalias sret %agg.result) {
48 entry:
49 ; CHECK-LABEL: f3
50 ; CHECK: stp xzr, xzr, [x8]
51 ; CHECK: str wzr, [x8, #16]
52
53 %a = getelementptr inbounds %struct.S3, %struct.S3* %agg.result, i32 0, i32 0
54 store i32 0, i32* %a, align 4
55 %b = getelementptr inbounds %struct.S3, %struct.S3* %agg.result, i32 0, i32 1
56 store i32 0, i32* %b, align 4
57 %c = getelementptr inbounds %struct.S3, %struct.S3* %agg.result, i32 0, i32 2
58 store i32 0, i32* %c, align 4
59 %d = getelementptr inbounds %struct.S3, %struct.S3* %agg.result, i32 0, i32 3
60 store i32 0, i32* %d, align 4
61 %e = getelementptr inbounds %struct.S3, %struct.S3* %agg.result, i32 0, i32 4
62 store i32 0, i32* %e, align 4
63 ret void
64 }
65
66 ; InReg arguments to non-instance methods must be passed in X0 and returns in
67 ; X0.
68 %class.B = type { i32 }
69 define dso_local void @"?f4"(%class.B* inreg noalias nocapture sret %agg.result) {
70 entry:
71 ; CHECK-LABEL: f4
72 ; CHECK: mov w8, #1
73 ; CHECK: str w8, [x0]
74 %X.i = getelementptr inbounds %class.B, %class.B* %agg.result, i64 0, i32 0
75 store i32 1, i32* %X.i, align 4
76 ret void
77 }
78
79 ; InReg arguments to instance methods must be passed in X1 and returns in X0.
80 %class.C = type { i8 }
81 %class.A = type { i8 }
82
83 define dso_local void @"?inst@C"(%class.C* %this, %class.A* inreg noalias sret %agg.result) {
84 entry:
85 ; CHECK-LABEL: inst@C
86 ; CHECK: str x0, [sp, #8]
87 ; CHECK: mov x0, x1
88
89 %this.addr = alloca %class.C*, align 8
90 store %class.C* %this, %class.C** %this.addr, align 8
91 %this1 = load %class.C*, %class.C** %this.addr, align 8
92 ret void
93 }
0 ; FIXME: Add tests for global-isel/fast-isel.
1
2 ; RUN: llc < %s -mtriple=arm64-windows | FileCheck %s
3
4 %class.C = type { [1 x i32] }
5
6 define dso_local void @"?bar"(%class.C* inreg noalias sret %agg.result) {
7 entry:
8 ; CHECK-LABEL: bar
9 ; CHECK: mov x19, x0
10 ; CHECK: bl "?foo"
11 ; CHECK: mov x0, x19
12
13 tail call void @"?foo"(%class.C* dereferenceable(4) %agg.result)
14 ret void
15 }
16
17 declare dso_local void @"?foo"(%class.C* dereferenceable(4))