blob: 5d4f0801ec62fd1dae55737e90cd82546dc2a284 [file]
/*
* Copyright (c) 2019, 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/macroAssembler.inline.hpp"
#include "code/codeBlob.hpp"
#include "code/vmreg.inline.hpp"
#include "gc/z/zAddress.hpp"
#include "gc/z/zBarrier.inline.hpp"
#include "gc/z/zBarrierSet.hpp"
#include "gc/z/zBarrierSetAssembler.hpp"
#include "gc/z/zBarrierSetRuntime.hpp"
#include "gc/z/zThreadLocalData.hpp"
#include "memory/resourceArea.hpp"
#include "nativeInst_aarch64.hpp"
#include "runtime/icache.hpp"
#include "runtime/jniHandles.hpp"
#include "runtime/sharedRuntime.hpp"
#include "utilities/macros.hpp"
#ifdef COMPILER1
#include "c1/c1_LIRAssembler.hpp"
#include "c1/c1_MacroAssembler.hpp"
#include "gc/z/c1/zBarrierSetC1.hpp"
#endif // COMPILER1
#ifdef COMPILER2
#include "gc/z/c2/zBarrierSetC2.hpp"
#include "opto/output.hpp"
#endif // COMPILER2
#ifdef PRODUCT
#define BLOCK_COMMENT(str) /* nothing */
#else
#define BLOCK_COMMENT(str) __ block_comment(str)
#endif
#undef __
#define __ masm->
// Helper for saving and restoring registers across a runtime call that does
// not have any live vector registers.
class ZRuntimeCallSpill {
private:
MacroAssembler* _masm;
Register _result;
void save() {
MacroAssembler* masm = _masm;
__ enter(true /* strip_ret_addr */);
if (_result != noreg) {
__ push_call_clobbered_registers_except(RegSet::of(_result));
} else {
__ push_call_clobbered_registers();
}
}
void restore() {
MacroAssembler* masm = _masm;
if (_result != noreg) {
// Make sure _result has the return value.
if (_result != r0) {
__ mov(_result, r0);
}
__ pop_call_clobbered_registers_except(RegSet::of(_result));
} else {
__ pop_call_clobbered_registers();
}
__ leave();
}
public:
ZRuntimeCallSpill(MacroAssembler* masm, Register result)
: _masm(masm),
_result(result) {
save();
}
~ZRuntimeCallSpill() {
restore();
}
};
void ZBarrierSetAssembler::load_at(MacroAssembler* masm,
DecoratorSet decorators,
BasicType type,
Register dst,
Address src,
Register tmp1,
Register tmp2) {
if (!ZBarrierSet::barrier_needed(decorators, type)) {
// Barrier not needed
BarrierSetAssembler::load_at(masm, decorators, type, dst, src, tmp1, tmp2);
return;
}
assert_different_registers(tmp1, tmp2, src.base(), noreg);
assert_different_registers(tmp1, tmp2, src.index());
assert_different_registers(tmp1, tmp2, dst, noreg);
assert_different_registers(tmp2, rscratch1);
Label done;
Label uncolor;
// Load bad mask into scratch register.
const bool on_non_strong =
(decorators & ON_WEAK_OOP_REF) != 0 ||
(decorators & ON_PHANTOM_OOP_REF) != 0;
if (on_non_strong) {
__ ldr(tmp1, mark_bad_mask_from_thread(rthread));
} else {
__ ldr(tmp1, load_bad_mask_from_thread(rthread));
}
__ lea(tmp2, src);
__ ldr(dst, tmp2);
// Test reference against bad mask. If mask bad, then we need to fix it up.
__ tst(dst, tmp1);
__ br(Assembler::EQ, uncolor);
{
// Call VM
ZRuntimeCallSpill rcs(masm, dst);
if (c_rarg0 != dst) {
__ mov(c_rarg0, dst);
}
__ mov(c_rarg1, tmp2);
__ call_VM_leaf(ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_addr(decorators), 2);
}
// Slow-path has already uncolored
__ b(done);
__ bind(uncolor);
// Remove the color bits
__ lsr(dst, dst, ZPointerLoadShift);
__ bind(done);
}
void ZBarrierSetAssembler::store_barrier_fast(MacroAssembler* masm,
Address ref_addr,
Register rnew_zaddress,
Register rnew_zpointer,
Register rtmp,
bool in_nmethod,
bool is_atomic,
Label& medium_path,
Label& medium_path_continuation) const {
assert_different_registers(ref_addr.base(), rnew_zpointer, rtmp);
assert_different_registers(ref_addr.index(), rnew_zpointer, rtmp);
assert_different_registers(rnew_zaddress, rnew_zpointer, rtmp);
if (in_nmethod) {
if (is_atomic) {
__ ldrh(rtmp, ref_addr);
// Atomic operations must ensure that the contents of memory are store-good before
// an atomic operation can execute.
// A not relocatable object could have spurious raw null pointers in its fields after
// getting promoted to the old generation.
__ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatStoreGoodBeforeMov);
__ movzw(rnew_zpointer, barrier_Relocation::unpatched);
__ cmpw(rtmp, rnew_zpointer);
} else {
__ ldr(rtmp, ref_addr);
// Stores on relocatable objects never need to deal with raw null pointers in fields.
// Raw null pointers may only exist in the young generation, as they get pruned when
// the object is relocated to old. And no pre-write barrier needs to perform any action
// in the young generation.
__ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatStoreBadBeforeMov);
__ movzw(rnew_zpointer, barrier_Relocation::unpatched);
__ tst(rtmp, rnew_zpointer);
}
__ br(Assembler::NE, medium_path);
__ bind(medium_path_continuation);
assert_different_registers(rnew_zaddress, rnew_zpointer);
__ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatStoreGoodBeforeMov);
__ movzw(rnew_zpointer, barrier_Relocation::unpatched);
__ orr(rnew_zpointer, rnew_zpointer, rnew_zaddress, Assembler::LSL, ZPointerLoadShift);
} else {
assert(!is_atomic, "atomics outside of nmethods not supported");
__ lea(rtmp, ref_addr);
__ ldr(rtmp, rtmp);
__ ldr(rnew_zpointer, Address(rthread, ZThreadLocalData::store_bad_mask_offset()));
__ tst(rtmp, rnew_zpointer);
__ br(Assembler::NE, medium_path);
__ bind(medium_path_continuation);
if (rnew_zaddress == noreg) {
__ eor(rnew_zpointer, rnew_zpointer, rnew_zpointer);
} else {
__ mov(rnew_zpointer, rnew_zaddress);
}
// Load the current good shift, and add the color bits
__ lsl(rnew_zpointer, rnew_zpointer, ZPointerLoadShift);
__ ldr(rtmp, Address(rthread, ZThreadLocalData::store_good_mask_offset()));
__ orr(rnew_zpointer, rnew_zpointer, rtmp);
}
}
static void store_barrier_buffer_add(MacroAssembler* masm,
Address ref_addr,
Register tmp1,
Register tmp2,
Label& slow_path) {
Address buffer(rthread, ZThreadLocalData::store_barrier_buffer_offset());
assert_different_registers(ref_addr.base(), ref_addr.index(), tmp1, tmp2);
__ ldr(tmp1, buffer);
// Combined pointer bump and check if the buffer is disabled or full
__ ldr(tmp2, Address(tmp1, ZStoreBarrierBuffer::current_offset()));
__ cmp(tmp2, (uint8_t)0);
__ br(Assembler::EQ, slow_path);
// Bump the pointer
__ sub(tmp2, tmp2, sizeof(ZStoreBarrierEntry));
__ str(tmp2, Address(tmp1, ZStoreBarrierBuffer::current_offset()));
// Compute the buffer entry address
__ lea(tmp2, Address(tmp2, ZStoreBarrierBuffer::buffer_offset()));
__ add(tmp2, tmp2, tmp1);
// Compute and log the store address
__ lea(tmp1, ref_addr);
__ str(tmp1, Address(tmp2, in_bytes(ZStoreBarrierEntry::p_offset())));
// Load and log the prev value
__ ldr(tmp1, tmp1);
__ str(tmp1, Address(tmp2, in_bytes(ZStoreBarrierEntry::prev_offset())));
}
void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm,
Address ref_addr,
Register rtmp1,
Register rtmp2,
Register rtmp3,
bool is_native,
bool is_atomic,
Label& medium_path_continuation,
Label& slow_path,
Label& slow_path_continuation) const {
assert_different_registers(ref_addr.base(), ref_addr.index(), rtmp1, rtmp2);
// The reason to end up in the medium path is that the pre-value was not 'good'.
if (is_native) {
__ b(slow_path);
__ bind(slow_path_continuation);
__ b(medium_path_continuation);
} else if (is_atomic) {
// Atomic accesses can get to the medium fast path because the value was a
// raw null value. If it was not null, then there is no doubt we need to take a slow path.
__ lea(rtmp2, ref_addr);
__ ldr(rtmp1, rtmp2);
__ cbnz(rtmp1, slow_path);
// If we get this far, we know there is a young raw null value in the field.
__ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatStoreGoodBeforeMov);
__ movzw(rtmp1, barrier_Relocation::unpatched);
__ cmpxchg(rtmp2, zr, rtmp1,
Assembler::xword,
false /* acquire */, false /* release */, true /* weak */,
rtmp3);
__ br(Assembler::NE, slow_path);
__ bind(slow_path_continuation);
__ b(medium_path_continuation);
} else {
// A non-atomic relocatable object won't get to the medium fast path due to a
// raw null in the young generation. We only get here because the field is bad.
// In this path we don't need any self healing, so we can avoid a runtime call
// most of the time by buffering the store barrier to be applied lazily.
store_barrier_buffer_add(masm,
ref_addr,
rtmp1,
rtmp2,
slow_path);
__ bind(slow_path_continuation);
__ b(medium_path_continuation);
}
}
void ZBarrierSetAssembler::store_at(MacroAssembler* masm,
DecoratorSet decorators,
BasicType type,
Address dst,
Register val,
Register tmp1,
Register tmp2,
Register tmp3) {
if (!ZBarrierSet::barrier_needed(decorators, type)) {
BarrierSetAssembler::store_at(masm, decorators, type, dst, val, tmp1, tmp2, tmp3);
return;
}
bool dest_uninitialized = (decorators & IS_DEST_UNINITIALIZED) != 0;
assert_different_registers(val, tmp1, dst.base(), dst.index());
if (dest_uninitialized) {
if (val == noreg) {
__ eor(tmp1, tmp1, tmp1);
} else {
__ mov(tmp1, val);
}
// Add the color bits
__ lsl(tmp1, tmp1, ZPointerLoadShift);
__ ldr(tmp2, Address(rthread, ZThreadLocalData::store_good_mask_offset()));
__ orr(tmp1, tmp2, tmp1);
} else {
Label done;
Label medium;
Label medium_continuation;
Label slow;
Label slow_continuation;
store_barrier_fast(masm, dst, val, tmp1, tmp2, false, false, medium, medium_continuation);
__ b(done);
__ bind(medium);
store_barrier_medium(masm,
dst,
tmp1,
tmp2,
noreg /* tmp3 */,
false /* is_native */,
false /* is_atomic */,
medium_continuation,
slow,
slow_continuation);
__ bind(slow);
{
// Call VM
ZRuntimeCallSpill rcs(masm, noreg);
__ lea(c_rarg0, dst);
__ MacroAssembler::call_VM_leaf(ZBarrierSetRuntime::store_barrier_on_oop_field_without_healing_addr(), 1);
}
__ b(slow_continuation);
__ bind(done);
}
// Store value
BarrierSetAssembler::store_at(masm, decorators, type, dst, tmp1, tmp2, tmp3, noreg);
}
static FloatRegister z_copy_load_bad_vreg = v17;
static FloatRegister z_copy_store_good_vreg = v18;
static FloatRegister z_copy_store_bad_vreg = v19;
static void load_wide_arraycopy_masks(MacroAssembler* masm) {
__ lea(rscratch1, ExternalAddress((address)&ZPointerVectorLoadBadMask));
__ ldrq(z_copy_load_bad_vreg, Address(rscratch1, 0));
__ lea(rscratch1, ExternalAddress((address)&ZPointerVectorStoreBadMask));
__ ldrq(z_copy_store_bad_vreg, Address(rscratch1, 0));
__ lea(rscratch1, ExternalAddress((address)&ZPointerVectorStoreGoodMask));
__ ldrq(z_copy_store_good_vreg, Address(rscratch1, 0));
}
class ZCopyRuntimeCallSpill {
private:
MacroAssembler* _masm;
Register _result;
void save() {
MacroAssembler* masm = _masm;
__ enter(true /* strip_ret_addr */);
if (_result != noreg) {
__ push(__ call_clobbered_gp_registers() - RegSet::of(_result), sp);
} else {
__ push(__ call_clobbered_gp_registers(), sp);
}
int neonSize = wordSize * 2;
__ sub(sp, sp, 4 * neonSize);
__ st1(v0, v1, v2, v3, Assembler::T16B, Address(sp, 0));
__ sub(sp, sp, 4 * neonSize);
__ st1(v4, v5, v6, v7, Assembler::T16B, Address(sp, 0));
__ sub(sp, sp, 4 * neonSize);
__ st1(v16, v17, v18, v19, Assembler::T16B, Address(sp, 0));
}
void restore() {
MacroAssembler* masm = _masm;
int neonSize = wordSize * 2;
__ ld1(v16, v17, v18, v19, Assembler::T16B, Address(sp, 0));
__ add(sp, sp, 4 * neonSize);
__ ld1(v4, v5, v6, v7, Assembler::T16B, Address(sp, 0));
__ add(sp, sp, 4 * neonSize);
__ ld1(v0, v1, v2, v3, Assembler::T16B, Address(sp, 0));
__ add(sp, sp, 4 * neonSize);
if (_result != noreg) {
if (_result != r0) {
__ mov(_result, r0);
}
__ pop(__ call_clobbered_gp_registers() - RegSet::of(_result), sp);
} else {
__ pop(__ call_clobbered_gp_registers(), sp);
}
__ leave();
}
public:
ZCopyRuntimeCallSpill(MacroAssembler* masm, Register result)
: _masm(masm),
_result(result) {
save();
}
~ZCopyRuntimeCallSpill() {
restore();
}
};
void ZBarrierSetAssembler::arraycopy_prologue(MacroAssembler* masm,
DecoratorSet decorators,
bool is_oop,
Register src,
Register dst,
Register count,
RegSet saved_regs) {
if (!is_oop) {
// Barrier not needed
return;
}
BLOCK_COMMENT("ZBarrierSetAssembler::arraycopy_prologue {");
load_wide_arraycopy_masks(masm);
BLOCK_COMMENT("} ZBarrierSetAssembler::arraycopy_prologue");
}
static void copy_load_barrier(MacroAssembler* masm,
Register ref,
Address src,
Register tmp) {
Label done;
__ ldr(tmp, Address(rthread, ZThreadLocalData::load_bad_mask_offset()));
// Test reference against bad mask. If mask bad, then we need to fix it up.
__ tst(ref, tmp);
__ br(Assembler::EQ, done);
{
// Call VM
ZCopyRuntimeCallSpill rcs(masm, ref);
__ lea(c_rarg1, src);
if (c_rarg0 != ref) {
__ mov(c_rarg0, ref);
}
__ call_VM_leaf(ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_addr(IN_HEAP | ON_STRONG_OOP_REF), 2);
}
// Slow-path has uncolored; revert
__ lsl(ref, ref, ZPointerLoadShift);
__ bind(done);
}
static void copy_load_barrier(MacroAssembler* masm,
FloatRegister ref,
Address src,
Register tmp1,
Register tmp2,
FloatRegister vec_tmp) {
Label done;
// Test reference against bad mask. If mask bad, then we need to fix it up.
__ andr(vec_tmp, Assembler::T16B, ref, z_copy_load_bad_vreg);
__ umaxv(vec_tmp, Assembler::T16B, vec_tmp);
__ fcmpd(vec_tmp, 0.0);
__ br(Assembler::EQ, done);
__ umov(tmp2, ref, Assembler::D, 0);
copy_load_barrier(masm, tmp2, Address(src.base(), src.offset() + 0), tmp1);
__ mov(ref, __ D, 0, tmp2);
__ umov(tmp2, ref, Assembler::D, 1);
copy_load_barrier(masm, tmp2, Address(src.base(), src.offset() + 8), tmp1);
__ mov(ref, __ D, 1, tmp2);
__ bind(done);
}
static void copy_store_barrier(MacroAssembler* masm,
Register pre_ref,
Register new_ref,
Address src,
Register tmp1,
Register tmp2) {
Label done;
Label slow;
// Test reference against bad mask. If mask bad, then we need to fix it up.
__ ldr(tmp1, Address(rthread, ZThreadLocalData::store_bad_mask_offset()));
__ tst(pre_ref, tmp1);
__ br(Assembler::EQ, done);
store_barrier_buffer_add(masm, src, tmp1, tmp2, slow);
__ b(done);
__ bind(slow);
{
// Call VM
ZCopyRuntimeCallSpill rcs(masm, noreg);
__ lea(c_rarg0, src);
__ call_VM_leaf(ZBarrierSetRuntime::store_barrier_on_oop_field_without_healing_addr(), 1);
}
__ bind(done);
if (new_ref != noreg) {
// Set store-good color, replacing whatever color was there before
__ ldr(tmp1, Address(rthread, ZThreadLocalData::store_good_mask_offset()));
__ bfi(new_ref, tmp1, 0, 16);
}
}
static void copy_store_barrier(MacroAssembler* masm,
FloatRegister pre_ref,
FloatRegister new_ref,
Address src,
Register tmp1,
Register tmp2,
Register tmp3,
FloatRegister vec_tmp) {
Label done;
// Test reference against bad mask. If mask bad, then we need to fix it up.
__ andr(vec_tmp, Assembler::T16B, pre_ref, z_copy_store_bad_vreg);
__ umaxv(vec_tmp, Assembler::T16B, vec_tmp);
__ fcmpd(vec_tmp, 0.0);
__ br(Assembler::EQ, done);
// Extract the 2 oops from the pre_ref vector register
__ umov(tmp2, pre_ref, Assembler::D, 0);
copy_store_barrier(masm, tmp2, noreg, Address(src.base(), src.offset() + 0), tmp1, tmp3);
__ umov(tmp2, pre_ref, Assembler::D, 1);
copy_store_barrier(masm, tmp2, noreg, Address(src.base(), src.offset() + 8), tmp1, tmp3);
__ bind(done);
// Remove any bad colors
__ bic(new_ref, Assembler::T16B, new_ref, z_copy_store_bad_vreg);
// Add good colors
__ orr(new_ref, Assembler::T16B, new_ref, z_copy_store_good_vreg);
}
class ZAdjustAddress {
private:
MacroAssembler* _masm;
Address _addr;
int _pre_adjustment;
int _post_adjustment;
void pre() {
if (_pre_adjustment != 0) {
_masm->add(_addr.base(), _addr.base(), _addr.offset());
}
}
void post() {
if (_post_adjustment != 0) {
_masm->add(_addr.base(), _addr.base(), _addr.offset());
}
}
public:
ZAdjustAddress(MacroAssembler* masm, Address addr)
: _masm(masm),
_addr(addr),
_pre_adjustment(addr.getMode() == Address::pre ? addr.offset() : 0),
_post_adjustment(addr.getMode() == Address::post ? addr.offset() : 0) {
pre();
}
~ZAdjustAddress() {
post();
}
Address address() {
if (_pre_adjustment != 0 || _post_adjustment != 0) {
return Address(_addr.base(), 0);
} else {
return Address(_addr.base(), _addr.offset());
}
}
};
void ZBarrierSetAssembler::copy_load_at(MacroAssembler* masm,
DecoratorSet decorators,
BasicType type,
size_t bytes,
Register dst1,
Register dst2,
Address src,
Register tmp) {
if (!is_reference_type(type)) {
BarrierSetAssembler::copy_load_at(masm, decorators, type, bytes, dst1, dst2, src, noreg);
return;
}
ZAdjustAddress adjust(masm, src);
src = adjust.address();
BarrierSetAssembler::copy_load_at(masm, decorators, type, bytes, dst1, dst2, src, noreg);
if (bytes == 8) {
copy_load_barrier(masm, dst1, src, tmp);
} else if (bytes == 16) {
copy_load_barrier(masm, dst1, Address(src.base(), src.offset() + 0), tmp);
copy_load_barrier(masm, dst2, Address(src.base(), src.offset() + 8), tmp);
} else {
ShouldNotReachHere();
}
if ((decorators & ARRAYCOPY_CHECKCAST) != 0) {
__ lsr(dst1, dst1, ZPointerLoadShift);
}
}
void ZBarrierSetAssembler::copy_store_at(MacroAssembler* masm,
DecoratorSet decorators,
BasicType type,
size_t bytes,
Address dst,
Register src1,
Register src2,
Register tmp1,
Register tmp2,
Register tmp3) {
if (!is_reference_type(type)) {
BarrierSetAssembler::copy_store_at(masm, decorators, type, bytes, dst, src1, src2, noreg, noreg, noreg);
return;
}
ZAdjustAddress adjust(masm, dst);
dst = adjust.address();
if ((decorators & ARRAYCOPY_CHECKCAST) != 0) {
__ lsl(src1, src1, ZPointerLoadShift);
}
bool is_dest_uninitialized = (decorators & IS_DEST_UNINITIALIZED) != 0;
if (is_dest_uninitialized) {
__ ldr(tmp1, Address(rthread, ZThreadLocalData::store_good_mask_offset()));
if (bytes == 8) {
__ bfi(src1, tmp1, 0, 16);
} else if (bytes == 16) {
__ bfi(src1, tmp1, 0, 16);
__ bfi(src2, tmp1, 0, 16);
} else {
ShouldNotReachHere();
}
} else {
// Store barrier pre values and color new values
if (bytes == 8) {
__ ldr(tmp1, dst);
copy_store_barrier(masm, tmp1, src1, dst, tmp2, tmp3);
} else if (bytes == 16) {
Address dst1(dst.base(), dst.offset() + 0);
Address dst2(dst.base(), dst.offset() + 8);
__ ldr(tmp1, dst1);
copy_store_barrier(masm, tmp1, src1, dst1, tmp2, tmp3);
__ ldr(tmp1, dst2);
copy_store_barrier(masm, tmp1, src2, dst2, tmp2, tmp3);
} else {
ShouldNotReachHere();
}
}
// Store new values
BarrierSetAssembler::copy_store_at(masm, decorators, type, bytes, dst, src1, src2, noreg, noreg, noreg);
}
void ZBarrierSetAssembler::copy_load_at(MacroAssembler* masm,
DecoratorSet decorators,
BasicType type,
size_t bytes,
FloatRegister dst1,
FloatRegister dst2,
Address src,
Register tmp1,
Register tmp2,
FloatRegister vec_tmp) {
if (!is_reference_type(type)) {
BarrierSetAssembler::copy_load_at(masm, decorators, type, bytes, dst1, dst2, src, noreg, noreg, fnoreg);
return;
}
ZAdjustAddress adjust(masm, src);
src = adjust.address();
BarrierSetAssembler::copy_load_at(masm, decorators, type, bytes, dst1, dst2, src, noreg, noreg, fnoreg);
if (bytes == 32) {
copy_load_barrier(masm, dst1, Address(src.base(), src.offset() + 0), tmp1, tmp2, vec_tmp);
copy_load_barrier(masm, dst2, Address(src.base(), src.offset() + 16), tmp1, tmp2, vec_tmp);
} else {
ShouldNotReachHere();
}
}
void ZBarrierSetAssembler::copy_store_at(MacroAssembler* masm,
DecoratorSet decorators,
BasicType type,
size_t bytes,
Address dst,
FloatRegister src1,
FloatRegister src2,
Register tmp1,
Register tmp2,
Register tmp3,
FloatRegister vec_tmp1,
FloatRegister vec_tmp2,
FloatRegister vec_tmp3) {
if (!is_reference_type(type)) {
BarrierSetAssembler::copy_store_at(masm, decorators, type, bytes, dst, src1, src2, noreg, noreg, noreg, fnoreg, fnoreg, fnoreg);
return;
}
bool is_dest_uninitialized = (decorators & IS_DEST_UNINITIALIZED) != 0;
ZAdjustAddress adjust(masm, dst);
dst = adjust.address();
if (is_dest_uninitialized) {
if (bytes == 32) {
__ bic(src1, Assembler::T16B, src1, z_copy_store_bad_vreg);
__ orr(src1, Assembler::T16B, src1, z_copy_store_good_vreg);
__ bic(src2, Assembler::T16B, src2, z_copy_store_bad_vreg);
__ orr(src2, Assembler::T16B, src2, z_copy_store_good_vreg);
} else {
ShouldNotReachHere();
}
} else {
// Load pre values
BarrierSetAssembler::copy_load_at(masm, decorators, type, bytes, vec_tmp1, vec_tmp2, dst, noreg, noreg, fnoreg);
// Store barrier pre values and color new values
if (bytes == 32) {
copy_store_barrier(masm, vec_tmp1, src1, Address(dst.base(), dst.offset() + 0), tmp1, tmp2, tmp3, vec_tmp3);
copy_store_barrier(masm, vec_tmp2, src2, Address(dst.base(), dst.offset() + 16), tmp1, tmp2, tmp3, vec_tmp3);
} else {
ShouldNotReachHere();
}
}
// Store new values
BarrierSetAssembler::copy_store_at(masm, decorators, type, bytes, dst, src1, src2, noreg, noreg, noreg, fnoreg, fnoreg, fnoreg);
}
void ZBarrierSetAssembler::try_resolve_jobject_in_native(MacroAssembler* masm,
Register jni_env,
Register robj,
Register tmp,
Label& slowpath) {
BLOCK_COMMENT("ZBarrierSetAssembler::try_resolve_jobject_in_native {");
Label done, tagged, weak_tagged, uncolor;
// Test for tag
__ tst(robj, JNIHandles::tag_mask);
__ br(Assembler::NE, tagged);
// Resolve local handle
__ ldr(robj, robj);
__ b(done);
__ bind(tagged);
// Test for weak tag
__ tst(robj, JNIHandles::TypeTag::weak_global);
__ br(Assembler::NE, weak_tagged);
// Resolve global handle
__ ldr(robj, Address(robj, -JNIHandles::TypeTag::global));
__ lea(tmp, load_bad_mask_from_jni_env(jni_env));
__ ldr(tmp, tmp);
__ tst(robj, tmp);
__ br(Assembler::NE, slowpath);
__ b(uncolor);
__ bind(weak_tagged);
// Resolve weak handle
__ ldr(robj, Address(robj, -JNIHandles::TypeTag::weak_global));
__ lea(tmp, mark_bad_mask_from_jni_env(jni_env));
__ ldr(tmp, tmp);
__ tst(robj, tmp);
__ br(Assembler::NE, slowpath);
__ bind(uncolor);
// Uncolor
__ lsr(robj, robj, ZPointerLoadShift);
__ bind(done);
BLOCK_COMMENT("} ZBarrierSetAssembler::try_resolve_jobject_in_native");
}
static uint16_t patch_barrier_relocation_value(int format) {
switch (format) {
case ZBarrierRelocationFormatLoadGoodBeforeTbX:
return (uint16_t)exact_log2(ZPointerRemapped);
case ZBarrierRelocationFormatMarkBadBeforeMov:
return (uint16_t)ZPointerMarkBadMask;
case ZBarrierRelocationFormatStoreGoodBeforeMov:
return (uint16_t)ZPointerStoreGoodMask;
case ZBarrierRelocationFormatStoreBadBeforeMov:
return (uint16_t)ZPointerStoreBadMask;
default:
ShouldNotReachHere();
return 0;
}
}
static void change_immediate(uint32_t& instr, uint32_t imm, uint32_t start, uint32_t end) {
uint32_t imm_mask = ((1u << start) - 1u) ^ ((1u << (end + 1)) - 1u);
instr &= ~imm_mask;
instr |= imm << start;
}
void ZBarrierSetAssembler::patch_barrier_relocation(address addr, int format) {
const uint16_t value = patch_barrier_relocation_value(format);
uint32_t* const patch_addr = (uint32_t*)addr;
switch (format) {
case ZBarrierRelocationFormatLoadGoodBeforeTbX:
change_immediate(*patch_addr, value, 19, 23);
break;
case ZBarrierRelocationFormatStoreGoodBeforeMov:
case ZBarrierRelocationFormatMarkBadBeforeMov:
case ZBarrierRelocationFormatStoreBadBeforeMov:
change_immediate(*patch_addr, value, 5, 20);
break;
default:
ShouldNotReachHere();
}
OrderAccess::fence();
ICache::invalidate_word((address)patch_addr);
}
#ifdef COMPILER1
#undef __
#define __ ce->masm()->
static void z_uncolor(LIR_Assembler* ce, LIR_Opr ref) {
__ lsr(ref->as_register(), ref->as_register(), ZPointerLoadShift);
}
static void z_color(LIR_Assembler* ce, LIR_Opr ref) {
__ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatStoreGoodBeforeMov);
__ movzw(rscratch2, barrier_Relocation::unpatched);
__ orr(ref->as_register(), rscratch2, ref->as_register(), Assembler::LSL, ZPointerLoadShift);
}
void ZBarrierSetAssembler::generate_c1_uncolor(LIR_Assembler* ce, LIR_Opr ref) const {
z_uncolor(ce, ref);
}
void ZBarrierSetAssembler::generate_c1_color(LIR_Assembler* ce, LIR_Opr ref) const {
z_color(ce, ref);
}
void ZBarrierSetAssembler::generate_c1_load_barrier(LIR_Assembler* ce,
LIR_Opr ref,
ZLoadBarrierStubC1* stub,
bool on_non_strong) const {
if (on_non_strong) {
// Test against MarkBad mask
assert_different_registers(rscratch1, rthread, ref->as_register());
__ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatMarkBadBeforeMov);
__ movzw(rscratch1, barrier_Relocation::unpatched);
__ tst(ref->as_register(), rscratch1);
__ br(Assembler::NE, *stub->entry());
z_uncolor(ce, ref);
} else {
Label good;
__ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatLoadGoodBeforeTbX);
__ tbz(ref->as_register(), barrier_Relocation::unpatched, good);
__ b(*stub->entry());
__ bind(good);
z_uncolor(ce, ref);
}
__ bind(*stub->continuation());
}
void ZBarrierSetAssembler::generate_c1_load_barrier_stub(LIR_Assembler* ce,
ZLoadBarrierStubC1* stub) const {
// Stub entry
__ bind(*stub->entry());
Register ref = stub->ref()->as_register();
Register ref_addr = noreg;
Register tmp = noreg;
if (stub->tmp()->is_valid()) {
// Load address into tmp register
ce->leal(stub->ref_addr(), stub->tmp());
ref_addr = tmp = stub->tmp()->as_pointer_register();
} else {
// Address already in register
ref_addr = stub->ref_addr()->as_address_ptr()->base()->as_pointer_register();
}
assert_different_registers(ref, ref_addr, noreg);
// Save r0 unless it is the result or tmp register
// Set up SP to accommodate parameters and maybe r0..
if (ref != r0 && tmp != r0) {
__ sub(sp, sp, 32);
__ str(r0, Address(sp, 16));
} else {
__ sub(sp, sp, 16);
}
// Setup arguments and call runtime stub
ce->store_parameter(ref_addr, 1);
ce->store_parameter(ref, 0);
__ far_call(stub->runtime_stub());
// Verify result
__ verify_oop(r0);
// Move result into place
if (ref != r0) {
__ mov(ref, r0);
}
// Restore r0 unless it is the result or tmp register
if (ref != r0 && tmp != r0) {
__ ldr(r0, Address(sp, 16));
__ add(sp, sp, 32);
} else {
__ add(sp, sp, 16);
}
// Stub exit
__ b(*stub->continuation());
}
void ZBarrierSetAssembler::generate_c1_store_barrier(LIR_Assembler* ce,
LIR_Address* addr,
LIR_Opr new_zaddress,
LIR_Opr new_zpointer,
ZStoreBarrierStubC1* stub) const {
Register rnew_zaddress = new_zaddress->as_register();
Register rnew_zpointer = new_zpointer->as_register();
store_barrier_fast(ce->masm(),
ce->as_Address(addr),
rnew_zaddress,
rnew_zpointer,
rscratch2,
true,
stub->is_atomic(),
*stub->entry(),
*stub->continuation());
}
void ZBarrierSetAssembler::generate_c1_store_barrier_stub(LIR_Assembler* ce,
ZStoreBarrierStubC1* stub) const {
// Stub entry
__ bind(*stub->entry());
Label slow;
Label slow_continuation;
store_barrier_medium(ce->masm(),
ce->as_Address(stub->ref_addr()->as_address_ptr()),
rscratch2,
stub->new_zpointer()->as_register(),
rscratch1,
false /* is_native */,
stub->is_atomic(),
*stub->continuation(),
slow,
slow_continuation);
__ bind(slow);
__ lea(stub->new_zpointer()->as_register(), ce->as_Address(stub->ref_addr()->as_address_ptr()));
__ sub(sp, sp, 16);
// Setup arguments and call runtime stub
assert(stub->new_zpointer()->is_valid(), "invariant");
ce->store_parameter(stub->new_zpointer()->as_register(), 0);
__ far_call(stub->runtime_stub());
__ add(sp, sp, 16);
// Stub exit
__ b(slow_continuation);
}
#undef __
#define __ sasm->
void ZBarrierSetAssembler::generate_c1_load_barrier_runtime_stub(StubAssembler* sasm,
DecoratorSet decorators) const {
__ prologue("zgc_load_barrier stub", false);
__ push_call_clobbered_registers_except(RegSet::of(r0));
// Setup arguments
__ load_parameter(0, c_rarg0);
__ load_parameter(1, c_rarg1);
__ call_VM_leaf(ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_addr(decorators), 2);
__ pop_call_clobbered_registers_except(RegSet::of(r0));
__ epilogue();
}
void ZBarrierSetAssembler::generate_c1_store_barrier_runtime_stub(StubAssembler* sasm,
bool self_healing) const {
__ prologue("zgc_store_barrier stub", false);
__ push_call_clobbered_registers();
// Setup arguments
__ load_parameter(0, c_rarg0);
if (self_healing) {
__ call_VM_leaf(ZBarrierSetRuntime::store_barrier_on_oop_field_with_healing_addr(), 1);
} else {
__ call_VM_leaf(ZBarrierSetRuntime::store_barrier_on_oop_field_without_healing_addr(), 1);
}
__ pop_call_clobbered_registers();
__ epilogue();
}
#endif // COMPILER1
#ifdef COMPILER2
#undef __
#define __ _masm->
class ZSetupArguments {
private:
MacroAssembler* const _masm;
const Register _ref;
const Address _ref_addr;
public:
ZSetupArguments(MacroAssembler* masm, ZLoadBarrierStubC2* stub)
: _masm(masm),
_ref(stub->ref()),
_ref_addr(stub->ref_addr()) {
// Setup arguments
if (_ref_addr.base() == noreg) {
// No self healing
if (_ref != c_rarg0) {
__ mov(c_rarg0, _ref);
}
__ mov(c_rarg1, 0);
} else {
// Self healing
if (_ref == c_rarg0) {
// _ref is already at correct place
__ lea(c_rarg1, _ref_addr);
} else if (_ref != c_rarg1) {
// _ref is in wrong place, but not in c_rarg1, so fix it first
__ lea(c_rarg1, _ref_addr);
__ mov(c_rarg0, _ref);
} else if (_ref_addr.base() != c_rarg0 && _ref_addr.index() != c_rarg0) {
assert(_ref == c_rarg1, "Mov ref first, vacating c_rarg0");
__ mov(c_rarg0, _ref);
__ lea(c_rarg1, _ref_addr);
} else {
assert(_ref == c_rarg1, "Need to vacate c_rarg1 and _ref_addr is using c_rarg0");
if (_ref_addr.base() == c_rarg0 || _ref_addr.index() == c_rarg0) {
__ mov(rscratch2, c_rarg1);
__ lea(c_rarg1, _ref_addr);
__ mov(c_rarg0, rscratch2);
} else {
ShouldNotReachHere();
}
}
}
}
~ZSetupArguments() {
// Transfer result
if (_ref != r0) {
__ mov(_ref, r0);
}
}
};
#undef __
#define __ masm->
void ZBarrierSetAssembler::generate_c2_load_barrier_stub(MacroAssembler* masm, ZLoadBarrierStubC2* stub) const {
Assembler::InlineSkippedInstructionsCounter skipped_counter(masm);
BLOCK_COMMENT("ZLoadBarrierStubC2");
// Stub entry
if (!Compile::current()->output()->in_scratch_emit_size()) {
__ bind(*stub->entry());
}
{
SaveLiveRegisters save_live_registers(masm, stub);
ZSetupArguments setup_arguments(masm, stub);
__ mov(rscratch1, stub->slow_path());
__ blr(rscratch1);
}
// Stub exit
__ b(*stub->continuation());
}
void ZBarrierSetAssembler::generate_c2_store_barrier_stub(MacroAssembler* masm, ZStoreBarrierStubC2* stub) const {
Assembler::InlineSkippedInstructionsCounter skipped_counter(masm);
BLOCK_COMMENT("ZStoreBarrierStubC2");
// Stub entry
__ bind(*stub->entry());
Label slow;
Label slow_continuation;
store_barrier_medium(masm,
stub->ref_addr(),
stub->new_zpointer(),
rscratch1,
rscratch2,
stub->is_native(),
stub->is_atomic(),
*stub->continuation(),
slow,
slow_continuation);
__ bind(slow);
{
SaveLiveRegisters save_live_registers(masm, stub);
__ lea(c_rarg0, stub->ref_addr());
if (stub->is_native()) {
__ lea(rscratch1, RuntimeAddress(ZBarrierSetRuntime::store_barrier_on_native_oop_field_without_healing_addr()));
} else if (stub->is_atomic()) {
__ lea(rscratch1, RuntimeAddress(ZBarrierSetRuntime::store_barrier_on_oop_field_with_healing_addr()));
} else if (stub->is_nokeepalive()) {
__ lea(rscratch1, RuntimeAddress(ZBarrierSetRuntime::no_keepalive_store_barrier_on_oop_field_without_healing_addr()));
} else {
__ lea(rscratch1, RuntimeAddress(ZBarrierSetRuntime::store_barrier_on_oop_field_without_healing_addr()));
}
__ blr(rscratch1);
}
// Stub exit
__ b(slow_continuation);
}
// Only handles forward branch jumps, target_offset >= branch_offset
static bool aarch64_test_and_branch_reachable(int branch_offset, int target_offset) {
assert(branch_offset >= 0, "branch to stub offsets must be positive");
assert(target_offset >= 0, "offset in stubs section must be positive");
assert(target_offset >= branch_offset, "forward branches only, branch_offset -> target_offset");
const int test_and_branch_delta_limit = 32 * K;
const int test_and_branch_to_trampoline_delta = target_offset - branch_offset;
return test_and_branch_to_trampoline_delta < test_and_branch_delta_limit;
}
ZLoadBarrierStubC2Aarch64::ZLoadBarrierStubC2Aarch64(const MachNode* node, Address ref_addr, Register ref)
: ZLoadBarrierStubC2(node, ref_addr, ref), _test_and_branch_reachable_entry(), _offset(), _deferred_emit(false), _test_and_branch_reachable(false) {}
ZLoadBarrierStubC2Aarch64::ZLoadBarrierStubC2Aarch64(const MachNode* node, Address ref_addr, Register ref, int offset)
: ZLoadBarrierStubC2(node, ref_addr, ref), _test_and_branch_reachable_entry(), _offset(offset), _deferred_emit(false), _test_and_branch_reachable(false) {
PhaseOutput* const output = Compile::current()->output();
if (output->in_scratch_emit_size()) {
return;
}
const int code_size = output->buffer_sizing_data()->_code;
const int offset_code = _offset;
// Assumption that the stub can always be reached from a branch immediate. (128 M Product, 2 M Debug)
// Same assumption is made in z_aarch64.ad
const int trampoline_offset = trampoline_stubs_count() * NativeInstruction::instruction_size;
_test_and_branch_reachable = aarch64_test_and_branch_reachable(offset_code, code_size + trampoline_offset);
if (_test_and_branch_reachable) {
inc_trampoline_stubs_count();
}
}
int ZLoadBarrierStubC2Aarch64::get_stub_size() {
PhaseOutput* const output = Compile::current()->output();
assert(!output->in_scratch_emit_size(), "only used when emitting stubs");
BufferBlob* const blob = output->scratch_buffer_blob();
CodeBuffer cb(blob->content_begin(), (address)output->scratch_locs_memory() - blob->content_begin());
MacroAssembler masm(&cb);
output->set_in_scratch_emit_size(true);
ZLoadBarrierStubC2::emit_code(masm);
output->set_in_scratch_emit_size(false);
return cb.insts_size();
}
ZLoadBarrierStubC2Aarch64* ZLoadBarrierStubC2Aarch64::create(const MachNode* node, Address ref_addr, Register ref) {
ZLoadBarrierStubC2Aarch64* const stub = new (Compile::current()->comp_arena()) ZLoadBarrierStubC2Aarch64(node, ref_addr, ref);
register_stub(stub);
return stub;
}
ZLoadBarrierStubC2Aarch64* ZLoadBarrierStubC2Aarch64::create(const MachNode* node, Address ref_addr, Register ref, int offset) {
ZLoadBarrierStubC2Aarch64* const stub = new (Compile::current()->comp_arena()) ZLoadBarrierStubC2Aarch64(node, ref_addr, ref, offset);
register_stub(stub);
return stub;
}
#undef __
#define __ masm.
void ZLoadBarrierStubC2Aarch64::emit_code(MacroAssembler& masm) {
PhaseOutput* const output = Compile::current()->output();
const int branch_offset = _offset;
const int target_offset = __ offset();
// Deferred emission, emit actual stub
if (_deferred_emit) {
ZLoadBarrierStubC2::emit_code(masm);
return;
}
_deferred_emit = true;
// No trampoline used, defer emission to after trampolines
if (!_test_and_branch_reachable) {
register_stub(this);
return;
}
// Current assumption is that the barrier stubs are the first stubs emitted after the actual code
assert(stubs_start_offset() <= output->buffer_sizing_data()->_code, "stubs are assumed to be emitted directly after code and code_size is a hard limit on where it can start");
__ bind(_test_and_branch_reachable_entry);
// Next branch's offset is unknown, but is > branch_offset
const int next_branch_offset = branch_offset + NativeInstruction::instruction_size;
// If emitting the stub directly does not interfere with emission of the next trampoline then do it to avoid a double jump.
if (aarch64_test_and_branch_reachable(next_branch_offset, target_offset + get_stub_size())) {
// The next potential trampoline will still be reachable even if we emit the whole stub
ZLoadBarrierStubC2::emit_code(masm);
} else {
// Emit trampoline and defer actual stub to the end
assert(aarch64_test_and_branch_reachable(branch_offset, target_offset), "trampoline should be reachable");
__ b(*ZLoadBarrierStubC2::entry());
register_stub(this);
}
}
bool ZLoadBarrierStubC2Aarch64::is_test_and_branch_reachable() {
return _test_and_branch_reachable;
}
Label* ZLoadBarrierStubC2Aarch64::entry() {
if (_test_and_branch_reachable) {
return &_test_and_branch_reachable_entry;
}
return ZBarrierStubC2::entry();
}
ZStoreBarrierStubC2Aarch64::ZStoreBarrierStubC2Aarch64(const MachNode* node, Address ref_addr, Register new_zaddress, Register new_zpointer, bool is_native, bool is_atomic, bool is_nokeepalive)
: ZStoreBarrierStubC2(node, ref_addr, new_zaddress, new_zpointer, is_native, is_atomic, is_nokeepalive), _deferred_emit(false) {}
ZStoreBarrierStubC2Aarch64* ZStoreBarrierStubC2Aarch64::create(const MachNode* node, Address ref_addr, Register new_zaddress, Register new_zpointer, bool is_native, bool is_atomic, bool is_nokeepalive) {
ZStoreBarrierStubC2Aarch64* const stub = new (Compile::current()->comp_arena()) ZStoreBarrierStubC2Aarch64(node, ref_addr, new_zaddress, new_zpointer, is_native, is_atomic, is_nokeepalive);
register_stub(stub);
return stub;
}
void ZStoreBarrierStubC2Aarch64::emit_code(MacroAssembler& masm) {
if (_deferred_emit) {
ZStoreBarrierStubC2::emit_code(masm);
return;
}
// Defer emission of store barriers so that trampolines are emitted first
_deferred_emit = true;
register_stub(this);
}
#undef __
#endif // COMPILER2
#undef __
#define __ masm->
void ZBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2, Label& error) {
// C1 calls verfy_oop in the middle of barriers, before they have been uncolored
// and after being colored. Therefore, we must deal with colored oops as well.
Label done;
Label check_oop;
Label check_zaddress;
int color_bits = ZPointerRemappedShift + ZPointerRemappedBits;
uintptr_t shifted_base_start_mask = (UCONST64(1) << (ZAddressHeapBaseShift + color_bits + 1)) - 1;
uintptr_t shifted_base_end_mask = (UCONST64(1) << (ZAddressHeapBaseShift + 1)) - 1;
uintptr_t shifted_base_mask = shifted_base_start_mask ^ shifted_base_end_mask;
uintptr_t shifted_address_end_mask = (UCONST64(1) << (color_bits + 1)) - 1;
uintptr_t shifted_address_mask = shifted_address_end_mask ^ (uintptr_t)CONST64(-1);
__ get_nzcv(tmp2);
// Check colored null
__ mov(tmp1, shifted_address_mask);
__ tst(tmp1, obj);
__ br(Assembler::EQ, done);
// Check for zpointer
__ mov(tmp1, shifted_base_mask);
__ tst(tmp1, obj);
__ br(Assembler::EQ, check_oop);
// Uncolor presumed zpointer
__ lsr(obj, obj, ZPointerLoadShift);
__ b(check_zaddress);
__ bind(check_oop);
// make sure klass is 'reasonable', which is not zero.
__ load_klass(tmp1, obj); // get klass
__ tst(tmp1, tmp1);
__ br(Assembler::EQ, error); // if klass is null it is broken
__ bind(check_zaddress);
// Check if the oop is in the right area of memory
__ mov(tmp1, (intptr_t) Universe::verify_oop_mask());
__ andr(tmp1, tmp1, obj);
__ mov(obj, (intptr_t) Universe::verify_oop_bits());
__ cmp(tmp1, obj);
__ br(Assembler::NE, error);
__ bind(done);
__ set_nzcv(tmp2);
}
#undef __