| /* | |
| * Copyright (c) 2008, 2025, Oracle and/or its affiliates. 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 "asm/assembler.inline.hpp" | |
| #include "code/codeCache.hpp" | |
| #include "memory/resourceArea.hpp" | |
| #include "nativeInst_arm.hpp" | |
| #include "oops/oop.inline.hpp" | |
| #include "runtime/handles.hpp" | |
| #include "runtime/sharedRuntime.hpp" | |
| #include "runtime/stubRoutines.hpp" | |
| #include "utilities/ostream.hpp" | |
| #ifdef COMPILER1 | |
| #include "c1/c1_Runtime1.hpp" | |
| #endif | |
| int NativeMovRegMem::offset() const { | |
| switch (kind()) { | |
| case instr_ldr_str: | |
| return encoding() & 0xfff; | |
| case instr_ldrh_strh: | |
| return (encoding() & 0x0f) | ((encoding() >> 4) & 0xf0); | |
| case instr_fld_fst: | |
| return (encoding() & 0xff) << 2; | |
| default: | |
| ShouldNotReachHere(); | |
| return 0; | |
| } | |
| } | |
| void NativeMovRegMem::set_offset(int x) { | |
| assert(x >= 0 && x < 65536, "encoding constraint"); | |
| const int Rt = Rtemp->encoding(); | |
| // If offset is too large to be placed into single ldr/str instruction, we replace | |
| // ldr Rd, [Rn, #offset] | |
| // nop | |
| // with | |
| // add Rtemp, Rn, #offset_hi | |
| // ldr Rd, [Rtemp, #offset_lo] | |
| switch (kind()) { | |
| case instr_ldr_str: | |
| if (x < 4096) { | |
| set_encoding((encoding() & 0xfffff000) | x); | |
| } else { | |
| NativeInstruction* next = nativeInstruction_at(next_raw_instruction_address()); | |
| assert(next->is_nop(), "must be"); | |
| next->set_encoding((encoding() & 0xfff0f000) | Rt << 16 | (x & 0xfff)); | |
| this->set_encoding((encoding() & 0x000f0000) | Rt << 12 | x >> 12 | 0xe2800a00); | |
| } | |
| break; | |
| case instr_ldrh_strh: | |
| if (x < 256) { | |
| set_encoding((encoding() & 0xfffff0f0) | (x & 0x0f) | (x & 0xf0) << 4); | |
| } else { | |
| NativeInstruction* next = nativeInstruction_at(next_raw_instruction_address()); | |
| assert(next->is_nop(), "must be"); | |
| next->set_encoding((encoding() & 0xfff0f0f0) | Rt << 16 | (x & 0x0f) | (x & 0xf0) << 4); | |
| this->set_encoding((encoding() & 0x000f0000) | Rt << 12 | x >> 8 | 0xe2800c00); | |
| } | |
| break; | |
| case instr_fld_fst: | |
| if (x < 1024) { | |
| set_encoding((encoding() & 0xffffff00) | (x >> 2)); | |
| } else { | |
| NativeInstruction* next = nativeInstruction_at(next_raw_instruction_address()); | |
| assert(next->is_nop(), "must be"); | |
| next->set_encoding((encoding() & 0xfff0ff00) | Rt << 16 | ((x >> 2) & 0xff)); | |
| this->set_encoding((encoding() & 0x000f0000) | Rt << 12 | x >> 10 | 0xe2800b00); | |
| } | |
| break; | |
| default: | |
| ShouldNotReachHere(); | |
| } | |
| } | |
| intptr_t NativeMovConstReg::data() const { | |
| RawNativeInstruction* next = next_raw(); | |
| if (is_movw()) { | |
| // Oop embedded in movw/movt instructions | |
| assert(VM_Version::supports_movw(), "must be"); | |
| return (this->encoding() & 0x00000fff) | (this->encoding() & 0x000f0000) >> 4 | | |
| (next->encoding() & 0x00000fff) << 16 | (next->encoding() & 0x000f0000) << 12; | |
| } else { | |
| // Oop is loaded from oops section or inlined in the code | |
| int oop_offset; | |
| if (is_ldr_literal()) { | |
| // ldr Rd, [PC, #offset] | |
| oop_offset = ldr_offset(); | |
| } else { | |
| assert(next->is_ldr(), "must be"); | |
| oop_offset = (this->encoding() & 0xff) << 12 | (next->encoding() & 0xfff); | |
| if (is_add_pc()) { | |
| // add Rd, PC, #offset_hi | |
| // ldr Rd, [Rd, #offset_lo] | |
| assert(next->encoding() & (1 << 23), "sign mismatch"); | |
| // offset OK (both positive) | |
| } else { | |
| assert(is_sub_pc(), "must be"); | |
| // sub Rd, PC, #offset_hi | |
| // ldr Rd, [Rd, -#offset_lo] | |
| assert(!(next->encoding() & (1 << 23)), "sign mismatch"); | |
| // negative offsets | |
| oop_offset = -oop_offset; | |
| } | |
| } | |
| return *(int*)(instruction_address() + 8 + oop_offset); | |
| } | |
| } | |
| void NativeMovConstReg::set_data(intptr_t x, address pc) { | |
| // Find and replace the oop corresponding to this instruction in oops section | |
| RawNativeInstruction* next = next_raw(); | |
| oop* oop_addr = nullptr; | |
| Metadata** metadata_addr = nullptr; | |
| CodeBlob* cb = CodeCache::find_blob(instruction_address()); | |
| if (cb != nullptr) { | |
| nmethod* nm = cb->as_nmethod_or_null(); | |
| if (nm != nullptr) { | |
| RelocIterator iter(nm, instruction_address(), next->instruction_address()); | |
| while (iter.next()) { | |
| if (iter.type() == relocInfo::oop_type) { | |
| oop_addr = iter.oop_reloc()->oop_addr(); | |
| *oop_addr = cast_to_oop(x); | |
| break; | |
| } else if (iter.type() == relocInfo::metadata_type) { | |
| metadata_addr = iter.metadata_reloc()->metadata_addr(); | |
| *metadata_addr = (Metadata*)x; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if (is_movw()) { | |
| // data embedded in movw/movt instructions | |
| assert(VM_Version::supports_movw(), "must be"); | |
| unsigned int lo = (unsigned int)x; | |
| unsigned int hi = (unsigned int)(x >> 16); | |
| this->set_encoding((this->encoding() & 0xfff0f000) | (lo & 0xf000) << 4 | (lo & 0xfff)); | |
| next->set_encoding((next->encoding() & 0xfff0f000) | (hi & 0xf000) << 4 | (hi & 0xfff)); | |
| } else if (oop_addr == nullptr && metadata_addr == nullptr) { | |
| // A static ldr_literal (without oop or metadata relocation) | |
| assert(is_ldr_literal(), "must be"); | |
| int offset = ldr_offset(); | |
| oop_addr = (oop*)(instruction_address() + 8 + offset); | |
| *oop_addr = cast_to_oop(x); | |
| } else { | |
| // data is loaded from oop or metadata section | |
| int offset; | |
| address addr = oop_addr != nullptr ? (address)oop_addr : (address)metadata_addr; | |
| if(pc == 0) { | |
| offset = addr - instruction_address() - 8; | |
| } else { | |
| offset = addr - pc - 8; | |
| } | |
| int sign = (offset >= 0) ? (1 << 23) : 0; | |
| int delta = (offset >= 0) ? offset : (-offset); | |
| assert(delta < 0x100000, "within accessible range"); | |
| if (is_ldr_literal()) { | |
| // fix the ldr with the real offset to the oop/metadata table | |
| assert(next->is_nop(), "must be"); | |
| if (delta < 4096) { | |
| // ldr Rd, [PC, #offset] | |
| set_encoding((encoding() & 0xff7ff000) | delta | sign); | |
| assert(ldr_offset() == offset, "check encoding"); | |
| } else { | |
| int cc = encoding() & 0xf0000000; | |
| int Rd = (encoding() >> 12) & 0xf; | |
| int Rt = Rd; | |
| assert(Rt != 0xf, "Illegal destination register"); // or fix by using Rtemp | |
| // move the ldr, fixing delta_lo and the source register | |
| next->set_encoding((encoding() & 0xff70f000) | (Rt << 16) | (delta & 0xfff) | sign); | |
| assert(next->is_ldr(), "must be"); | |
| if (offset > 0) { | |
| // add Rt, PC, #delta_hi | |
| // ldr Rd, [Rt, #delta_lo] | |
| this->set_encoding((Rt << 12) | (delta >> 12) | 0x028f0a00 | cc); | |
| assert(is_add_pc(), "must be"); | |
| } else { | |
| // sub Rt, PC, #delta_hi | |
| // ldr Rd, [Rt, -#delta_lo] | |
| this->set_encoding((Rt << 12) | (delta >> 12) | 0x024f0a00 | cc); | |
| assert(is_sub_pc(), "must be"); | |
| } | |
| } | |
| } else { | |
| assert(is_pc_rel(), "must be"); | |
| assert(next->is_ldr(), "must be"); | |
| if (offset > 0) { | |
| // add Rt, PC, #delta_hi | |
| this->set_encoding((this->encoding() & 0xf00ff000) | 0x02800a00 | (delta >> 12)); | |
| assert(is_add_pc(), "must be"); | |
| } else { | |
| // sub Rt, PC, #delta_hi | |
| this->set_encoding((this->encoding() & 0xf00ff000) | 0x02400a00 | (delta >> 12)); | |
| assert(is_sub_pc(), "must be"); | |
| } | |
| // ldr Rd, Rt, #delta_lo (or -#delta_lo) | |
| next->set_encoding((next->encoding() & 0xff7ff000) | (delta & 0xfff) | sign); | |
| } | |
| } | |
| } | |
| void NativeMovConstReg::set_pc_relative_offset(address addr, address pc) { | |
| int offset; | |
| if (pc == 0) { | |
| offset = addr - instruction_address() - 8; | |
| } else { | |
| offset = addr - pc - 8; | |
| } | |
| RawNativeInstruction* next = next_raw(); | |
| int sign = (offset >= 0) ? (1 << 23) : 0; | |
| int delta = (offset >= 0) ? offset : (-offset); | |
| assert(delta < 0x100000, "within accessible range"); | |
| if (is_ldr_literal()) { | |
| if (delta < 4096) { | |
| // ldr Rd, [PC, #offset] | |
| set_encoding((encoding() & 0xff7ff000) | delta | sign); | |
| assert(ldr_offset() == offset, "check encoding"); | |
| } else { | |
| assert(next->is_nop(), "must be"); | |
| int cc = encoding() & 0xf0000000; | |
| int Rd = (encoding() >> 12) & 0xf; | |
| int Rt = Rd; | |
| assert(Rt != 0xf, "Illegal destination register"); // or fix by using Rtemp | |
| // move the ldr, fixing delta_lo and the source register | |
| next->set_encoding((encoding() & 0xff70f000) | (Rt << 16) | (delta & 0xfff) | sign); | |
| assert(next->is_ldr(), "must be"); | |
| if (offset > 0) { | |
| // add Rt, PC, #delta_hi | |
| // ldr Rd, [Rt, #delta_lo] | |
| this->set_encoding((Rt << 12) | (delta >> 12) | 0x028f0a00 | cc); | |
| assert(is_add_pc(), "must be"); | |
| } else { | |
| // sub Rt, PC, #delta_hi | |
| // ldr Rd, [Rt, -#delta_lo] | |
| this->set_encoding((Rt << 12) | (delta >> 12) | 0x024f0a00 | cc); | |
| assert(is_sub_pc(), "must be"); | |
| } | |
| } | |
| } else { | |
| assert(is_pc_rel(), "must be"); | |
| assert(next->is_ldr(), "must be"); | |
| if (offset > 0) { | |
| // add Rt, PC, #delta_hi | |
| this->set_encoding((this->encoding() & 0xf00ff000) | 0x02800a00 | (delta >> 12)); | |
| assert(is_add_pc(), "must be"); | |
| } else { | |
| // sub Rt, PC, #delta_hi | |
| this->set_encoding((this->encoding() & 0xf00ff000) | 0x02400a00 | (delta >> 12)); | |
| assert(is_sub_pc(), "must be"); | |
| } | |
| // ldr Rd, Rt, #delta_lo (or -#delta_lo) | |
| next->set_encoding((next->encoding() & 0xff7ff000) | (delta & 0xfff) | sign); | |
| } | |
| } | |
| void RawNativeJump::check_verified_entry_alignment(address entry, address verified_entry) { | |
| } | |
| void RawNativeJump::patch_verified_entry(address entry, address verified_entry, address dest) { | |
| assert(dest == SharedRuntime::get_handle_wrong_method_stub(), "should be"); | |
| int *a = (int *)verified_entry; | |
| a[0] = not_entrant_illegal_instruction; // always illegal | |
| ICache::invalidate_range((address)&a[0], sizeof a[0]); | |
| } | |
| void NativeGeneralJump::insert_unconditional(address code_pos, address entry) { | |
| int offset = (int)(entry - code_pos - 8); | |
| assert(offset < 0x2000000 && offset > -0x2000000, "encoding constraint"); | |
| nativeInstruction_at(code_pos)->set_encoding(0xea000000 | ((unsigned int)offset << 6 >> 8)); | |
| } | |
| static address raw_call_for(address return_address) { | |
| CodeBlob* cb = CodeCache::find_blob(return_address); | |
| nmethod* nm = cb->as_nmethod_or_null(); | |
| if (nm == nullptr) { | |
| ShouldNotReachHere(); | |
| return nullptr; | |
| } | |
| // Look back 4 instructions, to allow for ic_call | |
| address begin = MAX2(return_address - 4*NativeInstruction::instruction_size, nm->code_begin()); | |
| RelocIterator iter(nm, begin, return_address); | |
| while (iter.next()) { | |
| Relocation* reloc = iter.reloc(); | |
| if (reloc->is_call()) { | |
| address call = reloc->addr(); | |
| if (nativeInstruction_at(call)->is_call()) { | |
| if (nativeCall_at(call)->return_address() == return_address) { | |
| return call; | |
| } | |
| } else { | |
| // Some "calls" are really jumps | |
| assert(nativeInstruction_at(call)->is_jump(), "must be call or jump"); | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| bool RawNativeCall::is_call_before(address return_address) { | |
| return (raw_call_for(return_address) != nullptr); | |
| } | |
| NativeCall* rawNativeCall_before(address return_address) { | |
| address call = raw_call_for(return_address); | |
| assert(call != nullptr, "must be"); | |
| return nativeCall_at(call); | |
| } | |
| void NativePostCallNop::make_deopt() { | |
| NativeDeoptInstruction::insert(addr_at(0)); | |
| } | |
| void NativeDeoptInstruction::verify() { | |
| } | |
| // Inserts an undefined instruction at a given pc | |
| void NativeDeoptInstruction::insert(address code_pos) { | |
| // UDF, Encoding A1: | |
| // 1 1 1 0 | 0 1 1 1 | 1 1 1 1 | imm12 | 1 1 1 1 | imm4 | | |
| // e | 7 | f | dec | f | a | | |
| // 0xe7 | 0xfd, 0xec | 0xfa | |
| uint32_t insn = 0xe7fdecfa; | |
| uint32_t *pos = (uint32_t *) code_pos; | |
| *pos = insn; | |
| ICache::invalidate_range(code_pos, 4); | |
| } |