1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "aarch64_fixshortbranch.h"
17
18 namespace maplebe {
CalculateAlignRange(const BB & bb,uint32 addr) const19 uint32 AArch64FixShortBranch::CalculateAlignRange(const BB &bb, uint32 addr) const
20 {
21 if (addr == 0) {
22 return addr;
23 }
24 uint32 alignPower = bb.GetAlignPower();
25 /*
26 * The algorithm can avoid the problem that alignment causes conditional branch out of range in two stages.
27 * 1. asm: .mpl -> .s
28 * The pseudo-instruction [.p2align 5] is 12B.
29 * kAlignPseudoSize = 12 / 4 = 3
30 * 2. link: .s -> .o
31 * The pseudo-instruction will be expanded to nop.
32 * eg. .p2align 5
33 * alignPower = 5, alignValue = 2^5 = 32
34 * range = (32 - ((addr - 1) * 4) % 32) / 4 - 1
35 *
36 * =======> max[range, kAlignPseudoSize]
37 */
38 uint32 range = ((1U << alignPower) - (((addr - 1) * kInsnSize) & ((1U << alignPower) - 1))) / kInsnSize - 1;
39 return range > kAlignPseudoSize ? range : kAlignPseudoSize;
40 }
41
SetInsnId() const42 void AArch64FixShortBranch::SetInsnId() const
43 {
44 uint32 i = 0;
45 AArch64CGFunc *aarch64CGFunc = static_cast<AArch64CGFunc *>(cgFunc);
46 FOR_ALL_BB(bb, aarch64CGFunc)
47 {
48 FOR_BB_INSNS(insn, bb)
49 {
50 if (!insn->IsMachineInstruction()) {
51 continue;
52 }
53 i += insn->GetAtomicNum();
54 insn->SetId(i);
55 if (insn->GetMachineOpcode() == MOP_adrp_ldr && CGOptions::IsLazyBinding() &&
56 !cgFunc->GetCG()->IsLibcore()) {
57 /* For 1 additional EmitLazyBindingRoutine in lazybinding
58 * see function AArch64Insn::Emit in file aarch64_insn.cpp
59 */
60 ++i;
61 }
62 }
63 }
64 }
65
CalculateIfBBNum() const66 uint32 AArch64FixShortBranch::CalculateIfBBNum() const
67 {
68 uint32 ifBBCount = 0;
69 FOR_ALL_BB(bb, cgFunc)
70 {
71 if (bb->GetKind() != BB::kBBIf) {
72 ifBBCount++;
73 }
74 }
75 return ifBBCount;
76 }
77
PatchLongBranch()78 void AArch64FixShortBranch::PatchLongBranch()
79 {
80 AArch64CGFunc *aarch64CGFunc = static_cast<AArch64CGFunc *>(cgFunc);
81 SetInsnId();
82 uint32 ifBBCount = CalculateIfBBNum();
83 for (BB *bb = aarch64CGFunc->GetFirstBB(); bb != nullptr; bb = bb->GetNext()) {
84 if (bb->GetKind() != BB::kBBIf) {
85 continue;
86 }
87 Insn *insn = bb->GetLastMachineInsn();
88 while (insn != nullptr && insn->IsImmaterialInsn()) {
89 insn = insn->GetPrev();
90 }
91 if (insn == nullptr || !insn->IsCondBranch()) {
92 continue;
93 }
94 LabelIdx tbbLabelIdx = aarch64CGFunc->GetLabelInInsn(*insn);
95 // when we change condbr to condbr and b, we will have more insns
96 // in case the insn num will cause distance check not calculating right
97 // we assume that each if bb will be changed(which is the worst case).
98 if (ifBBCount <= AArch64Abi::kMaxInstrForCondBr &&
99 aarch64CGFunc->DistanceCheck(*bb, tbbLabelIdx, insn->GetId(), AArch64Abi::kMaxInstrForCondBr - ifBBCount)) {
100 continue;
101 }
102 aarch64CGFunc->InsertJumpPad(insn);
103 }
104 }
105 /*
106 * TBZ/TBNZ instruction is generated under -O2, these branch instructions only have a range of +/-32KB.
107 * If the branch target is not reachable, we split tbz/tbnz into combination of ubfx and cbz/cbnz, which
108 * will clobber one extra register. With LSRA under -O2, we can use one of the reserved registers R16 for
109 * that purpose. To save compile time, we do this change when there are more than 32KB / 4 instructions
110 * in the function.
111 */
FixShortBranches() const112 void AArch64FixShortBranch::FixShortBranches() const
113 {
114 AArch64CGFunc *aarch64CGFunc = static_cast<AArch64CGFunc *>(cgFunc);
115 bool change = false;
116 do {
117 change = false;
118 SetInsnId();
119 for (auto *bb = aarch64CGFunc->GetFirstBB(); bb != nullptr && !change; bb = bb->GetNext()) {
120 /* Do a backward scan searching for short branches */
121 for (auto *insn = bb->GetLastInsn(); insn != nullptr && !change; insn = insn->GetPrev()) {
122 if (!insn->IsMachineInstruction()) {
123 continue;
124 }
125 MOperator thisMop = insn->GetMachineOpcode();
126 if (thisMop != MOP_wtbz && thisMop != MOP_wtbnz && thisMop != MOP_xtbz && thisMop != MOP_xtbnz) {
127 continue;
128 }
129 LabelOperand &label = static_cast<LabelOperand &>(insn->GetOperand(kInsnThirdOpnd));
130 /* should not be commented out after bug fix */
131 if (aarch64CGFunc->DistanceCheck(*bb, label.GetLabelIndex(), insn->GetId(),
132 AArch64Abi::kMaxInstrForTbnz)) {
133 continue;
134 }
135 auto ® = static_cast<RegOperand &>(insn->GetOperand(kInsnFirstOpnd));
136 ImmOperand &bitSize = aarch64CGFunc->CreateImmOperand(1, k8BitSize, false);
137 auto &bitPos = static_cast<ImmOperand &>(insn->GetOperand(kInsnSecondOpnd));
138 MOperator ubfxOp = MOP_undef;
139 MOperator cbOp = MOP_undef;
140 switch (thisMop) {
141 case MOP_wtbz:
142 ubfxOp = MOP_wubfxrri5i5;
143 cbOp = MOP_wcbz;
144 break;
145 case MOP_wtbnz:
146 ubfxOp = MOP_wubfxrri5i5;
147 cbOp = MOP_wcbnz;
148 break;
149 case MOP_xtbz:
150 ubfxOp = MOP_xubfxrri6i6;
151 cbOp = MOP_xcbz;
152 break;
153 case MOP_xtbnz:
154 ubfxOp = MOP_xubfxrri6i6;
155 cbOp = MOP_xcbnz;
156 break;
157 default:
158 CHECK_FATAL_FALSE("must be");
159 break;
160 }
161 RegOperand &tmp = aarch64CGFunc->GetOrCreatePhysicalRegisterOperand(
162 R16, (ubfxOp == MOP_wubfxrri5i5) ? k32BitSize : k64BitSize, kRegTyInt);
163 (void)bb->InsertInsnAfter(*insn, cgFunc->GetInsnBuilder()->BuildInsn(cbOp, tmp, label));
164 (void)bb->InsertInsnAfter(*insn,
165 cgFunc->GetInsnBuilder()->BuildInsn(ubfxOp, tmp, reg, bitPos, bitSize));
166 bb->RemoveInsn(*insn);
167 change = true;
168 }
169 }
170 } while (change);
171 }
172
CheckFunctionSize(uint32 maxSize) const173 bool AArch64FixShortBranch::CheckFunctionSize(uint32 maxSize) const
174 {
175 uint32 firstInsnId = 0;
176 uint32 lastInsnId = UINT32_MAX;
177 bool findLast = false;
178 bool findFirst = false;
179
180 for (auto *bb = cgFunc->GetLastBB(); bb != nullptr && !findLast; bb = bb->GetPrev()) {
181 for (auto *insn = bb->GetLastInsn(); insn != nullptr && !findLast; insn = insn->GetPrev()) {
182 if (!insn->IsMachineInstruction() || insn->IsImmaterialInsn()) {
183 continue;
184 }
185 findLast = true;
186 lastInsnId = insn->GetId();
187 break;
188 }
189 }
190
191 for (auto *bb = cgFunc->GetFirstBB(); bb != nullptr && !findFirst; bb = bb->GetNext()) {
192 for (auto *insn = bb->GetFirstInsn(); insn != nullptr && !findFirst; insn = insn->GetNext()) {
193 if (!insn->IsMachineInstruction() || insn->IsImmaterialInsn()) {
194 continue;
195 }
196 findFirst = true;
197 firstInsnId = insn->GetId();
198 break;
199 }
200 }
201 return (lastInsnId - firstInsnId + 1) <= maxSize;
202 }
203
204 // when func size >= kMaxInstrForLdr
205 // ldr R1, .L.4__5
206 // .L_x: ...
207 // =>
208 // adrp x1, .L.4__5
209 // add x1, x1, :lo12:.L.4__5
210 // ldr x1, [x1]
FixLdr()211 void AArch64FixShortBranch::FixLdr()
212 {
213 AArch64CGFunc *aarch64CGFunc = static_cast<AArch64CGFunc *>(cgFunc);
214 SetInsnId();
215 if (CheckFunctionSize(AArch64Abi::kMaxInstrForLdr)) {
216 return;
217 }
218 FOR_ALL_BB(bb, cgFunc)
219 {
220 FOR_BB_INSNS(insn, bb)
221 {
222 if (!insn->IsMachineInstruction()) {
223 continue;
224 }
225 if (insn->GetMachineOpcode() == MOP_xldli && insn->GetOperand(kInsnSecondOpnd).IsLabelOpnd()) {
226 // ldr -> adrp + add
227 auto ®Opnd = static_cast<RegOperand &>(insn->GetOperand(kInsnFirstOpnd));
228 auto &labelOpnd = static_cast<LabelOperand &>(insn->GetOperand(kInsnSecondOpnd));
229 Operand &immOpnd = aarch64CGFunc->CreateImmOperand(labelOpnd.GetLabelIndex(), k64BitSize, false);
230 insn->SetOperand(kInsnSecondOpnd, immOpnd);
231 insn->SetMOP(AArch64CG::kMd[MOP_adrp_label]);
232 // ldr x1, [x1]
233 MemOperand *newDest = aarch64CGFunc->CreateMemOperand(
234 k64BitSize, regOpnd, aarch64CGFunc->CreateImmOperand(0, k32BitSize, false), false);
235 auto *newRegOpnd = static_cast<RegOperand *>(regOpnd.Clone(*aarch64CGFunc->GetMemoryPool()));
236 Insn &ldrInsn = aarch64CGFunc->GetInsnBuilder()->BuildInsn(MOP_xldr, *newRegOpnd, *newDest);
237 (void)bb->InsertInsnAfter(*insn, ldrInsn);
238 }
239 }
240 }
241 }
242
PhaseRun(maplebe::CGFunc & f)243 bool CgFixShortBranch::PhaseRun(maplebe::CGFunc &f)
244 {
245 auto *fixShortBranch = GetPhaseAllocator()->New<AArch64FixShortBranch>(&f);
246 CHECK_FATAL(fixShortBranch != nullptr, "AArch64FixShortBranch instance create failure");
247 fixShortBranch->FixShortBranches();
248
249 // fix ldr would cause insn num increasing, do ldr fix first.
250 fixShortBranch->FixLdr();
251 fixShortBranch->PatchLongBranch();
252 return false;
253 }
254 MAPLE_TRANSFORM_PHASE_REGISTER(CgFixShortBranch, fixshortbranch)
255 } /* namespace maplebe */
256