| /* |
| * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2014, 2020 Red Hat Inc. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| #include "precompiled.hpp" |
| #include "asm/assembler.hpp" |
| #include "asm/assembler.inline.hpp" |
| #include "asm/macroAssembler.hpp" |
| #include "compiler/disassembler.hpp" |
| #include "immediate_aarch64.hpp" |
| #include "memory/resourceArea.hpp" |
| #include "metaprogramming/primitiveConversions.hpp" |
| |
| #ifndef PRODUCT |
| const uintptr_t Assembler::asm_bp = 0x0000ffffac221240; |
| #endif |
| |
| static float unpack(unsigned value); |
| |
| short Assembler::SIMD_Size_in_bytes[] = { |
| // T8B, T16B, T4H, T8H, T2S, T4S, T1D, T2D, T1Q |
| 8, 16, 8, 16, 8, 16, 8, 16, 16 |
| }; |
| |
| Assembler::SIMD_Arrangement Assembler::_esize2arrangement_table[9][2] = { |
| // esize isQ:false isQ:true |
| /* 0 */ {INVALID_ARRANGEMENT, INVALID_ARRANGEMENT}, |
| /* 1 */ {T8B, T16B}, |
| /* 2 */ {T4H, T8H}, |
| /* 3 */ {INVALID_ARRANGEMENT, INVALID_ARRANGEMENT}, |
| /* 4 */ {T2S, T4S}, |
| /* 5 */ {INVALID_ARRANGEMENT, INVALID_ARRANGEMENT}, |
| /* 6 */ {INVALID_ARRANGEMENT, INVALID_ARRANGEMENT}, |
| /* 7 */ {INVALID_ARRANGEMENT, INVALID_ARRANGEMENT}, |
| /* 8 */ {T1D, T2D} |
| }; |
| |
| Assembler::SIMD_RegVariant Assembler::_esize2regvariant[9] = { |
| INVALID, |
| B, |
| H, |
| INVALID, |
| S, |
| INVALID, |
| INVALID, |
| INVALID, |
| D, |
| }; |
| |
| Assembler::SIMD_Arrangement Assembler::esize2arrangement(unsigned esize, bool isQ) { |
| guarantee(esize < ARRAY_SIZE(_esize2arrangement_table) && |
| _esize2arrangement_table[esize][isQ] != INVALID_ARRANGEMENT, "unsupported element size"); |
| return _esize2arrangement_table[esize][isQ]; |
| } |
| |
| Assembler::SIMD_RegVariant Assembler::elemBytes_to_regVariant(unsigned esize) { |
| guarantee(esize < ARRAY_SIZE(_esize2regvariant) && _esize2regvariant[esize] != INVALID, |
| "unsupported element size"); |
| return _esize2regvariant[esize]; |
| } |
| |
| Assembler::SIMD_RegVariant Assembler::elemType_to_regVariant(BasicType bt) { |
| return elemBytes_to_regVariant(type2aelembytes(bt)); |
| } |
| |
| unsigned Assembler::regVariant_to_elemBits(Assembler::SIMD_RegVariant T){ |
| guarantee(T != Q, "Invalid register variant"); |
| return 1 << (T + 3); |
| } |
| |
| void Assembler::emit_data64(jlong data, |
| relocInfo::relocType rtype, |
| int format) { |
| if (rtype == relocInfo::none) { |
| emit_int64(data); |
| } else { |
| emit_data64(data, Relocation::spec_simple(rtype), format); |
| } |
| } |
| |
| void Assembler::emit_data64(jlong data, |
| RelocationHolder const& rspec, |
| int format) { |
| |
| assert(inst_mark() != nullptr, "must be inside InstructionMark"); |
| // Do not use AbstractAssembler::relocate, which is not intended for |
| // embedded words. Instead, relocate to the enclosing instruction. |
| code_section()->relocate(inst_mark(), rspec, format); |
| emit_int64(data); |
| } |
| |
| extern "C" { |
| void das(uint64_t start, int len) { |
| ResourceMark rm; |
| len <<= 2; |
| if (len < 0) |
| Disassembler::decode((address)start + len, (address)start); |
| else |
| Disassembler::decode((address)start, (address)start + len); |
| } |
| |
| JNIEXPORT void das1(uintptr_t insn) { |
| das(insn, 1); |
| } |
| } |
| |
| #define __ as-> |
| |
| void Address::lea(MacroAssembler *as, Register r) const { |
| switch(_mode) { |
| case base_plus_offset: { |
| if (offset() == 0 && base() == r) // it's a nop |
| break; |
| if (offset() > 0) |
| __ add(r, base(), offset()); |
| else |
| __ sub(r, base(), -offset()); |
| break; |
| } |
| case base_plus_offset_reg: { |
| __ add(r, base(), index(), ext().op(), MAX2(ext().shift(), 0)); |
| break; |
| } |
| case literal: { |
| as->code_section()->relocate(as->inst_mark(), rspec()); |
| if (rspec().type() == relocInfo::none) |
| __ mov(r, target()); |
| else |
| __ movptr(r, (uint64_t)target()); |
| break; |
| } |
| default: |
| ShouldNotReachHere(); |
| } |
| } |
| |
| #undef __ |
| |
| #define starti Instruction_aarch64 current_insn(this); |
| |
| #define f current_insn.f |
| #define sf current_insn.sf |
| #define rf current_insn.rf |
| #define srf current_insn.srf |
| #define zrf current_insn.zrf |
| #define prf current_insn.prf |
| #define pgrf current_insn.pgrf |
| #define fixed current_insn.fixed |
| |
| void Assembler::adr(Register Rd, address adr) { |
| intptr_t offset = adr - pc(); |
| int offset_lo = offset & 3; |
| offset >>= 2; |
| starti; |
| f(0, 31), f(offset_lo, 30, 29), f(0b10000, 28, 24), sf(offset, 23, 5); |
| rf(Rd, 0); |
| } |
| |
| void Assembler::_adrp(Register Rd, address adr) { |
| uint64_t pc_page = (uint64_t)pc() >> 12; |
| uint64_t adr_page = (uint64_t)adr >> 12; |
| intptr_t offset = adr_page - pc_page; |
| int offset_lo = offset & 3; |
| offset >>= 2; |
| starti; |
| f(1, 31), f(offset_lo, 30, 29), f(0b10000, 28, 24), sf(offset, 23, 5); |
| zrf(Rd, 0); |
| } |
| |
| // This encoding is similar (but not quite identical) to the encoding used |
| // by literal ld/st. see JDK-8324123. |
| // PRFM does not support writeback or pre/post index. |
| void Assembler::prfm(const Address &adr, prfop pfop) { |
| Address::mode mode = adr.getMode(); |
| // PRFM does not support pre/post index |
| guarantee((mode != Address::pre) && (mode != Address::post), "prfm does not support pre/post indexing"); |
| if (mode == Address::literal) { |
| starti; |
| f(0b11, 31, 30), f(0b011, 29, 27), f(0b000, 26, 24); |
| f(pfop, 4, 0); |
| int64_t offset = (adr.target() - pc()) >> 2; |
| sf(offset, 23, 5); |
| } else { |
| assert((mode == Address::base_plus_offset) |
| || (mode == Address::base_plus_offset_reg), "must be base_plus_offset/base_plus_offset_reg"); |
| ld_st2(as_Register(pfop), adr, 0b11, 0b10); |
| } |
| } |
| |
| // An "all-purpose" add/subtract immediate, per ARM documentation: |
| // A "programmer-friendly" assembler may accept a negative immediate |
| // between -(2^24 -1) and -1 inclusive, causing it to convert a |
| // requested ADD operation to a SUB, or vice versa, and then encode |
| // the absolute value of the immediate as for uimm24. |
| void Assembler::add_sub_immediate(Instruction_aarch64 ¤t_insn, |
| Register Rd, Register Rn, unsigned uimm, int op, |
| int negated_op) { |
| bool sets_flags = op & 1; // this op sets flags |
| union { |
| unsigned u; |
| int imm; |
| }; |
| u = uimm; |
| bool shift = false; |
| bool neg = imm < 0; |
| if (neg) { |
| imm = -imm; |
| op = negated_op; |
| } |
| assert(Rd != sp || imm % 16 == 0, "misaligned stack"); |
| if (imm >= (1 << 11) |
| && ((imm >> 12) << 12 == imm)) { |
| imm >>= 12; |
| shift = true; |
| } |
| f(op, 31, 29), f(0b10001, 28, 24), f(shift, 23, 22), f(imm, 21, 10); |
| |
| // add/subtract immediate ops with the S bit set treat r31 as zr; |
| // with S unset they use sp. |
| if (sets_flags) |
| zrf(Rd, 0); |
| else |
| srf(Rd, 0); |
| |
| srf(Rn, 5); |
| } |
| |
| #undef f |
| #undef sf |
| #undef rf |
| #undef srf |
| #undef zrf |
| #undef prf |
| #undef pgrf |
| #undef fixed |
| |
| #undef starti |
| |
| #ifdef ASSERT |
| |
| void Address::assert_is_literal() const { |
| assert(_mode == literal, "addressing mode is non-literal: %d", _mode); |
| } |
| |
| void Address::assert_is_nonliteral() const { |
| assert(_mode != literal, "unexpected literal addressing mode"); |
| assert(_mode != no_mode, "unexpected no_mode addressing mode"); |
| } |
| |
| #endif // ASSERT |
| |
| static RelocationHolder address_relocation(address target, relocInfo::relocType rtype) { |
| switch (rtype) { |
| case relocInfo::oop_type: |
| case relocInfo::metadata_type: |
| // Oops are a special case. Normally they would be their own section |
| // but in cases like icBuffer they are literals in the code stream that |
| // we don't have a section for. We use none so that we get a literal address |
| // which is always patchable. |
| return RelocationHolder::none; |
| case relocInfo::external_word_type: |
| return external_word_Relocation::spec(target); |
| case relocInfo::internal_word_type: |
| return internal_word_Relocation::spec(target); |
| case relocInfo::opt_virtual_call_type: |
| return opt_virtual_call_Relocation::spec(); |
| case relocInfo::static_call_type: |
| return static_call_Relocation::spec(); |
| case relocInfo::runtime_call_type: |
| return runtime_call_Relocation::spec(); |
| case relocInfo::poll_type: |
| case relocInfo::poll_return_type: |
| return Relocation::spec_simple(rtype); |
| case relocInfo::none: |
| return RelocationHolder::none; |
| default: |
| ShouldNotReachHere(); |
| return RelocationHolder::none; |
| } |
| } |
| |
| Address::Address(address target, relocInfo::relocType rtype) : |
| _mode(literal), |
| _literal(target, address_relocation(target, rtype)) |
| {} |
| |
| void Assembler::b(const Address &dest) { |
| code_section()->relocate(pc(), dest.rspec()); |
| b(dest.target()); |
| } |
| |
| void Assembler::bl(const Address &dest) { |
| code_section()->relocate(pc(), dest.rspec()); |
| bl(dest.target()); |
| } |
| |
| void Assembler::adr(Register r, const Address &dest) { |
| code_section()->relocate(pc(), dest.rspec()); |
| adr(r, dest.target()); |
| } |
| |
| void Assembler::br(Condition cc, Label &L) { |
| if (L.is_bound()) { |
| br(cc, target(L)); |
| } else { |
| L.add_patch_at(code(), locator()); |
| br(cc, pc()); |
| } |
| } |
| |
| void Assembler::wrap_label(Label &L, |
| Assembler::uncond_branch_insn insn) { |
| if (L.is_bound()) { |
| (this->*insn)(target(L)); |
| } else { |
| L.add_patch_at(code(), locator()); |
| (this->*insn)(pc()); |
| } |
| } |
| |
| void Assembler::wrap_label(Register r, Label &L, |
| compare_and_branch_insn insn) { |
| if (L.is_bound()) { |
| (this->*insn)(r, target(L)); |
| } else { |
| L.add_patch_at(code(), locator()); |
| (this->*insn)(r, pc()); |
| } |
| } |
| |
| void Assembler::wrap_label(Register r, int bitpos, Label &L, |
| test_and_branch_insn insn) { |
| if (L.is_bound()) { |
| (this->*insn)(r, bitpos, target(L)); |
| } else { |
| L.add_patch_at(code(), locator()); |
| (this->*insn)(r, bitpos, pc()); |
| } |
| } |
| |
| void Assembler::wrap_label(Label &L, prfop op, prefetch_insn insn) { |
| if (L.is_bound()) { |
| (this->*insn)(target(L), op); |
| } else { |
| L.add_patch_at(code(), locator()); |
| (this->*insn)(pc(), op); |
| } |
| } |
| |
| bool Assembler::operand_valid_for_add_sub_immediate(int64_t imm) { |
| return operand_valid_for_immediate_bits(imm, 12); |
| } |
| |
| bool Assembler::operand_valid_for_sve_add_sub_immediate(int64_t imm) { |
| return operand_valid_for_immediate_bits(imm, 8); |
| } |
| |
| bool Assembler::operand_valid_for_logical_immediate(bool is32, uint64_t imm) { |
| return encode_logical_immediate(is32, imm) != 0xffffffff; |
| } |
| |
| // Check immediate encoding for movi. |
| // Return the shift amount which can be {0, 8, 16, 24} for B/H/S types. As the D type |
| // movi does not have shift variant, in this case the return value is the immediate |
| // after encoding. |
| // Return -1 if the input imm64 can not be encoded. |
| int Assembler::operand_valid_for_movi_immediate(uint64_t imm64, SIMD_Arrangement T) { |
| if (T == T1D || T == T2D) { |
| // To encode into movi, the 64-bit imm must be in the form of |
| // 'aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh' |
| // and encoded in "a:b:c:d:e:f:g:h". |
| uint64_t tmp = imm64; |
| uint64_t one_byte = 0; |
| for (int i = 0; i < 8; i++) { |
| one_byte = tmp & 0xffULL; |
| if (one_byte != 0xffULL && one_byte != 0) { |
| return -1; // can not be encoded |
| } |
| tmp = tmp >> 8; |
| } |
| |
| imm64 &= 0x0101010101010101ULL; |
| imm64 |= (imm64 >> 7); |
| imm64 |= (imm64 >> 14); |
| imm64 |= (imm64 >> 28); |
| |
| return imm64 & 0xff; |
| } |
| |
| uint32_t imm32 = imm64 & 0xffffffffULL; |
| if (T == T8B || T == T16B) { // 8-bit variant |
| if (0 == (imm32 & ~0xff)) return 0; |
| } else if(T == T4H || T == T8H) { // 16-bit variant |
| if (0 == (imm32 & ~0xff)) return 0; |
| if (0 == (imm32 & ~0xff00)) return 8; |
| } else if (T == T2S || T == T4S) { // 32-bit variant |
| if (0 == (imm32 & ~0xff)) return 0; |
| if (0 == (imm32 & ~0xff00)) return 8; |
| if (0 == (imm32 & ~0xff0000)) return 16; |
| if (0 == (imm32 & ~0xff000000)) return 24; |
| } else { |
| assert(false, "unsupported"); |
| ShouldNotReachHere(); |
| } |
| |
| return -1; |
| } |
| |
| bool Assembler::operand_valid_for_sve_logical_immediate(unsigned elembits, uint64_t imm) { |
| return encode_sve_logical_immediate(elembits, imm) != 0xffffffff; |
| } |
| |
| static uint64_t doubleTo64Bits(jdouble d) { |
| union { |
| jdouble double_value; |
| uint64_t double_bits; |
| }; |
| |
| double_value = d; |
| return double_bits; |
| } |
| |
| bool Assembler::operand_valid_for_float_immediate(double imm) { |
| // If imm is all zero bits we can use ZR as the source of a |
| // floating-point value. |
| if (doubleTo64Bits(imm) == 0) |
| return true; |
| |
| // Otherwise try to encode imm then convert the encoded value back |
| // and make sure it's the exact same bit pattern. |
| unsigned result = encoding_for_fp_immediate(imm); |
| return doubleTo64Bits(imm) == fp_immediate_for_encoding(result, true); |
| } |
| |
| int AbstractAssembler::code_fill_byte() { |
| return 0; |
| } |
| |
| // n.b. this is implemented in subclass MacroAssembler |
| void Assembler::bang_stack_with_offset(int offset) { Unimplemented(); } |
| |
| bool asm_util::operand_valid_for_immediate_bits(int64_t imm, unsigned nbits) { |
| guarantee(nbits == 8 || nbits == 12, "invalid nbits value"); |
| uint64_t uimm = (uint64_t)uabs((jlong)imm); |
| if (uimm < (UCONST64(1) << nbits)) |
| return true; |
| if (uimm < (UCONST64(1) << (2 * nbits)) |
| && ((uimm >> nbits) << nbits == uimm)) { |
| return true; |
| } |
| return false; |
| } |
| |
| // and now the routines called by the assembler which encapsulate the |
| // above encode and decode functions |
| |
| uint32_t |
| asm_util::encode_logical_immediate(bool is32, uint64_t imm) |
| { |
| if (is32) { |
| /* Allow all zeros or all ones in top 32-bits, so that |
| constant expressions like ~1 are permitted. */ |
| if (imm >> 32 != 0 && imm >> 32 != 0xffffffff) |
| return 0xffffffff; |
| /* Replicate the 32 lower bits to the 32 upper bits. */ |
| imm &= 0xffffffff; |
| imm |= imm << 32; |
| } |
| |
| return encoding_for_logical_immediate(imm); |
| } |
| |
| uint32_t |
| asm_util::encode_sve_logical_immediate(unsigned elembits, uint64_t imm) { |
| guarantee(elembits == 8 || elembits == 16 || |
| elembits == 32 || elembits == 64, "unsupported element size"); |
| uint64_t upper = UCONST64(-1) << (elembits/2) << (elembits/2); |
| /* Allow all zeros or all ones in top bits, so that |
| * constant expressions like ~1 are permitted. */ |
| if ((imm & ~upper) != imm && (imm | upper) != imm) |
| return 0xffffffff; |
| |
| // Replicate the immediate in different element sizes to 64 bits. |
| imm &= ~upper; |
| for (unsigned i = elembits; i < 64; i *= 2) { |
| imm |= (imm << i); |
| } |
| |
| return encoding_for_logical_immediate(imm); |
| } |
| |
| unsigned Assembler::pack(double value) { |
| float val = (float)value; |
| unsigned result = encoding_for_fp_immediate(val); |
| guarantee(unpack(result) == value, |
| "Invalid floating-point immediate operand"); |
| return result; |
| } |
| |
| // Packed operands for Floating-point Move (immediate) |
| |
| static float unpack(unsigned value) { |
| unsigned ival = fp_immediate_for_encoding(value, 0); |
| return PrimitiveConversions::cast<float>(ival); |
| } |
| |
| address Assembler::locate_next_instruction(address inst) { |
| return inst + Assembler::instruction_size; |
| } |