1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <iostream>
18 #include <type_traits>
19
20 #include "assembler_arm_vixl.h"
21 #include "base/bit_utils.h"
22 #include "base/bit_utils_iterator.h"
23 #include "entrypoints/quick/quick_entrypoints.h"
24 #include "heap_poisoning.h"
25 #include "thread.h"
26
27 using namespace vixl::aarch32; // NOLINT(build/namespaces)
28
29 using vixl::ExactAssemblyScope;
30 using vixl::CodeBufferCheckScope;
31
32 namespace art {
33 namespace arm {
34
35 #ifdef ___
36 #error "ARM Assembler macro already defined."
37 #else
38 #define ___ vixl_masm_.
39 #endif
40
41 // Thread register definition.
42 extern const vixl32::Register tr(TR);
43 // Marking register definition.
44 extern const vixl32::Register mr(MR);
45
FinalizeCode()46 void ArmVIXLAssembler::FinalizeCode() {
47 vixl_masm_.FinalizeCode();
48 }
49
CodeSize() const50 size_t ArmVIXLAssembler::CodeSize() const {
51 return vixl_masm_.GetSizeOfCodeGenerated();
52 }
53
CodeBufferBaseAddress() const54 const uint8_t* ArmVIXLAssembler::CodeBufferBaseAddress() const {
55 return vixl_masm_.GetBuffer().GetStartAddress<const uint8_t*>();
56 }
57
FinalizeInstructions(const MemoryRegion & region)58 void ArmVIXLAssembler::FinalizeInstructions(const MemoryRegion& region) {
59 // Copy the instructions from the buffer.
60 MemoryRegion from(vixl_masm_.GetBuffer()->GetStartAddress<void*>(), CodeSize());
61 region.CopyFrom(0, from);
62 }
63
PoisonHeapReference(vixl::aarch32::Register reg)64 void ArmVIXLAssembler::PoisonHeapReference(vixl::aarch32::Register reg) {
65 // reg = -reg.
66 ___ Rsb(reg, reg, 0);
67 }
68
UnpoisonHeapReference(vixl::aarch32::Register reg)69 void ArmVIXLAssembler::UnpoisonHeapReference(vixl::aarch32::Register reg) {
70 // reg = -reg.
71 ___ Rsb(reg, reg, 0);
72 }
73
MaybePoisonHeapReference(vixl32::Register reg)74 void ArmVIXLAssembler::MaybePoisonHeapReference(vixl32::Register reg) {
75 if (kPoisonHeapReferences) {
76 PoisonHeapReference(reg);
77 }
78 }
79
MaybeUnpoisonHeapReference(vixl32::Register reg)80 void ArmVIXLAssembler::MaybeUnpoisonHeapReference(vixl32::Register reg) {
81 if (kPoisonHeapReferences) {
82 UnpoisonHeapReference(reg);
83 }
84 }
85
GenerateMarkingRegisterCheck(vixl32::Register temp,int code)86 void ArmVIXLAssembler::GenerateMarkingRegisterCheck(vixl32::Register temp, int code) {
87 // The Marking Register is only used in the Baker read barrier configuration.
88 DCHECK(kEmitCompilerReadBarrier);
89 DCHECK(kUseBakerReadBarrier);
90
91 vixl32::Label mr_is_ok;
92
93 // temp = self.tls32_.is.gc_marking
94 ___ Ldr(temp, MemOperand(tr, Thread::IsGcMarkingOffset<kArmPointerSize>().Int32Value()));
95 // Check that mr == self.tls32_.is.gc_marking.
96 ___ Cmp(mr, temp);
97 ___ B(eq, &mr_is_ok, /* far_target */ false);
98 ___ Bkpt(code);
99 ___ Bind(&mr_is_ok);
100 }
101
LoadImmediate(vixl32::Register rd,int32_t value)102 void ArmVIXLAssembler::LoadImmediate(vixl32::Register rd, int32_t value) {
103 // TODO(VIXL): Implement this optimization in VIXL.
104 if (!ShifterOperandCanAlwaysHold(value) && ShifterOperandCanAlwaysHold(~value)) {
105 ___ Mvn(rd, ~value);
106 } else {
107 ___ Mov(rd, value);
108 }
109 }
110
ShifterOperandCanAlwaysHold(uint32_t immediate)111 bool ArmVIXLAssembler::ShifterOperandCanAlwaysHold(uint32_t immediate) {
112 return vixl_masm_.IsModifiedImmediate(immediate);
113 }
114
ShifterOperandCanHold(Opcode opcode,uint32_t immediate,vixl::aarch32::FlagsUpdate update_flags)115 bool ArmVIXLAssembler::ShifterOperandCanHold(Opcode opcode,
116 uint32_t immediate,
117 vixl::aarch32::FlagsUpdate update_flags) {
118 switch (opcode) {
119 case ADD:
120 case SUB:
121 // Less than (or equal to) 12 bits can be done if we don't need to set condition codes.
122 if (IsUint<12>(immediate) && update_flags != vixl::aarch32::SetFlags) {
123 return true;
124 }
125 return ShifterOperandCanAlwaysHold(immediate);
126
127 case MOV:
128 // TODO: Support less than or equal to 12bits.
129 return ShifterOperandCanAlwaysHold(immediate);
130
131 case MVN:
132 default:
133 return ShifterOperandCanAlwaysHold(immediate);
134 }
135 }
136
CanSplitLoadStoreOffset(int32_t allowed_offset_bits,int32_t offset,int32_t * add_to_base,int32_t * offset_for_load_store)137 bool ArmVIXLAssembler::CanSplitLoadStoreOffset(int32_t allowed_offset_bits,
138 int32_t offset,
139 /*out*/ int32_t* add_to_base,
140 /*out*/ int32_t* offset_for_load_store) {
141 int32_t other_bits = offset & ~allowed_offset_bits;
142 if (ShifterOperandCanAlwaysHold(other_bits) || ShifterOperandCanAlwaysHold(-other_bits)) {
143 *add_to_base = offset & ~allowed_offset_bits;
144 *offset_for_load_store = offset & allowed_offset_bits;
145 return true;
146 }
147 return false;
148 }
149
AdjustLoadStoreOffset(int32_t allowed_offset_bits,vixl32::Register temp,vixl32::Register base,int32_t offset)150 int32_t ArmVIXLAssembler::AdjustLoadStoreOffset(int32_t allowed_offset_bits,
151 vixl32::Register temp,
152 vixl32::Register base,
153 int32_t offset) {
154 DCHECK_NE(offset & ~allowed_offset_bits, 0);
155 int32_t add_to_base, offset_for_load;
156 if (CanSplitLoadStoreOffset(allowed_offset_bits, offset, &add_to_base, &offset_for_load)) {
157 ___ Add(temp, base, add_to_base);
158 return offset_for_load;
159 } else {
160 ___ Mov(temp, offset);
161 ___ Add(temp, temp, base);
162 return 0;
163 }
164 }
165
166 // TODO(VIXL): Implement this in VIXL.
GetAllowedLoadOffsetBits(LoadOperandType type)167 int32_t ArmVIXLAssembler::GetAllowedLoadOffsetBits(LoadOperandType type) {
168 switch (type) {
169 case kLoadSignedByte:
170 case kLoadSignedHalfword:
171 case kLoadUnsignedHalfword:
172 case kLoadUnsignedByte:
173 case kLoadWord:
174 // We can encode imm12 offset.
175 return 0xfff;
176 case kLoadSWord:
177 case kLoadDWord:
178 case kLoadWordPair:
179 // We can encode imm8:'00' offset.
180 return 0xff << 2;
181 default:
182 LOG(FATAL) << "UNREACHABLE";
183 UNREACHABLE();
184 }
185 }
186
187 // TODO(VIXL): Implement this in VIXL.
GetAllowedStoreOffsetBits(StoreOperandType type)188 int32_t ArmVIXLAssembler::GetAllowedStoreOffsetBits(StoreOperandType type) {
189 switch (type) {
190 case kStoreHalfword:
191 case kStoreByte:
192 case kStoreWord:
193 // We can encode imm12 offset.
194 return 0xfff;
195 case kStoreSWord:
196 case kStoreDWord:
197 case kStoreWordPair:
198 // We can encode imm8:'00' offset.
199 return 0xff << 2;
200 default:
201 LOG(FATAL) << "UNREACHABLE";
202 UNREACHABLE();
203 }
204 }
205
206 // TODO(VIXL): Implement this in VIXL.
CanHoldLoadOffsetThumb(LoadOperandType type,int offset)207 static bool CanHoldLoadOffsetThumb(LoadOperandType type, int offset) {
208 switch (type) {
209 case kLoadSignedByte:
210 case kLoadSignedHalfword:
211 case kLoadUnsignedHalfword:
212 case kLoadUnsignedByte:
213 case kLoadWord:
214 return IsAbsoluteUint<12>(offset);
215 case kLoadSWord:
216 case kLoadDWord:
217 return IsAbsoluteUint<10>(offset) && IsAligned<4>(offset); // VFP addressing mode.
218 case kLoadWordPair:
219 return IsAbsoluteUint<10>(offset) && IsAligned<4>(offset);
220 default:
221 LOG(FATAL) << "UNREACHABLE";
222 UNREACHABLE();
223 }
224 }
225
226 // TODO(VIXL): Implement this in VIXL.
CanHoldStoreOffsetThumb(StoreOperandType type,int offset)227 static bool CanHoldStoreOffsetThumb(StoreOperandType type, int offset) {
228 switch (type) {
229 case kStoreHalfword:
230 case kStoreByte:
231 case kStoreWord:
232 return IsAbsoluteUint<12>(offset);
233 case kStoreSWord:
234 case kStoreDWord:
235 return IsAbsoluteUint<10>(offset) && IsAligned<4>(offset); // VFP addressing mode.
236 case kStoreWordPair:
237 return IsAbsoluteUint<10>(offset) && IsAligned<4>(offset);
238 default:
239 LOG(FATAL) << "UNREACHABLE";
240 UNREACHABLE();
241 }
242 }
243
244 // Implementation note: this method must emit at most one instruction when
245 // Address::CanHoldStoreOffsetThumb.
246 // TODO(VIXL): Implement AdjustLoadStoreOffset logic in VIXL.
StoreToOffset(StoreOperandType type,vixl32::Register reg,vixl32::Register base,int32_t offset)247 void ArmVIXLAssembler::StoreToOffset(StoreOperandType type,
248 vixl32::Register reg,
249 vixl32::Register base,
250 int32_t offset) {
251 vixl32::Register tmp_reg;
252 UseScratchRegisterScope temps(&vixl_masm_);
253
254 if (!CanHoldStoreOffsetThumb(type, offset)) {
255 CHECK_NE(base.GetCode(), kIpCode);
256 if ((reg.GetCode() != kIpCode) &&
257 (!vixl_masm_.GetScratchRegisterList()->IsEmpty()) &&
258 ((type != kStoreWordPair) || (reg.GetCode() + 1 != kIpCode))) {
259 tmp_reg = temps.Acquire();
260 } else {
261 // Be careful not to use ip twice (for `reg` (or `reg` + 1 in
262 // the case of a word-pair store) and `base`) to build the
263 // Address object used by the store instruction(s) below.
264 // Instead, save R5 on the stack (or R6 if R5 is already used by
265 // `base`), use it as secondary temporary register, and restore
266 // it after the store instruction has been emitted.
267 tmp_reg = (base.GetCode() != 5) ? r5 : r6;
268 ___ Push(tmp_reg);
269 if (base.GetCode() == kSpCode) {
270 offset += kRegisterSize;
271 }
272 }
273 // TODO: Implement indexed store (not available for STRD), inline AdjustLoadStoreOffset()
274 // and in the "unsplittable" path get rid of the "add" by using the store indexed instead.
275 offset = AdjustLoadStoreOffset(GetAllowedStoreOffsetBits(type), tmp_reg, base, offset);
276 base = tmp_reg;
277 }
278 DCHECK(CanHoldStoreOffsetThumb(type, offset));
279 switch (type) {
280 case kStoreByte:
281 ___ Strb(reg, MemOperand(base, offset));
282 break;
283 case kStoreHalfword:
284 ___ Strh(reg, MemOperand(base, offset));
285 break;
286 case kStoreWord:
287 ___ Str(reg, MemOperand(base, offset));
288 break;
289 case kStoreWordPair:
290 ___ Strd(reg, vixl32::Register(reg.GetCode() + 1), MemOperand(base, offset));
291 break;
292 default:
293 LOG(FATAL) << "UNREACHABLE";
294 UNREACHABLE();
295 }
296 if ((tmp_reg.IsValid()) && (tmp_reg.GetCode() != kIpCode)) {
297 CHECK(tmp_reg.Is(r5) || tmp_reg.Is(r6)) << tmp_reg;
298 ___ Pop(tmp_reg);
299 }
300 }
301
302 // Implementation note: this method must emit at most one instruction when
303 // Address::CanHoldLoadOffsetThumb.
304 // TODO(VIXL): Implement AdjustLoadStoreOffset logic in VIXL.
LoadFromOffset(LoadOperandType type,vixl32::Register dest,vixl32::Register base,int32_t offset)305 void ArmVIXLAssembler::LoadFromOffset(LoadOperandType type,
306 vixl32::Register dest,
307 vixl32::Register base,
308 int32_t offset) {
309 if (!CanHoldLoadOffsetThumb(type, offset)) {
310 CHECK(!base.Is(ip));
311 // Inlined AdjustLoadStoreOffset() allows us to pull a few more tricks.
312 int32_t allowed_offset_bits = GetAllowedLoadOffsetBits(type);
313 DCHECK_NE(offset & ~allowed_offset_bits, 0);
314 int32_t add_to_base, offset_for_load;
315 if (CanSplitLoadStoreOffset(allowed_offset_bits, offset, &add_to_base, &offset_for_load)) {
316 // Use reg for the adjusted base. If it's low reg, we may end up using 16-bit load.
317 AddConstant(dest, base, add_to_base);
318 base = dest;
319 offset = offset_for_load;
320 } else {
321 UseScratchRegisterScope temps(&vixl_masm_);
322 vixl32::Register temp = (dest.Is(base)) ? temps.Acquire() : dest;
323 LoadImmediate(temp, offset);
324 // TODO: Implement indexed load (not available for LDRD) and use it here to avoid the ADD.
325 // Use reg for the adjusted base. If it's low reg, we may end up using 16-bit load.
326 ___ Add(dest, dest, (dest.Is(base)) ? temp : base);
327 base = dest;
328 offset = 0;
329 }
330 }
331
332 DCHECK(CanHoldLoadOffsetThumb(type, offset));
333 switch (type) {
334 case kLoadSignedByte:
335 ___ Ldrsb(dest, MemOperand(base, offset));
336 break;
337 case kLoadUnsignedByte:
338 ___ Ldrb(dest, MemOperand(base, offset));
339 break;
340 case kLoadSignedHalfword:
341 ___ Ldrsh(dest, MemOperand(base, offset));
342 break;
343 case kLoadUnsignedHalfword:
344 ___ Ldrh(dest, MemOperand(base, offset));
345 break;
346 case kLoadWord:
347 CHECK(!dest.IsSP());
348 ___ Ldr(dest, MemOperand(base, offset));
349 break;
350 case kLoadWordPair:
351 ___ Ldrd(dest, vixl32::Register(dest.GetCode() + 1), MemOperand(base, offset));
352 break;
353 default:
354 LOG(FATAL) << "UNREACHABLE";
355 UNREACHABLE();
356 }
357 }
358
StoreSToOffset(vixl32::SRegister source,vixl32::Register base,int32_t offset)359 void ArmVIXLAssembler::StoreSToOffset(vixl32::SRegister source,
360 vixl32::Register base,
361 int32_t offset) {
362 ___ Vstr(source, MemOperand(base, offset));
363 }
364
StoreDToOffset(vixl32::DRegister source,vixl32::Register base,int32_t offset)365 void ArmVIXLAssembler::StoreDToOffset(vixl32::DRegister source,
366 vixl32::Register base,
367 int32_t offset) {
368 ___ Vstr(source, MemOperand(base, offset));
369 }
370
LoadSFromOffset(vixl32::SRegister reg,vixl32::Register base,int32_t offset)371 void ArmVIXLAssembler::LoadSFromOffset(vixl32::SRegister reg,
372 vixl32::Register base,
373 int32_t offset) {
374 ___ Vldr(reg, MemOperand(base, offset));
375 }
376
LoadDFromOffset(vixl32::DRegister reg,vixl32::Register base,int32_t offset)377 void ArmVIXLAssembler::LoadDFromOffset(vixl32::DRegister reg,
378 vixl32::Register base,
379 int32_t offset) {
380 ___ Vldr(reg, MemOperand(base, offset));
381 }
382
383 // Prefer Str to Add/Stm in ArmVIXLAssembler::StoreRegisterList and
384 // ArmVIXLAssembler::LoadRegisterList where this generates less code (size).
385 static constexpr int kRegListThreshold = 4;
386
StoreRegisterList(RegList regs,size_t stack_offset)387 void ArmVIXLAssembler::StoreRegisterList(RegList regs, size_t stack_offset) {
388 int number_of_regs = POPCOUNT(static_cast<uint32_t>(regs));
389 if (number_of_regs != 0) {
390 if (number_of_regs > kRegListThreshold) {
391 UseScratchRegisterScope temps(GetVIXLAssembler());
392 vixl32::Register base = sp;
393 if (stack_offset != 0) {
394 base = temps.Acquire();
395 DCHECK_EQ(regs & (1u << base.GetCode()), 0u);
396 ___ Add(base, sp, Operand::From(stack_offset));
397 }
398 ___ Stm(base, NO_WRITE_BACK, RegisterList(regs));
399 } else {
400 for (uint32_t i : LowToHighBits(static_cast<uint32_t>(regs))) {
401 ___ Str(vixl32::Register(i), MemOperand(sp, stack_offset));
402 stack_offset += kRegSizeInBytes;
403 }
404 }
405 }
406 }
407
LoadRegisterList(RegList regs,size_t stack_offset)408 void ArmVIXLAssembler::LoadRegisterList(RegList regs, size_t stack_offset) {
409 int number_of_regs = POPCOUNT(static_cast<uint32_t>(regs));
410 if (number_of_regs != 0) {
411 if (number_of_regs > kRegListThreshold) {
412 UseScratchRegisterScope temps(GetVIXLAssembler());
413 vixl32::Register base = sp;
414 if (stack_offset != 0) {
415 base = temps.Acquire();
416 ___ Add(base, sp, Operand::From(stack_offset));
417 }
418 ___ Ldm(base, NO_WRITE_BACK, RegisterList(regs));
419 } else {
420 for (uint32_t i : LowToHighBits(static_cast<uint32_t>(regs))) {
421 ___ Ldr(vixl32::Register(i), MemOperand(sp, stack_offset));
422 stack_offset += kRegSizeInBytes;
423 }
424 }
425 }
426 }
427
AddConstant(vixl32::Register rd,int32_t value)428 void ArmVIXLAssembler::AddConstant(vixl32::Register rd, int32_t value) {
429 AddConstant(rd, rd, value);
430 }
431
432 // TODO(VIXL): think about using adds which updates flags where possible.
AddConstant(vixl32::Register rd,vixl32::Register rn,int32_t value)433 void ArmVIXLAssembler::AddConstant(vixl32::Register rd,
434 vixl32::Register rn,
435 int32_t value) {
436 DCHECK(vixl_masm_.OutsideITBlock());
437 // TODO(VIXL): implement this optimization in VIXL.
438 if (value == 0) {
439 if (!rd.Is(rn)) {
440 ___ Mov(rd, rn);
441 }
442 return;
443 }
444 ___ Add(rd, rn, value);
445 }
446
447 // Inside IT block we must use assembler, macroassembler instructions are not permitted.
AddConstantInIt(vixl32::Register rd,vixl32::Register rn,int32_t value,vixl32::Condition cond)448 void ArmVIXLAssembler::AddConstantInIt(vixl32::Register rd,
449 vixl32::Register rn,
450 int32_t value,
451 vixl32::Condition cond) {
452 DCHECK(vixl_masm_.InITBlock());
453 if (value == 0) {
454 ___ mov(cond, rd, rn);
455 } else {
456 ___ add(cond, rd, rn, value);
457 }
458 }
459
CompareAndBranchIfZero(vixl32::Register rn,vixl32::Label * label,bool is_far_target)460 void ArmVIXLMacroAssembler::CompareAndBranchIfZero(vixl32::Register rn,
461 vixl32::Label* label,
462 bool is_far_target) {
463 if (!is_far_target && rn.IsLow() && !label->IsBound()) {
464 // In T32, Cbz/Cbnz instructions have following limitations:
465 // - There are only 7 bits (i:imm5:0) to encode branch target address (cannot be far target).
466 // - Only low registers (i.e R0 .. R7) can be encoded.
467 // - Only forward branches (unbound labels) are supported.
468 Cbz(rn, label);
469 return;
470 }
471 Cmp(rn, 0);
472 B(eq, label, is_far_target);
473 }
474
CompareAndBranchIfNonZero(vixl32::Register rn,vixl32::Label * label,bool is_far_target)475 void ArmVIXLMacroAssembler::CompareAndBranchIfNonZero(vixl32::Register rn,
476 vixl32::Label* label,
477 bool is_far_target) {
478 if (!is_far_target && rn.IsLow() && !label->IsBound()) {
479 Cbnz(rn, label);
480 return;
481 }
482 Cmp(rn, 0);
483 B(ne, label, is_far_target);
484 }
485
B(vixl32::Label * label)486 void ArmVIXLMacroAssembler::B(vixl32::Label* label) {
487 if (!label->IsBound()) {
488 // Try to use a 16-bit encoding of the B instruction.
489 DCHECK(OutsideITBlock());
490 BPreferNear(label);
491 return;
492 }
493 MacroAssembler::B(label);
494 }
495
B(vixl32::Condition cond,vixl32::Label * label,bool is_far_target)496 void ArmVIXLMacroAssembler::B(vixl32::Condition cond, vixl32::Label* label, bool is_far_target) {
497 if (!label->IsBound() && !is_far_target) {
498 // Try to use a 16-bit encoding of the B instruction.
499 DCHECK(OutsideITBlock());
500 BPreferNear(cond, label);
501 return;
502 }
503 MacroAssembler::B(cond, label);
504 }
505
506 } // namespace arm
507 } // namespace art
508