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