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