/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "codegen_arm64.h" #include "arm64_lir.h" #include "base/logging.h" #include "dex/quick/mir_to_lir-inl.h" #include "dex/reg_storage_eq.h" namespace art { /* This file contains codegen for the A64 ISA. */ int32_t Arm64Mir2Lir::EncodeImmSingle(uint32_t bits) { /* * Valid values will have the form: * * aBbb.bbbc.defg.h000.0000.0000.0000.0000 * * where B = not(b). In other words, if b == 1, then B == 0 and viceversa. */ // bits[19..0] are cleared. if ((bits & 0x0007ffff) != 0) return -1; // bits[29..25] are all set or all cleared. uint32_t b_pattern = (bits >> 16) & 0x3e00; if (b_pattern != 0 && b_pattern != 0x3e00) return -1; // bit[30] and bit[29] are opposite. if (((bits ^ (bits << 1)) & 0x40000000) == 0) return -1; // bits: aBbb.bbbc.defg.h000.0000.0000.0000.0000 // bit7: a000.0000 uint32_t bit7 = ((bits >> 31) & 0x1) << 7; // bit6: 0b00.0000 uint32_t bit6 = ((bits >> 29) & 0x1) << 6; // bit5_to_0: 00cd.efgh uint32_t bit5_to_0 = (bits >> 19) & 0x3f; return (bit7 | bit6 | bit5_to_0); } int32_t Arm64Mir2Lir::EncodeImmDouble(uint64_t bits) { /* * Valid values will have the form: * * aBbb.bbbb.bbcd.efgh.0000.0000.0000.0000 * 0000.0000.0000.0000.0000.0000.0000.0000 * * where B = not(b). */ // bits[47..0] are cleared. if ((bits & UINT64_C(0xffffffffffff)) != 0) return -1; // bits[61..54] are all set or all cleared. uint32_t b_pattern = (bits >> 48) & 0x3fc0; if (b_pattern != 0 && b_pattern != 0x3fc0) return -1; // bit[62] and bit[61] are opposite. if (((bits ^ (bits << 1)) & UINT64_C(0x4000000000000000)) == 0) return -1; // bit7: a000.0000 uint32_t bit7 = ((bits >> 63) & 0x1) << 7; // bit6: 0b00.0000 uint32_t bit6 = ((bits >> 61) & 0x1) << 6; // bit5_to_0: 00cd.efgh uint32_t bit5_to_0 = (bits >> 48) & 0x3f; return (bit7 | bit6 | bit5_to_0); } size_t Arm64Mir2Lir::GetLoadStoreSize(LIR* lir) { bool opcode_is_wide = IS_WIDE(lir->opcode); A64Opcode opcode = UNWIDE(lir->opcode); DCHECK(!IsPseudoLirOp(opcode)); const A64EncodingMap *encoder = GetEncoder(opcode); uint32_t bits = opcode_is_wide ? encoder->xskeleton : encoder->wskeleton; return (bits >> 30); } size_t Arm64Mir2Lir::GetInstructionOffset(LIR* lir) { size_t offset = lir->operands[2]; uint64_t check_flags = GetTargetInstFlags(lir->opcode); DCHECK((check_flags & IS_LOAD) || (check_flags & IS_STORE)); if (check_flags & SCALED_OFFSET_X0) { DCHECK(check_flags & IS_TERTIARY_OP); offset = offset * (1 << GetLoadStoreSize(lir)); } return offset; } LIR* Arm64Mir2Lir::LoadFPConstantValue(RegStorage r_dest, int32_t value) { DCHECK(r_dest.IsSingle()); if (value == 0) { return NewLIR2(kA64Fmov2sw, r_dest.GetReg(), rwzr); } else { int32_t encoded_imm = EncodeImmSingle((uint32_t)value); if (encoded_imm >= 0) { return NewLIR2(kA64Fmov2fI, r_dest.GetReg(), encoded_imm); } } LIR* data_target = ScanLiteralPool(literal_list_, value, 0); if (data_target == nullptr) { // Wide, as we need 8B alignment. data_target = AddWideData(&literal_list_, value, 0); } ScopedMemRefType mem_ref_type(this, ResourceMask::kLiteral); LIR* load_pc_rel = RawLIR(current_dalvik_offset_, kA64Ldr2fp, r_dest.GetReg(), 0, 0, 0, 0, data_target); AppendLIR(load_pc_rel); return load_pc_rel; } LIR* Arm64Mir2Lir::LoadFPConstantValueWide(RegStorage r_dest, int64_t value) { DCHECK(r_dest.IsDouble()); if (value == 0) { return NewLIR2(kA64Fmov2Sx, r_dest.GetReg(), rxzr); } else { int32_t encoded_imm = EncodeImmDouble(value); if (encoded_imm >= 0) { return NewLIR2(WIDE(kA64Fmov2fI), r_dest.GetReg(), encoded_imm); } } // No short form - load from the literal pool. int32_t val_lo = Low32Bits(value); int32_t val_hi = High32Bits(value); LIR* data_target = ScanLiteralPoolWide(literal_list_, val_lo, val_hi); if (data_target == nullptr) { data_target = AddWideData(&literal_list_, val_lo, val_hi); } ScopedMemRefType mem_ref_type(this, ResourceMask::kLiteral); LIR* load_pc_rel = RawLIR(current_dalvik_offset_, WIDE(kA64Ldr2fp), r_dest.GetReg(), 0, 0, 0, 0, data_target); AppendLIR(load_pc_rel); return load_pc_rel; } static int CountLeadingZeros(bool is_wide, uint64_t value) { return (is_wide) ? __builtin_clzll(value) : __builtin_clz((uint32_t)value); } static int CountTrailingZeros(bool is_wide, uint64_t value) { return (is_wide) ? __builtin_ctzll(value) : __builtin_ctz((uint32_t)value); } static int CountSetBits(bool is_wide, uint64_t value) { return ((is_wide) ? __builtin_popcountll(value) : __builtin_popcount((uint32_t)value)); } /** * @brief Try encoding an immediate in the form required by logical instructions. * * @param is_wide Whether @p value is a 64-bit (as opposed to 32-bit) value. * @param value An integer to be encoded. This is interpreted as 64-bit if @p is_wide is true and as * 32-bit if @p is_wide is false. * @return A non-negative integer containing the encoded immediate or -1 if the encoding failed. * @note This is the inverse of Arm64Mir2Lir::DecodeLogicalImmediate(). */ int Arm64Mir2Lir::EncodeLogicalImmediate(bool is_wide, uint64_t value) { unsigned n, imm_s, imm_r; // Logical immediates are encoded using parameters n, imm_s and imm_r using // the following table: // // N imms immr size S R // 1 ssssss rrrrrr 64 UInt(ssssss) UInt(rrrrrr) // 0 0sssss xrrrrr 32 UInt(sssss) UInt(rrrrr) // 0 10ssss xxrrrr 16 UInt(ssss) UInt(rrrr) // 0 110sss xxxrrr 8 UInt(sss) UInt(rrr) // 0 1110ss xxxxrr 4 UInt(ss) UInt(rr) // 0 11110s xxxxxr 2 UInt(s) UInt(r) // (s bits must not be all set) // // A pattern is constructed of size bits, where the least significant S+1 // bits are set. The pattern is rotated right by R, and repeated across a // 32 or 64-bit value, depending on destination register width. // // To test if an arbitary immediate can be encoded using this scheme, an // iterative algorithm is used. // // 1. If the value has all set or all clear bits, it can't be encoded. if (value == 0 || value == ~UINT64_C(0) || (!is_wide && (uint32_t)value == ~UINT32_C(0))) { return -1; } unsigned lead_zero = CountLeadingZeros(is_wide, value); unsigned lead_one = CountLeadingZeros(is_wide, ~value); unsigned trail_zero = CountTrailingZeros(is_wide, value); unsigned trail_one = CountTrailingZeros(is_wide, ~value); unsigned set_bits = CountSetBits(is_wide, value); // The fixed bits in the immediate s field. // If width == 64 (X reg), start at 0xFFFFFF80. // If width == 32 (W reg), start at 0xFFFFFFC0, as the iteration for 64-bit // widths won't be executed. unsigned width = (is_wide) ? 64 : 32; int imm_s_fixed = (is_wide) ? -128 : -64; int imm_s_mask = 0x3f; for (;;) { // 2. If the value is two bits wide, it can be encoded. if (width == 2) { n = 0; imm_s = 0x3C; imm_r = (value & 3) - 1; break; } n = (width == 64) ? 1 : 0; imm_s = ((imm_s_fixed | (set_bits - 1)) & imm_s_mask); if ((lead_zero + set_bits) == width) { imm_r = 0; } else { imm_r = (lead_zero > 0) ? (width - trail_zero) : lead_one; } // 3. If the sum of leading zeros, trailing zeros and set bits is // equal to the bit width of the value, it can be encoded. if (lead_zero + trail_zero + set_bits == width) { break; } // 4. If the sum of leading ones, trailing ones and unset bits in the // value is equal to the bit width of the value, it can be encoded. if (lead_one + trail_one + (width - set_bits) == width) { break; } // 5. If the most-significant half of the bitwise value is equal to // the least-significant half, return to step 2 using the // least-significant half of the value. uint64_t mask = (UINT64_C(1) << (width >> 1)) - 1; if ((value & mask) == ((value >> (width >> 1)) & mask)) { width >>= 1; set_bits >>= 1; imm_s_fixed >>= 1; continue; } // 6. Otherwise, the value can't be encoded. return -1; } return (n << 12 | imm_r << 6 | imm_s); } // Maximum number of instructions to use for encoding the immediate. static const int max_num_ops_per_const_load = 2; /** * @brief Return the number of fast halfwords in the given uint64_t integer. * @details The input integer is split into 4 halfwords (bits 0-15, 16-31, 32-47, 48-63). The * number of fast halfwords (halfwords that are either 0 or 0xffff) is returned. See below for * a more accurate description. * @param value The input 64-bit integer. * @return Return @c retval such that (retval & 0x7) is the maximum between n and m, where n is * the number of halfwords with all bits unset (0) and m is the number of halfwords with all bits * set (0xffff). Additionally (retval & 0x8) is set when m > n. */ static int GetNumFastHalfWords(uint64_t value) { unsigned int num_0000_halfwords = 0; unsigned int num_ffff_halfwords = 0; for (int shift = 0; shift < 64; shift += 16) { uint16_t halfword = static_cast(value >> shift); if (halfword == 0) num_0000_halfwords++; else if (halfword == UINT16_C(0xffff)) num_ffff_halfwords++; } if (num_0000_halfwords >= num_ffff_halfwords) { DCHECK_LE(num_0000_halfwords, 4U); return num_0000_halfwords; } else { DCHECK_LE(num_ffff_halfwords, 4U); return num_ffff_halfwords | 0x8; } } // The InexpensiveConstantXXX variants below are used in the promotion algorithm to determine how a // constant is considered for promotion. If the constant is "inexpensive" then the promotion // algorithm will give it a low priority for promotion, even when it is referenced many times in // the code. bool Arm64Mir2Lir::InexpensiveConstantInt(int32_t value ATTRIBUTE_UNUSED) { // A 32-bit int can always be loaded with 2 instructions (and without using the literal pool). // We therefore return true and give it a low priority for promotion. return true; } bool Arm64Mir2Lir::InexpensiveConstantFloat(int32_t value) { return EncodeImmSingle(value) >= 0; } bool Arm64Mir2Lir::InexpensiveConstantLong(int64_t value) { int num_slow_halfwords = 4 - (GetNumFastHalfWords(value) & 0x7); if (num_slow_halfwords <= max_num_ops_per_const_load) { return true; } return (EncodeLogicalImmediate(/*is_wide=*/true, value) >= 0); } bool Arm64Mir2Lir::InexpensiveConstantDouble(int64_t value) { return EncodeImmDouble(value) >= 0; } // The InexpensiveConstantXXX variants below are used to determine which A64 instructions to use // when one of the operands is an immediate (e.g. register version or immediate version of add). bool Arm64Mir2Lir::InexpensiveConstantInt(int32_t value, Instruction::Code opcode) { switch (opcode) { case Instruction::IF_EQ: case Instruction::IF_NE: case Instruction::IF_LT: case Instruction::IF_GE: case Instruction::IF_GT: case Instruction::IF_LE: case Instruction::ADD_INT: case Instruction::ADD_INT_2ADDR: case Instruction::SUB_INT: case Instruction::SUB_INT_2ADDR: // The code below is consistent with the implementation of OpRegRegImm(). { uint32_t abs_value = (value == INT_MIN) ? value : std::abs(value); if (abs_value < 0x1000) { return true; } else if ((abs_value & UINT64_C(0xfff)) == 0 && ((abs_value >> 12) < 0x1000)) { return true; } return false; } case Instruction::SHL_INT: case Instruction::SHL_INT_2ADDR: case Instruction::SHR_INT: case Instruction::SHR_INT_2ADDR: case Instruction::USHR_INT: case Instruction::USHR_INT_2ADDR: return true; case Instruction::AND_INT: case Instruction::AND_INT_2ADDR: case Instruction::AND_INT_LIT16: case Instruction::AND_INT_LIT8: case Instruction::OR_INT: case Instruction::OR_INT_2ADDR: case Instruction::OR_INT_LIT16: case Instruction::OR_INT_LIT8: case Instruction::XOR_INT: case Instruction::XOR_INT_2ADDR: case Instruction::XOR_INT_LIT16: case Instruction::XOR_INT_LIT8: if (value == 0 || value == INT32_C(-1)) { return true; } return (EncodeLogicalImmediate(/*is_wide=*/false, value) >= 0); default: return false; } } /* * Load a immediate using one single instruction when possible; otherwise * use a pair of movz and movk instructions. * * No additional register clobbering operation performed. Use this version when * 1) r_dest is freshly returned from AllocTemp or * 2) The codegen is under fixed register usage */ LIR* Arm64Mir2Lir::LoadConstantNoClobber(RegStorage r_dest, int value) { LIR* res; if (r_dest.IsFloat()) { return LoadFPConstantValue(r_dest, value); } if (r_dest.Is64Bit()) { return LoadConstantWide(r_dest, value); } // Loading SP/ZR with an immediate is not supported. DCHECK(!A64_REG_IS_SP(r_dest.GetReg())); DCHECK(!A64_REG_IS_ZR(r_dest.GetReg())); // Compute how many movk, movz instructions are needed to load the value. uint16_t high_bits = High16Bits(value); uint16_t low_bits = Low16Bits(value); bool low_fast = ((uint16_t)(low_bits + 1) <= 1); bool high_fast = ((uint16_t)(high_bits + 1) <= 1); if (LIKELY(low_fast || high_fast)) { // 1 instruction is enough to load the immediate. if (LIKELY(low_bits == high_bits)) { // Value is either 0 or -1: we can just use wzr. A64Opcode opcode = LIKELY(low_bits == 0) ? kA64Mov2rr : kA64Mvn2rr; res = NewLIR2(opcode, r_dest.GetReg(), rwzr); } else { uint16_t uniform_bits, useful_bits; int shift; if (LIKELY(high_fast)) { shift = 0; uniform_bits = high_bits; useful_bits = low_bits; } else { shift = 1; uniform_bits = low_bits; useful_bits = high_bits; } if (UNLIKELY(uniform_bits != 0)) { res = NewLIR3(kA64Movn3rdM, r_dest.GetReg(), ~useful_bits, shift); } else { res = NewLIR3(kA64Movz3rdM, r_dest.GetReg(), useful_bits, shift); } } } else { // movk, movz require 2 instructions. Try detecting logical immediates. int log_imm = EncodeLogicalImmediate(/*is_wide=*/false, value); if (log_imm >= 0) { res = NewLIR3(kA64Orr3Rrl, r_dest.GetReg(), rwzr, log_imm); } else { // Use 2 instructions. res = NewLIR3(kA64Movz3rdM, r_dest.GetReg(), low_bits, 0); NewLIR3(kA64Movk3rdM, r_dest.GetReg(), high_bits, 1); } } return res; } // TODO: clean up the names. LoadConstantWide() should really be LoadConstantNoClobberWide(). LIR* Arm64Mir2Lir::LoadConstantWide(RegStorage r_dest, int64_t value) { if (r_dest.IsFloat()) { return LoadFPConstantValueWide(r_dest, value); } DCHECK(r_dest.Is64Bit()); // Loading SP/ZR with an immediate is not supported. DCHECK(!A64_REG_IS_SP(r_dest.GetReg())); DCHECK(!A64_REG_IS_ZR(r_dest.GetReg())); if (LIKELY(value == INT64_C(0) || value == INT64_C(-1))) { // value is either 0 or -1: we can just use xzr. A64Opcode opcode = LIKELY(value == 0) ? WIDE(kA64Mov2rr) : WIDE(kA64Mvn2rr); return NewLIR2(opcode, r_dest.GetReg(), rxzr); } // At least one in value's halfwords is not 0x0, nor 0xffff: find out how many. uint64_t uvalue = static_cast(value); int num_fast_halfwords = GetNumFastHalfWords(uvalue); int num_slow_halfwords = 4 - (num_fast_halfwords & 0x7); bool more_ffff_halfwords = (num_fast_halfwords & 0x8) != 0; if (num_slow_halfwords > 1) { // A single movz/movn is not enough. Try the logical immediate route. int log_imm = EncodeLogicalImmediate(/*is_wide=*/true, value); if (log_imm >= 0) { return NewLIR3(WIDE(kA64Orr3Rrl), r_dest.GetReg(), rxzr, log_imm); } } if (num_slow_halfwords <= max_num_ops_per_const_load) { // We can encode the number using a movz/movn followed by one or more movk. A64Opcode op; uint16_t background; LIR* res = nullptr; // Decide whether to use a movz or a movn. if (more_ffff_halfwords) { op = WIDE(kA64Movn3rdM); background = 0xffff; } else { op = WIDE(kA64Movz3rdM); background = 0; } // Emit the first instruction (movz, movn). int shift; for (shift = 0; shift < 4; shift++) { uint16_t halfword = static_cast(uvalue >> (shift << 4)); if (halfword != background) { res = NewLIR3(op, r_dest.GetReg(), halfword ^ background, shift); break; } } // Emit the movk instructions. for (shift++; shift < 4; shift++) { uint16_t halfword = static_cast(uvalue >> (shift << 4)); if (halfword != background) { NewLIR3(WIDE(kA64Movk3rdM), r_dest.GetReg(), halfword, shift); } } return res; } // Use the literal pool. int32_t val_lo = Low32Bits(value); int32_t val_hi = High32Bits(value); LIR* data_target = ScanLiteralPoolWide(literal_list_, val_lo, val_hi); if (data_target == nullptr) { data_target = AddWideData(&literal_list_, val_lo, val_hi); } ScopedMemRefType mem_ref_type(this, ResourceMask::kLiteral); LIR *res = RawLIR(current_dalvik_offset_, WIDE(kA64Ldr2rp), r_dest.GetReg(), 0, 0, 0, 0, data_target); AppendLIR(res); return res; } LIR* Arm64Mir2Lir::OpUnconditionalBranch(LIR* target) { LIR* res = NewLIR1(kA64B1t, 0 /* offset to be patched during assembly */); res->target = target; return res; } LIR* Arm64Mir2Lir::OpCondBranch(ConditionCode cc, LIR* target) { LIR* branch = NewLIR2(kA64B2ct, ArmConditionEncoding(cc), 0 /* offset to be patched */); branch->target = target; return branch; } LIR* Arm64Mir2Lir::OpReg(OpKind op, RegStorage r_dest_src) { A64Opcode opcode = kA64Brk1d; switch (op) { case kOpBlx: opcode = kA64Blr1x; break; default: LOG(FATAL) << "Bad opcode " << op; } return NewLIR1(opcode, r_dest_src.GetReg()); } LIR* Arm64Mir2Lir::OpRegRegShift(OpKind op, RegStorage r_dest_src1, RegStorage r_src2, int shift) { A64Opcode wide = (r_dest_src1.Is64Bit()) ? WIDE(0) : UNWIDE(0); CHECK_EQ(r_dest_src1.Is64Bit(), r_src2.Is64Bit()); A64Opcode opcode = kA64Brk1d; switch (op) { case kOpCmn: opcode = kA64Cmn3rro; break; case kOpCmp: opcode = kA64Cmp3rro; break; case kOpMov: opcode = kA64Mov2rr; break; case kOpMvn: opcode = kA64Mvn2rr; break; case kOpNeg: opcode = kA64Neg3rro; break; case kOpTst: opcode = kA64Tst3rro; break; case kOpRev: DCHECK_EQ(shift, 0); // Binary, but rm is encoded twice. return NewLIR2(kA64Rev2rr | wide, r_dest_src1.GetReg(), r_src2.GetReg()); case kOpRevsh: // Binary, but rm is encoded twice. NewLIR2(kA64Rev162rr | wide, r_dest_src1.GetReg(), r_src2.GetReg()); // "sxth r1, r2" is "sbfm r1, r2, #0, #15" return NewLIR4(kA64Sbfm4rrdd | wide, r_dest_src1.GetReg(), r_dest_src1.GetReg(), 0, 15); case kOp2Byte: DCHECK_EQ(shift, ENCODE_NO_SHIFT); // "sbfx r1, r2, #imm1, #imm2" is "sbfm r1, r2, #imm1, #(imm1 + imm2 - 1)". // For now we use sbfm directly. return NewLIR4(kA64Sbfm4rrdd | wide, r_dest_src1.GetReg(), r_src2.GetReg(), 0, 7); case kOp2Short: DCHECK_EQ(shift, ENCODE_NO_SHIFT); // For now we use sbfm rather than its alias, sbfx. return NewLIR4(kA64Sbfm4rrdd | wide, r_dest_src1.GetReg(), r_src2.GetReg(), 0, 15); case kOp2Char: // "ubfx r1, r2, #imm1, #imm2" is "ubfm r1, r2, #imm1, #(imm1 + imm2 - 1)". // For now we use ubfm directly. DCHECK_EQ(shift, ENCODE_NO_SHIFT); return NewLIR4(kA64Ubfm4rrdd | wide, r_dest_src1.GetReg(), r_src2.GetReg(), 0, 15); default: return OpRegRegRegShift(op, r_dest_src1, r_dest_src1, r_src2, shift); } DCHECK(!IsPseudoLirOp(opcode)); if (GetEncoder(opcode)->flags & IS_BINARY_OP) { DCHECK_EQ(shift, ENCODE_NO_SHIFT); return NewLIR2(opcode | wide, r_dest_src1.GetReg(), r_src2.GetReg()); } else if (GetEncoder(opcode)->flags & IS_TERTIARY_OP) { A64EncodingKind kind = GetEncoder(opcode)->field_loc[2].kind; if (kind == kFmtShift) { return NewLIR3(opcode | wide, r_dest_src1.GetReg(), r_src2.GetReg(), shift); } } LOG(FATAL) << "Unexpected encoding operand count"; return nullptr; } LIR* Arm64Mir2Lir::OpRegRegExtend(OpKind op, RegStorage r_dest_src1, RegStorage r_src2, A64RegExtEncodings ext, uint8_t amount) { A64Opcode wide = (r_dest_src1.Is64Bit()) ? WIDE(0) : UNWIDE(0); A64Opcode opcode = kA64Brk1d; switch (op) { case kOpCmn: opcode = kA64Cmn3Rre; break; case kOpCmp: opcode = kA64Cmp3Rre; break; case kOpAdd: // Note: intentional fallthrough case kOpSub: return OpRegRegRegExtend(op, r_dest_src1, r_dest_src1, r_src2, ext, amount); default: LOG(FATAL) << "Bad Opcode: " << opcode; UNREACHABLE(); } DCHECK(!IsPseudoLirOp(opcode)); if (GetEncoder(opcode)->flags & IS_TERTIARY_OP) { A64EncodingKind kind = GetEncoder(opcode)->field_loc[2].kind; if (kind == kFmtExtend) { return NewLIR3(opcode | wide, r_dest_src1.GetReg(), r_src2.GetReg(), EncodeExtend(ext, amount)); } } LOG(FATAL) << "Unexpected encoding operand count"; return nullptr; } LIR* Arm64Mir2Lir::OpRegReg(OpKind op, RegStorage r_dest_src1, RegStorage r_src2) { /* RegReg operations with SP in first parameter need extended register instruction form. * Only CMN, CMP, ADD & SUB instructions are implemented. */ if (r_dest_src1 == rs_sp) { return OpRegRegExtend(op, r_dest_src1, r_src2, kA64Uxtx, 0); } else { return OpRegRegShift(op, r_dest_src1, r_src2, ENCODE_NO_SHIFT); } } LIR* Arm64Mir2Lir::OpMovRegMem(RegStorage r_dest, RegStorage r_base, int offset, MoveType move_type) { UNUSED(r_dest, r_base, offset, move_type); UNIMPLEMENTED(FATAL); UNREACHABLE(); } LIR* Arm64Mir2Lir::OpMovMemReg(RegStorage r_base, int offset, RegStorage r_src, MoveType move_type) { UNUSED(r_base, offset, r_src, move_type); UNIMPLEMENTED(FATAL); return nullptr; } LIR* Arm64Mir2Lir::OpCondRegReg(OpKind op, ConditionCode cc, RegStorage r_dest, RegStorage r_src) { UNUSED(op, cc, r_dest, r_src); LOG(FATAL) << "Unexpected use of OpCondRegReg for Arm64"; UNREACHABLE(); } LIR* Arm64Mir2Lir::OpRegRegRegShift(OpKind op, RegStorage r_dest, RegStorage r_src1, RegStorage r_src2, int shift) { A64Opcode opcode = kA64Brk1d; switch (op) { case kOpAdd: opcode = kA64Add4rrro; break; case kOpSub: opcode = kA64Sub4rrro; break; // case kOpRsub: // opcode = kA64RsubWWW; // break; case kOpAdc: opcode = kA64Adc3rrr; break; case kOpAnd: opcode = kA64And4rrro; break; case kOpXor: opcode = kA64Eor4rrro; break; case kOpMul: opcode = kA64Mul3rrr; break; case kOpDiv: opcode = kA64Sdiv3rrr; break; case kOpOr: opcode = kA64Orr4rrro; break; case kOpSbc: opcode = kA64Sbc3rrr; break; case kOpLsl: opcode = kA64Lsl3rrr; break; case kOpLsr: opcode = kA64Lsr3rrr; break; case kOpAsr: opcode = kA64Asr3rrr; break; case kOpRor: opcode = kA64Ror3rrr; break; default: LOG(FATAL) << "Bad opcode: " << op; break; } // The instructions above belong to two kinds: // - 4-operands instructions, where the last operand is a shift/extend immediate, // - 3-operands instructions with no shift/extend. A64Opcode widened_opcode = r_dest.Is64Bit() ? WIDE(opcode) : opcode; CHECK_EQ(r_dest.Is64Bit(), r_src1.Is64Bit()); CHECK_EQ(r_dest.Is64Bit(), r_src2.Is64Bit()); if (GetEncoder(opcode)->flags & IS_QUAD_OP) { DCHECK(!IsExtendEncoding(shift)); return NewLIR4(widened_opcode, r_dest.GetReg(), r_src1.GetReg(), r_src2.GetReg(), shift); } else { DCHECK(GetEncoder(opcode)->flags & IS_TERTIARY_OP); DCHECK_EQ(shift, ENCODE_NO_SHIFT); return NewLIR3(widened_opcode, r_dest.GetReg(), r_src1.GetReg(), r_src2.GetReg()); } } LIR* Arm64Mir2Lir::OpRegRegRegExtend(OpKind op, RegStorage r_dest, RegStorage r_src1, RegStorage r_src2, A64RegExtEncodings ext, uint8_t amount) { A64Opcode opcode = kA64Brk1d; switch (op) { case kOpAdd: opcode = kA64Add4RRre; break; case kOpSub: opcode = kA64Sub4RRre; break; default: UNIMPLEMENTED(FATAL) << "Unimplemented opcode: " << op; UNREACHABLE(); } A64Opcode widened_opcode = r_dest.Is64Bit() ? WIDE(opcode) : opcode; if (r_dest.Is64Bit()) { CHECK(r_src1.Is64Bit()); // dest determines whether the op is wide or not. Up-convert src2 when necessary. // Note: this is not according to aarch64 specifications, but our encoding. if (!r_src2.Is64Bit()) { r_src2 = As64BitReg(r_src2); } } else { CHECK(!r_src1.Is64Bit()); CHECK(!r_src2.Is64Bit()); } // Sanity checks. // 1) Amount is in the range 0..4 CHECK_LE(amount, 4); return NewLIR4(widened_opcode, r_dest.GetReg(), r_src1.GetReg(), r_src2.GetReg(), EncodeExtend(ext, amount)); } LIR* Arm64Mir2Lir::OpRegRegReg(OpKind op, RegStorage r_dest, RegStorage r_src1, RegStorage r_src2) { return OpRegRegRegShift(op, r_dest, r_src1, r_src2, ENCODE_NO_SHIFT); } LIR* Arm64Mir2Lir::OpRegRegImm(OpKind op, RegStorage r_dest, RegStorage r_src1, int value) { return OpRegRegImm64(op, r_dest, r_src1, static_cast(value)); } LIR* Arm64Mir2Lir::OpRegRegImm64(OpKind op, RegStorage r_dest, RegStorage r_src1, int64_t value) { LIR* res; bool neg = (value < 0); uint64_t abs_value = (neg & !(value == LLONG_MIN)) ? -value : value; A64Opcode opcode = kA64Brk1d; A64Opcode alt_opcode = kA64Brk1d; bool is_logical = false; bool is_wide = r_dest.Is64Bit(); A64Opcode wide = (is_wide) ? WIDE(0) : UNWIDE(0); int info = 0; switch (op) { case kOpLsl: { // "lsl w1, w2, #imm" is an alias of "ubfm w1, w2, #(-imm MOD 32), #(31-imm)" // and "lsl x1, x2, #imm" of "ubfm x1, x2, #(-imm MOD 64), #(63-imm)". // For now, we just use ubfm directly. int max_value = (is_wide) ? 63 : 31; return NewLIR4(kA64Ubfm4rrdd | wide, r_dest.GetReg(), r_src1.GetReg(), (-value) & max_value, max_value - value); } case kOpLsr: return NewLIR3(kA64Lsr3rrd | wide, r_dest.GetReg(), r_src1.GetReg(), value); case kOpAsr: return NewLIR3(kA64Asr3rrd | wide, r_dest.GetReg(), r_src1.GetReg(), value); case kOpRor: // "ror r1, r2, #imm" is an alias of "extr r1, r2, r2, #imm". // For now, we just use extr directly. return NewLIR4(kA64Extr4rrrd | wide, r_dest.GetReg(), r_src1.GetReg(), r_src1.GetReg(), value); case kOpAdd: neg = !neg; FALLTHROUGH_INTENDED; case kOpSub: // Add and sub below read/write sp rather than xzr. if (abs_value < 0x1000) { opcode = (neg) ? kA64Add4RRdT : kA64Sub4RRdT; return NewLIR4(opcode | wide, r_dest.GetReg(), r_src1.GetReg(), abs_value, 0); } else if ((abs_value & UINT64_C(0xfff)) == 0 && ((abs_value >> 12) < 0x1000)) { opcode = (neg) ? kA64Add4RRdT : kA64Sub4RRdT; return NewLIR4(opcode | wide, r_dest.GetReg(), r_src1.GetReg(), abs_value >> 12, 1); } else { alt_opcode = (op == kOpAdd) ? kA64Add4RRre : kA64Sub4RRre; info = EncodeExtend(is_wide ? kA64Uxtx : kA64Uxtw, 0); } break; case kOpAdc: alt_opcode = kA64Adc3rrr; break; case kOpSbc: alt_opcode = kA64Sbc3rrr; break; case kOpOr: is_logical = true; opcode = kA64Orr3Rrl; alt_opcode = kA64Orr4rrro; break; case kOpAnd: is_logical = true; opcode = kA64And3Rrl; alt_opcode = kA64And4rrro; break; case kOpXor: is_logical = true; opcode = kA64Eor3Rrl; alt_opcode = kA64Eor4rrro; break; case kOpMul: // TUNING: power of 2, shift & add alt_opcode = kA64Mul3rrr; break; default: LOG(FATAL) << "Bad opcode: " << op; } if (is_logical) { int log_imm = EncodeLogicalImmediate(is_wide, value); if (log_imm >= 0) { return NewLIR3(opcode | wide, r_dest.GetReg(), r_src1.GetReg(), log_imm); } else { // When the immediate is either 0 or ~0, the logical operation can be trivially reduced // to a - possibly negated - assignment. if (value == 0) { switch (op) { case kOpOr: case kOpXor: // Or/Xor by zero reduces to an assignment. return NewLIR2(kA64Mov2rr | wide, r_dest.GetReg(), r_src1.GetReg()); default: // And by zero reduces to a `mov rdest, xzr'. DCHECK(op == kOpAnd); return NewLIR2(kA64Mov2rr | wide, r_dest.GetReg(), (is_wide) ? rxzr : rwzr); } } else if (value == INT64_C(-1) || (!is_wide && static_cast(value) == ~UINT32_C(0))) { switch (op) { case kOpAnd: // And by -1 reduces to an assignment. return NewLIR2(kA64Mov2rr | wide, r_dest.GetReg(), r_src1.GetReg()); case kOpXor: // Xor by -1 reduces to an `mvn rdest, rsrc'. return NewLIR2(kA64Mvn2rr | wide, r_dest.GetReg(), r_src1.GetReg()); default: // Or by -1 reduces to a `mvn rdest, xzr'. DCHECK(op == kOpOr); return NewLIR2(kA64Mvn2rr | wide, r_dest.GetReg(), (is_wide) ? rxzr : rwzr); } } } } RegStorage r_scratch; if (is_wide) { r_scratch = AllocTempWide(); LoadConstantWide(r_scratch, value); } else { r_scratch = AllocTemp(); LoadConstant(r_scratch, value); } if (GetEncoder(alt_opcode)->flags & IS_QUAD_OP) res = NewLIR4(alt_opcode | wide, r_dest.GetReg(), r_src1.GetReg(), r_scratch.GetReg(), info); else res = NewLIR3(alt_opcode | wide, r_dest.GetReg(), r_src1.GetReg(), r_scratch.GetReg()); FreeTemp(r_scratch); return res; } LIR* Arm64Mir2Lir::OpRegImm(OpKind op, RegStorage r_dest_src1, int value) { return OpRegImm64(op, r_dest_src1, static_cast(value)); } LIR* Arm64Mir2Lir::OpRegImm64(OpKind op, RegStorage r_dest_src1, int64_t value) { A64Opcode wide = (r_dest_src1.Is64Bit()) ? WIDE(0) : UNWIDE(0); A64Opcode opcode = kA64Brk1d; A64Opcode neg_opcode = kA64Brk1d; bool shift; bool neg = (value < 0); uint64_t abs_value = (neg & !(value == LLONG_MIN)) ? -value : value; if (LIKELY(abs_value < 0x1000)) { // abs_value is a 12-bit immediate. shift = false; } else if ((abs_value & UINT64_C(0xfff)) == 0 && ((abs_value >> 12) < 0x1000)) { // abs_value is a shifted 12-bit immediate. shift = true; abs_value >>= 12; } else if (LIKELY(abs_value < 0x1000000 && (op == kOpAdd || op == kOpSub))) { // Note: It is better to use two ADD/SUB instead of loading a number to a temp register. // This works for both normal registers and SP. // For a frame size == 0x2468, it will be encoded as: // sub sp, #0x2000 // sub sp, #0x468 if (neg) { op = (op == kOpAdd) ? kOpSub : kOpAdd; } OpRegImm64(op, r_dest_src1, abs_value & (~INT64_C(0xfff))); return OpRegImm64(op, r_dest_src1, abs_value & 0xfff); } else { RegStorage r_tmp; LIR* res; if (IS_WIDE(wide)) { r_tmp = AllocTempWide(); res = LoadConstantWide(r_tmp, value); } else { r_tmp = AllocTemp(); res = LoadConstant(r_tmp, value); } OpRegReg(op, r_dest_src1, r_tmp); FreeTemp(r_tmp); return res; } switch (op) { case kOpAdd: neg_opcode = kA64Sub4RRdT; opcode = kA64Add4RRdT; break; case kOpSub: neg_opcode = kA64Add4RRdT; opcode = kA64Sub4RRdT; break; case kOpCmp: neg_opcode = kA64Cmn3RdT; opcode = kA64Cmp3RdT; break; default: LOG(FATAL) << "Bad op-kind in OpRegImm: " << op; break; } if (UNLIKELY(neg)) opcode = neg_opcode; if (GetEncoder(opcode)->flags & IS_QUAD_OP) return NewLIR4(opcode | wide, r_dest_src1.GetReg(), r_dest_src1.GetReg(), abs_value, (shift) ? 1 : 0); else return NewLIR3(opcode | wide, r_dest_src1.GetReg(), abs_value, (shift) ? 1 : 0); } int Arm64Mir2Lir::EncodeShift(int shift_type, int amount) { DCHECK_EQ(shift_type & 0x3, shift_type); DCHECK_EQ(amount & 0x3f, amount); return ((shift_type & 0x3) << 7) | (amount & 0x3f); } int Arm64Mir2Lir::EncodeExtend(int extend_type, int amount) { DCHECK_EQ(extend_type & 0x7, extend_type); DCHECK_EQ(amount & 0x7, amount); return (1 << 6) | ((extend_type & 0x7) << 3) | (amount & 0x7); } bool Arm64Mir2Lir::IsExtendEncoding(int encoded_value) { return ((1 << 6) & encoded_value) != 0; } LIR* Arm64Mir2Lir::LoadBaseIndexed(RegStorage r_base, RegStorage r_index, RegStorage r_dest, int scale, OpSize size) { LIR* load; int expected_scale = 0; A64Opcode opcode = kA64Brk1d; r_base = Check64BitReg(r_base); // TODO(Arm64): The sign extension of r_index should be carried out by using an extended // register offset load (rather than doing the sign extension in a separate instruction). if (r_index.Is32Bit()) { // Assemble: ``sxtw xN, wN''. r_index = As64BitReg(r_index); NewLIR4(WIDE(kA64Sbfm4rrdd), r_index.GetReg(), r_index.GetReg(), 0, 31); } if (r_dest.IsFloat()) { if (r_dest.IsDouble()) { DCHECK(size == k64 || size == kDouble); expected_scale = 3; opcode = WIDE(kA64Ldr4fXxG); } else { DCHECK(r_dest.IsSingle()); DCHECK(size == k32 || size == kSingle); expected_scale = 2; opcode = kA64Ldr4fXxG; } DCHECK(scale == 0 || scale == expected_scale); return NewLIR4(opcode, r_dest.GetReg(), r_base.GetReg(), r_index.GetReg(), (scale != 0) ? 1 : 0); } switch (size) { case kDouble: case kWord: case k64: r_dest = Check64BitReg(r_dest); opcode = WIDE(kA64Ldr4rXxG); expected_scale = 3; break; case kReference: r_dest = As32BitReg(r_dest); FALLTHROUGH_INTENDED; case kSingle: // Intentional fall-through. case k32: r_dest = Check32BitReg(r_dest); opcode = kA64Ldr4rXxG; expected_scale = 2; break; case kUnsignedHalf: r_dest = Check32BitReg(r_dest); opcode = kA64Ldrh4wXxd; expected_scale = 1; break; case kSignedHalf: r_dest = Check32BitReg(r_dest); opcode = kA64Ldrsh4rXxd; expected_scale = 1; break; case kUnsignedByte: r_dest = Check32BitReg(r_dest); opcode = kA64Ldrb3wXx; break; case kSignedByte: r_dest = Check32BitReg(r_dest); opcode = kA64Ldrsb3rXx; break; default: LOG(FATAL) << "Bad size: " << size; } if (UNLIKELY(expected_scale == 0)) { // This is a tertiary op (e.g. ldrb, ldrsb), it does not not support scale. DCHECK_NE(GetEncoder(UNWIDE(opcode))->flags & IS_TERTIARY_OP, 0U); DCHECK_EQ(scale, 0); load = NewLIR3(opcode, r_dest.GetReg(), r_base.GetReg(), r_index.GetReg()); } else { DCHECK(scale == 0 || scale == expected_scale); load = NewLIR4(opcode, r_dest.GetReg(), r_base.GetReg(), r_index.GetReg(), (scale != 0) ? 1 : 0); } return load; } LIR* Arm64Mir2Lir::StoreBaseIndexed(RegStorage r_base, RegStorage r_index, RegStorage r_src, int scale, OpSize size) { LIR* store; int expected_scale = 0; A64Opcode opcode = kA64Brk1d; r_base = Check64BitReg(r_base); // TODO(Arm64): The sign extension of r_index should be carried out by using an extended // register offset store (rather than doing the sign extension in a separate instruction). if (r_index.Is32Bit()) { // Assemble: ``sxtw xN, wN''. r_index = As64BitReg(r_index); NewLIR4(WIDE(kA64Sbfm4rrdd), r_index.GetReg(), r_index.GetReg(), 0, 31); } if (r_src.IsFloat()) { if (r_src.IsDouble()) { DCHECK(size == k64 || size == kDouble); expected_scale = 3; opcode = WIDE(kA64Str4fXxG); } else { DCHECK(r_src.IsSingle()); DCHECK(size == k32 || size == kSingle); expected_scale = 2; opcode = kA64Str4fXxG; } DCHECK(scale == 0 || scale == expected_scale); return NewLIR4(opcode, r_src.GetReg(), r_base.GetReg(), r_index.GetReg(), (scale != 0) ? 1 : 0); } switch (size) { case kDouble: // Intentional fall-trough. case kWord: // Intentional fall-trough. case k64: r_src = Check64BitReg(r_src); opcode = WIDE(kA64Str4rXxG); expected_scale = 3; break; case kReference: r_src = As32BitReg(r_src); FALLTHROUGH_INTENDED; case kSingle: // Intentional fall-trough. case k32: r_src = Check32BitReg(r_src); opcode = kA64Str4rXxG; expected_scale = 2; break; case kUnsignedHalf: case kSignedHalf: r_src = Check32BitReg(r_src); opcode = kA64Strh4wXxd; expected_scale = 1; break; case kUnsignedByte: case kSignedByte: r_src = Check32BitReg(r_src); opcode = kA64Strb3wXx; break; default: LOG(FATAL) << "Bad size: " << size; } if (UNLIKELY(expected_scale == 0)) { // This is a tertiary op (e.g. strb), it does not not support scale. DCHECK_NE(GetEncoder(UNWIDE(opcode))->flags & IS_TERTIARY_OP, 0U); DCHECK_EQ(scale, 0); store = NewLIR3(opcode, r_src.GetReg(), r_base.GetReg(), r_index.GetReg()); } else { store = NewLIR4(opcode, r_src.GetReg(), r_base.GetReg(), r_index.GetReg(), (scale != 0) ? 1 : 0); } return store; } /* * Load value from base + displacement. Optionally perform null check * on base (which must have an associated s_reg and MIR). If not * performing null check, incoming MIR can be null. */ LIR* Arm64Mir2Lir::LoadBaseDispBody(RegStorage r_base, int displacement, RegStorage r_dest, OpSize size) { LIR* load = nullptr; A64Opcode opcode = kA64Brk1d; A64Opcode alt_opcode = kA64Brk1d; int scale = 0; switch (size) { case kDouble: // Intentional fall-through. case kWord: // Intentional fall-through. case k64: r_dest = Check64BitReg(r_dest); scale = 3; if (r_dest.IsFloat()) { DCHECK(r_dest.IsDouble()); opcode = WIDE(kA64Ldr3fXD); alt_opcode = WIDE(kA64Ldur3fXd); } else { opcode = WIDE(kA64Ldr3rXD); alt_opcode = WIDE(kA64Ldur3rXd); } break; case kReference: r_dest = As32BitReg(r_dest); FALLTHROUGH_INTENDED; case kSingle: // Intentional fall-through. case k32: r_dest = Check32BitReg(r_dest); scale = 2; if (r_dest.IsFloat()) { DCHECK(r_dest.IsSingle()); opcode = kA64Ldr3fXD; } else { opcode = kA64Ldr3rXD; } break; case kUnsignedHalf: scale = 1; opcode = kA64Ldrh3wXF; break; case kSignedHalf: scale = 1; opcode = kA64Ldrsh3rXF; break; case kUnsignedByte: opcode = kA64Ldrb3wXd; break; case kSignedByte: opcode = kA64Ldrsb3rXd; break; default: LOG(FATAL) << "Bad size: " << size; } bool displacement_is_aligned = (displacement & ((1 << scale) - 1)) == 0; int scaled_disp = displacement >> scale; if (displacement_is_aligned && scaled_disp >= 0 && scaled_disp < 4096) { // Can use scaled load. load = NewLIR3(opcode, r_dest.GetReg(), r_base.GetReg(), scaled_disp); } else if (alt_opcode != kA64Brk1d && IS_SIGNED_IMM9(displacement)) { // Can use unscaled load. load = NewLIR3(alt_opcode, r_dest.GetReg(), r_base.GetReg(), displacement); } else { // Use long sequence. // TODO: cleaner support for index/displacement registers? Not a reference, but must match width. RegStorage r_scratch = AllocTempWide(); LoadConstantWide(r_scratch, displacement); load = LoadBaseIndexed(r_base, r_scratch, (size == kReference) ? As64BitReg(r_dest) : r_dest, 0, size); FreeTemp(r_scratch); } // TODO: in future may need to differentiate Dalvik accesses w/ spills if (mem_ref_type_ == ResourceMask::kDalvikReg) { DCHECK_EQ(r_base, rs_sp); AnnotateDalvikRegAccess(load, displacement >> 2, true /* is_load */, r_dest.Is64Bit()); } return load; } LIR* Arm64Mir2Lir::LoadBaseDisp(RegStorage r_base, int displacement, RegStorage r_dest, OpSize size, VolatileKind is_volatile) { // LoadBaseDisp() will emit correct insn for atomic load on arm64 // assuming r_dest is correctly prepared using RegClassForFieldLoadStore(). LIR* load = LoadBaseDispBody(r_base, displacement, r_dest, size); if (UNLIKELY(is_volatile == kVolatile)) { // TODO: This should generate an acquire load instead of the barrier. GenMemBarrier(kLoadAny); } return load; } LIR* Arm64Mir2Lir::StoreBaseDispBody(RegStorage r_base, int displacement, RegStorage r_src, OpSize size) { LIR* store = nullptr; A64Opcode opcode = kA64Brk1d; A64Opcode alt_opcode = kA64Brk1d; int scale = 0; switch (size) { case kDouble: // Intentional fall-through. case kWord: // Intentional fall-through. case k64: r_src = Check64BitReg(r_src); scale = 3; if (r_src.IsFloat()) { DCHECK(r_src.IsDouble()); opcode = WIDE(kA64Str3fXD); alt_opcode = WIDE(kA64Stur3fXd); } else { opcode = WIDE(kA64Str3rXD); alt_opcode = WIDE(kA64Stur3rXd); } break; case kReference: r_src = As32BitReg(r_src); FALLTHROUGH_INTENDED; case kSingle: // Intentional fall-through. case k32: r_src = Check32BitReg(r_src); scale = 2; if (r_src.IsFloat()) { DCHECK(r_src.IsSingle()); opcode = kA64Str3fXD; } else { opcode = kA64Str3rXD; } break; case kUnsignedHalf: case kSignedHalf: scale = 1; opcode = kA64Strh3wXF; break; case kUnsignedByte: case kSignedByte: opcode = kA64Strb3wXd; break; default: LOG(FATAL) << "Bad size: " << size; } bool displacement_is_aligned = (displacement & ((1 << scale) - 1)) == 0; int scaled_disp = displacement >> scale; if (displacement_is_aligned && scaled_disp >= 0 && scaled_disp < 4096) { // Can use scaled store. store = NewLIR3(opcode, r_src.GetReg(), r_base.GetReg(), scaled_disp); } else if (alt_opcode != kA64Brk1d && IS_SIGNED_IMM9(displacement)) { // Can use unscaled store. store = NewLIR3(alt_opcode, r_src.GetReg(), r_base.GetReg(), displacement); } else { // Use long sequence. RegStorage r_scratch = AllocTempWide(); LoadConstantWide(r_scratch, displacement); store = StoreBaseIndexed(r_base, r_scratch, (size == kReference) ? As64BitReg(r_src) : r_src, 0, size); FreeTemp(r_scratch); } // TODO: In future, may need to differentiate Dalvik & spill accesses. if (mem_ref_type_ == ResourceMask::kDalvikReg) { DCHECK_EQ(r_base, rs_sp); AnnotateDalvikRegAccess(store, displacement >> 2, false /* is_load */, r_src.Is64Bit()); } return store; } LIR* Arm64Mir2Lir::StoreBaseDisp(RegStorage r_base, int displacement, RegStorage r_src, OpSize size, VolatileKind is_volatile) { // TODO: This should generate a release store and no barriers. if (UNLIKELY(is_volatile == kVolatile)) { // Ensure that prior accesses become visible to other threads first. GenMemBarrier(kAnyStore); } // StoreBaseDisp() will emit correct insn for atomic store on arm64 // assuming r_dest is correctly prepared using RegClassForFieldLoadStore(). LIR* store = StoreBaseDispBody(r_base, displacement, r_src, size); if (UNLIKELY(is_volatile == kVolatile)) { // Preserve order with respect to any subsequent volatile loads. // We need StoreLoad, but that generally requires the most expensive barrier. GenMemBarrier(kAnyAny); } return store; } LIR* Arm64Mir2Lir::OpFpRegCopy(RegStorage r_dest, RegStorage r_src) { UNUSED(r_dest, r_src); LOG(FATAL) << "Unexpected use of OpFpRegCopy for Arm64"; UNREACHABLE(); } LIR* Arm64Mir2Lir::OpMem(OpKind op, RegStorage r_base, int disp) { UNUSED(op, r_base, disp); LOG(FATAL) << "Unexpected use of OpMem for Arm64"; UNREACHABLE(); } LIR* Arm64Mir2Lir::InvokeTrampoline(OpKind op, RegStorage r_tgt, QuickEntrypointEnum trampoline ATTRIBUTE_UNUSED) { // The address of the trampoline is already loaded into r_tgt. return OpReg(op, r_tgt); } } // namespace art