__ Bind(GetEntryLabel()); // The source range and destination pointer were initialized before entering the slow-path.
Riscv64Label slow_copy_loop;
__ Bind(&slow_copy_loop);
__ Loadwu(tmp_reg, src_curr_addr, 0);
codegen->MaybeUnpoisonHeapReference(tmp_reg); // TODO: Inline the mark bit check before calling the runtime? // tmp_reg = ReadBarrier::Mark(tmp_reg); // No need to save live registers; it's taken care of by the // entrypoint. Also, there is no need to update the stack mask, // as this runtime call will not trigger a garbage collection. // (See ReadBarrierMarkSlowPathRISCV64::EmitNativeCode for more // explanations.)
int32_t entry_point_offset = ReadBarrierMarkEntrypointOffset(tmp_); // This runtime call does not require a stack map.
codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
codegen->MaybePoisonHeapReference(tmp_reg);
__ Storew(tmp_reg, dst_curr_addr, 0);
__ Addi(src_curr_addr, src_curr_addr, element_size);
__ Addi(dst_curr_addr, dst_curr_addr, element_size);
__ Bne(src_curr_addr, src_stop_addr, &slow_copy_loop);
__ J(GetExitLabel());
}
// The MethodHandle.invokeExact intrinsic sets up arguments to match the target method call. If we // need to go to the slow path, we call art_quick_invoke_polymorphic_with_hidden_receiver, which // expects the MethodHandle object in a0 (in place of the actual ArtMethod). class InvokePolymorphicSlowPathRISCV64 : public SlowPathCodeRISCV64 { public:
InvokePolymorphicSlowPathRISCV64(HInstruction* instruction, XRegister method_handle)
: SlowPathCodeRISCV64(instruction), method_handle_(method_handle) {
DCHECK(instruction->IsInvokePolymorphic());
}
// Check if divisor is zero, bail to managed implementation to handle.
SlowPathCodeRISCV64* slow_path = new (codegen->GetScopedAllocator()) IntrinsicSlowPathRISCV64(invoke);
codegen->AddSlowPath(slow_path);
__ Beqz(divisor, slow_path->GetEntryLabel());
void IntrinsicCodeGeneratorRISCV64::HandleValueOf(HInvoke* invoke, const IntrinsicVisitor::ValueOfInfo& info,
DataType::Type type) {
Riscv64Assembler* assembler = codegen_->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
XRegister out = locations->Out().AsCoreRegister<XRegister>();
ScratchRegisterScope srs(assembler);
XRegister temp = srs.AllocateXRegister(); auto allocate_instance = [&]() {
DCHECK_EQ(out, InvokeRuntimeCallingConvention().GetRegisterAt(0));
codegen_->LoadIntrinsicDeclaringClass(out, invoke);
codegen_->InvokeRuntime(kQuickAllocObjectInitialized, invoke);
CheckEntrypointTypes<kQuickAllocObjectWithChecks, void*, mirror::Class*>();
}; if (invoke->InputAt(0)->IsIntConstant()) {
int32_t value = invoke->InputAt(0)->AsIntConstant()->GetValue(); if (static_cast<uint32_t>(value - info.low) < info.length) { // Just embed the object in the code.
DCHECK_NE(info.value_boot_image_reference, ValueOfInfo::kInvalidReference);
codegen_->LoadBootImageAddress(out, info.value_boot_image_reference);
} else {
DCHECK(locations->CanCall()); // Allocate and initialize a new object. // TODO: If we JIT, we could allocate the object now, and store it in the // JIT object table.
allocate_instance();
__ Li(temp, value);
codegen_->GetInstructionVisitor()->Store(
Location::CoreRegister(temp), out, info.value_offset, type); // Class pointer and `value` final field stores require a barrier before publication.
codegen_->GenerateMemoryBarrier(MemBarrierKind::kStoreStore);
}
} else {
DCHECK(locations->CanCall());
XRegister in = locations->InAt(0).AsCoreRegister<XRegister>();
Riscv64Label allocate, done; // Check bounds of our cache.
__ AddConst32(out, in, -info.low);
__ Li(temp, info.length);
__ Bgeu(out, temp, &allocate); // If the value is within the bounds, load the object directly from the array.
codegen_->LoadBootImageAddress(temp, info.array_data_boot_image_reference);
__ Sh2Add(temp, out, temp);
__ Loadwu(out, temp, 0);
codegen_->MaybeUnpoisonHeapReference(out);
__ J(&done);
__ Bind(&allocate); // Otherwise allocate and initialize a new object.
allocate_instance();
codegen_->GetInstructionVisitor()->Store(
Location::CoreRegister(in), out, info.value_offset, type); // Class pointer and `value` final field stores require a barrier before publication.
codegen_->GenerateMemoryBarrier(MemBarrierKind::kStoreStore);
__ Bind(&done);
}
}
// Emit memory barrier for load-acquire.
codegen_->GenerateMemoryBarrier(MemBarrierKind::kLoadAny);
if (codegen_->EmitReadBarrier()) {
DCHECK(kUseBakerReadBarrier);
Riscv64Label calculate_result;
// If equal to `other`, the loaded reference is final (it cannot be a from-space reference).
__ Beq(out, other, &calculate_result);
// If the GC is not marking, the loaded reference is final.
ScratchRegisterScope srs(assembler);
XRegister tmp = srs.AllocateXRegister();
__ Loadwu(tmp, TR, Thread::IsGcMarkingOffset<kRiscv64PointerSize>().Int32Value());
__ Beqz(tmp, &calculate_result);
// Check if the loaded reference is null.
__ Beqz(out, &calculate_result);
// For correct memory visibility, we need a barrier before loading the lock word to // synchronize with the publishing of `other` by the CC GC. However, as long as the // load-acquire above is implemented as a plain load followed by a barrier (rather // than an atomic load-acquire instruction which synchronizes only with other // instructions on the same memory location), that barrier is sufficient.
// Load the lockword and check if it is a forwarding address.
static_assert(LockWord::kStateShift == 30u);
static_assert(LockWord::kStateForwardingAddress == 3u); // Load the lock word sign-extended. Comparing it to the sign-extended forwarding // address bits as unsigned is the same as comparing both zero-extended.
__ Loadw(tmp, out, monitor_offset); // Materialize sign-extended forwarding address bits. This is a single LUI instruction.
XRegister tmp2 = srs.AllocateXRegister();
__ Li(tmp2, INT64_C(-1) & ~static_cast<int64_t>((1 << LockWord::kStateShift) - 1)); // If we do not have a forwarding address, the loaded reference cannot be the same as `other`, // so we proceed to calculate the result with `out != other`.
__ Bltu(tmp, tmp2, &calculate_result);
// Extract the forwarding address for comparison with `other`. // Note that the high 32 bits shall not be used for the result calculation.
__ Slliw(out, tmp, LockWord::kForwardingAddressShift);
__ Bind(&calculate_result);
}
// Calculate the result `out == other`.
__ Subw(out, out, other);
__ Seqz(out, out);
}
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
// Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, // or directly dispatch for a large constant, or omit slow-path for a small constant or a char.
SlowPathCodeRISCV64* slow_path = nullptr;
HInstruction* code_point = invoke->InputAt(1); if (code_point->IsIntConstant()) { if (static_cast<uint32_t>(code_point->AsIntConstant()->GetValue()) > 0xFFFFU) { // Always needs the slow-path. We could directly dispatch to it, but this case should be // rare, so for simplicity just put the full slow-path down and branch unconditionally.
slow_path = new (codegen->GetScopedAllocator()) IntrinsicSlowPathRISCV64(invoke);
codegen->AddSlowPath(slow_path);
__ J(slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel()); return;
}
} elseif (code_point->GetType() != DataType::Type::kUint16) {
slow_path = new (codegen->GetScopedAllocator()) IntrinsicSlowPathRISCV64(invoke);
codegen->AddSlowPath(slow_path);
ScratchRegisterScope srs(assembler);
XRegister tmp = srs.AllocateXRegister();
__ Srliw(tmp, locations->InAt(1).AsCoreRegister<XRegister>(), 16);
__ Bnez(tmp, slow_path->GetEntryLabel());
}
if (slow_path != nullptr) {
__ Bind(slow_path->GetExitLabel());
}
}
void IntrinsicLocationsBuilderRISCV64::VisitStringIndexOf(HInvoke* invoke) {
LocationSummary* locations = LocationSummary::Create(
allocator_, invoke, LocationSummary::kCallOnMainAndSlowPath, kIntrinsified); // We have a hand-crafted assembly stub that follows the runtime calling convention. So it's // best to align the inputs accordingly.
InvokeRuntimeCallingConvention calling_convention;
locations->SetInAt(0, Location::CoreRegister(calling_convention.GetRegisterAt(0)));
locations->SetInAt(1, Location::CoreRegister(calling_convention.GetRegisterAt(1)));
locations->SetOut(calling_convention.GetReturnLocation(DataType::Type::kInt32));
// Need to send start_index=0.
locations->AddTemp(Location::CoreRegister(calling_convention.GetRegisterAt(2)));
}
void IntrinsicLocationsBuilderRISCV64::VisitStringIndexOfAfter(HInvoke* invoke) {
LocationSummary* locations = LocationSummary::Create(
allocator_, invoke, LocationSummary::kCallOnMainAndSlowPath, kIntrinsified); // We have a hand-crafted assembly stub that follows the runtime calling convention. So it's // best to align the inputs accordingly.
InvokeRuntimeCallingConvention calling_convention;
locations->SetInAt(0, Location::CoreRegister(calling_convention.GetRegisterAt(0)));
locations->SetInAt(1, Location::CoreRegister(calling_convention.GetRegisterAt(1)));
locations->SetInAt(2, Location::CoreRegister(calling_convention.GetRegisterAt(2)));
locations->SetOut(calling_convention.GetReturnLocation(DataType::Type::kInt32));
}
void IntrinsicCodeGeneratorRISCV64::VisitStringNewStringFromChars(HInvoke* invoke) { // No need to emit code checking whether `locations->InAt(2)` is a null // pointer, as callers of the native method // // java.lang.StringFactory.newStringFromChars(int offset, int charCount, char[] data) // // all include a null check on `data` before calling that method.
codegen_->InvokeRuntime(kQuickAllocStringFromChars, invoke);
CheckEntrypointTypes<kQuickAllocStringFromChars, void*, int32_t, int32_t, void*>();
}
staticvoid EmitLoadReserved(Riscv64Assembler* assembler,
DataType::Type type,
XRegister ptr,
XRegister old_value,
AqRl aqrl) { switch (type) { case DataType::Type::kInt32:
__ LrW(old_value, ptr, aqrl); break; case DataType::Type::kReference:
__ LrW(old_value, ptr, aqrl); // TODO(riscv64): The `ZextW()` macro currently emits `SLLI+SRLI` which are from the // base "I" instruction set. When the assembler is updated to use a single-instruction // `ZextW()` macro, either the ADD.UW, or the C.ZEXT.W (16-bit encoding), we need to // rewrite this to avoid these non-"I" instructions. We could, for example, sign-extend // the reference and do the CAS as `Int32`.
__ ZextW(old_value, old_value); break; case DataType::Type::kInt64:
__ LrD(old_value, ptr, aqrl); break; default:
LOG(FATAL) << "Unexpected type: " << type;
UNREACHABLE();
}
}
void IntrinsicLocationsBuilderRISCV64::VisitStringEquals(HInvoke* invoke) {
LocationSummary* locations = LocationSummary::CreateNoCall(allocator_, invoke, kIntrinsified);
locations->SetInAt(0, Location::RequiresCoreRegister());
locations->SetInAt(1, Location::RequiresCoreRegister());
locations->AddTemp(Location::RequiresCoreRegister()); // TODO: If the String.equals() is used only for an immediately following HIf, we can // mark it as emitted-at-use-site and emit branches directly to the appropriate blocks. // Then we shall need an extra temporary register instead of the output register.
locations->SetOut(Location::RequiresCoreRegister(), Location::kOutputOverlap);
}
// Get offsets of count, value, and class fields within a string object. const int32_t count_offset = mirror::String::CountOffset().Int32Value(); const int32_t value_offset = mirror::String::ValueOffset().Int32Value(); const int32_t class_offset = mirror::Object::ClassOffset().Int32Value();
StringEqualsOptimizations optimizations(invoke); if (!optimizations.GetArgumentNotNull()) { // Check if input is null, return false if it is.
__ Beqz(arg, &return_false);
}
// Reference equality check, return true if same reference.
__ Beq(str, arg, &return_true);
if (!optimizations.GetArgumentIsString()) { // Instanceof check for the argument by comparing class fields. // All string objects must have the same type since String cannot be subclassed. // Receiver must be a string object, so its class field is equal to all strings' class fields. // If the argument is a string object, its class field must be equal to receiver's class field. // // As the String class is expected to be non-movable, we can read the class // field from String.equals' arguments without read barriers.
AssertNonMovableStringClass(); // /* HeapReference<Class> */ temp = str->klass_
__ Loadwu(temp, str, class_offset); // /* HeapReference<Class> */ temp1 = arg->klass_
__ Loadwu(temp1, arg, class_offset); // Also, because we use the previously loaded class references only in the // following comparison, we don't need to unpoison them.
__ Bne(temp, temp1, &return_false);
}
// Load `count` fields of this and argument strings.
__ Loadwu(temp, str, count_offset);
__ Loadwu(temp1, arg, count_offset); // Check if `count` fields are equal, return false if they're not. // Also compares the compression style, if differs return false.
__ Bne(temp, temp1, &return_false);
// Assertions that must hold in order to compare strings 8 bytes at a time. // Ok to do this because strings are zero-padded to kObjectAlignment.
DCHECK_ALIGNED(value_offset, 8);
static_assert(IsAligned<8>(kObjectAlignment), "String of odd length is not zero padded");
// Return true if both strings are empty. Even with string compression `count == 0` means empty.
static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u, "Expecting 0=compressed, 1=uncompressed");
__ Beqz(temp, &return_true);
if (mirror::kUseStringCompression) { // For string compression, calculate the number of bytes to compare (not chars). // This could in theory exceed INT32_MAX, so treat temp as unsigned.
__ Andi(temp1, temp, 1); // Extract compression flag.
__ Srliw(temp, temp, 1u); // Extract length.
__ Sllw(temp, temp, temp1); // Calculate number of bytes to compare.
}
// Store offset of string value in preparation for comparison loop
__ Li(temp1, value_offset);
XRegister temp2 = srs.AllocateXRegister(); // Loop to compare strings 8 bytes at a time starting at the front of the string.
__ Bind(&loop);
__ Add(out, str, temp1);
__ Ld(out, out, 0);
__ Add(temp2, arg, temp1);
__ Ld(temp2, temp2, 0);
__ Addi(temp1, temp1, sizeof(uint64_t));
__ Bne(out, temp2, &return_false); // With string compression, we have compared 8 bytes, otherwise 4 chars.
__ Addi(temp, temp, mirror::kUseStringCompression ? -8 : -4);
__ Bgt(temp, Zero, &loop);
// Return true and exit the function. // If loop does not result in returning false, we return true.
__ Bind(&return_true);
__ Li(out, 1);
__ J(&end);
// Return false and exit the function.
__ Bind(&return_false);
__ Li(out, 0);
__ Bind(&end);
}
// The `expected2` is valid only for reference slow path and represents the unmarked old value // from the main path attempt to emit CAS when the marked old value matched `expected`.
DCHECK_IMPLIES(expected2 != kNoXRegister, type == DataType::Type::kReference);
auto [load_aqrl, store_aqrl] = GetLrScAqRl(order);
// repeat: { // old_value = [ptr]; // Load exclusive. // cmp_value = old_value & mask; // Extract relevant bits if applicable. // if (cmp_value != expected && cmp_value != expected2) goto cmp_failure; // store_result = failed([ptr] <- new_value); // Store exclusive. // } // if (strong) { // if (store_result) goto repeat; // Repeat until compare fails or store exclusive succeeds. // } else { // store_result = store_result ^ 1; // Report success as 1, failure as 0. // } // // (If `mask` is not valid, `expected` is compared with `old_value` instead of `cmp_value`.) // (If `expected2` is not valid, the `cmp_value == expected2` part is not emitted.)
// Note: We're using "bare" local branches to enforce that they shall not be expanded // and the scrach register `TMP` shall not be clobbered if taken. Taking the branch to // `cmp_failure` can theoretically clobber `TMP` (if outside the 1 MiB range).
Riscv64Label loop; if (strong) {
__ Bind(&loop);
}
EmitLoadReserved(assembler, type, ptr, old_value, load_aqrl);
XRegister to_store = new_value;
{
ScopedLrScExtensionsRestriction slser(assembler); if (mask != kNoXRegister) {
DCHECK_EQ(expected2, kNoXRegister);
DCHECK_NE(masked, kNoXRegister);
__ And(masked, old_value, mask);
__ Bne(masked, expected, cmp_failure); // The `old_value` does not need to be preserved as the caller shall use `masked` // to return the old value if needed.
to_store = old_value; // TODO(riscv64): We could XOR the old and new value before the loop and use a single XOR here // instead of the XOR+OR. (The `new_value` is either Zero or a temporary we can clobber.)
__ Xor(to_store, old_value, masked);
__ Or(to_store, to_store, new_value);
} elseif (expected2 != kNoXRegister) {
Riscv64Label match2;
__ Beq(old_value, expected2, &match2, /*is_bare=*/ true);
__ Bne(old_value, expected, cmp_failure);
__ Bind(&match2);
} else {
__ Bne(old_value, expected, cmp_failure);
}
}
EmitStoreConditional(assembler, type, ptr, store_result, to_store, store_aqrl); if (strong) {
__ Bnez(store_result, &loop, /*is_bare=*/ true);
} else { // Flip the `store_result` register to indicate success by 1 and failure by 0.
__ Xori(store_result, store_result, 1);
}
}
class ReadBarrierCasSlowPathRISCV64 : public SlowPathCodeRISCV64 { public:
ReadBarrierCasSlowPathRISCV64(HInvoke* invoke,
std::memory_order order, bool strong,
XRegister base,
XRegister offset,
XRegister expected,
XRegister new_value,
XRegister old_value,
XRegister old_value_temp,
XRegister store_result, bool update_old_value,
CodeGeneratorRISCV64* riscv64_codegen)
: SlowPathCodeRISCV64(invoke),
order_(order),
strong_(strong),
base_(base),
offset_(offset),
expected_(expected),
new_value_(new_value),
old_value_(old_value),
old_value_temp_(old_value_temp),
store_result_(store_result),
update_old_value_(update_old_value),
mark_old_value_slow_path_(nullptr),
update_old_value_slow_path_(nullptr) { // We need to add slow paths now, it is too late when emitting slow path code.
Location old_value_loc = Location::CoreRegister(old_value);
Location old_value_temp_loc = Location::CoreRegister(old_value_temp); if (kUseBakerReadBarrier) {
mark_old_value_slow_path_ = riscv64_codegen->AddGcRootBakerBarrierBarrierSlowPath(
invoke, old_value_temp_loc, kBakerReadBarrierTemp); if (update_old_value_) {
update_old_value_slow_path_ = riscv64_codegen->AddGcRootBakerBarrierBarrierSlowPath(
invoke, old_value_loc, kBakerReadBarrierTemp);
}
} else {
Location base_loc = Location::CoreRegister(base);
Location index = Location::CoreRegister(offset);
mark_old_value_slow_path_ = riscv64_codegen->AddReadBarrierSlowPath(
invoke, old_value_temp_loc, old_value_loc, base_loc, /*offset=*/ 0u, index); if (update_old_value_) {
update_old_value_slow_path_ = riscv64_codegen->AddReadBarrierSlowPath(
invoke, old_value_loc, old_value_temp_loc, base_loc, /*offset=*/ 0u, index);
}
}
}
// We return to a different label on success for a strong CAS that does not return old value.
Riscv64Label* GetSuccessExitLabel() { return &success_exit_label_;
}
// Mark the `old_value_` from the main path and compare with `expected_`.
DCHECK(mark_old_value_slow_path_ != nullptr); if (kUseBakerReadBarrier) {
__ Mv(old_value_temp_, old_value_);
riscv64_codegen->EmitBakerReadBarierMarkingCheck(mark_old_value_slow_path_,
Location::CoreRegister(old_value_temp_),
kBakerReadBarrierTemp);
} else {
__ J(mark_old_value_slow_path_->GetEntryLabel());
__ Bind(mark_old_value_slow_path_->GetExitLabel());
}
Riscv64Label move_marked_old_value;
__ Bne(old_value_temp_, expected_, update_old_value_ ? &move_marked_old_value : GetExitLabel());
// The `old_value` we have read did not match `expected` (which is always a to-space // reference) but after the read barrier the marked to-space value matched, so the // `old_value` must be a from-space reference to the same object. Do the same CAS loop // as the main path but check for both `expected` and the unmarked old value // representing the to-space and from-space references for the same object.
// Recalculate the `tmp_ptr` from main path potentially clobbered by the read barrier above // or by an expanded conditional branch (clobbers `TMP` if beyond 1MiB).
__ Add(tmp_ptr, base_, offset_);
Riscv64Label mark_old_value;
GenerateCompareAndSet(riscv64_codegen->GetAssembler(),
DataType::Type::kReference,
order_,
strong_, /*cmp_failure=*/ update_old_value_ ? &mark_old_value : GetExitLabel(),
tmp_ptr,
new_value_, /*old_value=*/ old_value_temp_, /*mask=*/ kNoXRegister, /*masked=*/ kNoXRegister,
store_result,
expected_, /*expected2=*/ old_value_); if (update_old_value_) { // To reach this point, the `old_value_temp_` must be either a from-space or a to-space // reference of the `expected_` object. Update the `old_value_` to the to-space reference.
__ Mv(old_value_, expected_);
} if (!update_old_value_ && strong_) { // Load success value to the result register. // We must jump to the instruction that loads the success value in the main path. // Note that a SC failure in the CAS loop sets the `store_result` to 1, so the main // path must not use the `store_result` as an indication of success.
__ J(GetSuccessExitLabel());
} else {
__ J(GetExitLabel());
}
if (update_old_value_) { // TODO(riscv64): If we initially saw a from-space reference and then saw // a different reference, can the latter be also a from-space reference? // (Shouldn't every reference write store a to-space reference?)
DCHECK(update_old_value_slow_path_ != nullptr);
__ Bind(&mark_old_value); if (kUseBakerReadBarrier) {
__ Mv(old_value_, old_value_temp_);
riscv64_codegen->EmitBakerReadBarierMarkingCheck(update_old_value_slow_path_,
Location::CoreRegister(old_value_),
kBakerReadBarrierTemp);
} else { // Note: We could redirect the `failure` above directly to the entry label and bind // the exit label in the main path, but the main path would need to access the // `update_old_value_slow_path_`. To keep the code simple, keep the extra jumps.
__ J(update_old_value_slow_path_->GetEntryLabel());
__ Bind(update_old_value_slow_path_->GetExitLabel());
}
__ J(GetExitLabel());
staticvoid CheckSystemArrayCopyPosition(Riscv64Assembler* assembler,
XRegister array,
Location pos,
Location length,
SlowPathCodeRISCV64* slow_path,
XRegister temp1,
XRegister temp2, bool length_is_array_length, bool position_sign_checked) { const int32_t length_offset = mirror::Array::LengthOffset().Int32Value(); if (pos.IsConstant()) {
int32_t pos_const = pos.GetConstant()->AsIntConstant()->GetValue();
DCHECK_GE(pos_const, 0); // Checked in location builder. if (pos_const == 0) { if (!length_is_array_length) { // Check that length(array) >= length.
__ Loadw(temp1, array, length_offset);
EmitBlt32(assembler, temp1, length, slow_path->GetEntryLabel(), temp2);
}
} else { // Calculate length(array) - pos. // Both operands are known to be non-negative `int32_t`, so the difference cannot underflow // as `int32_t`. If the result is negative, the BLT below shall go to the slow path.
__ Loadw(temp1, array, length_offset);
__ AddConst32(temp1, temp1, -pos_const);
// Check that (length(array) - pos) >= length.
EmitBlt32(assembler, temp1, length, slow_path->GetEntryLabel(), temp2);
}
} elseif (length_is_array_length) { // The only way the copy can succeed is if pos is zero.
__ Bnez(pos.AsCoreRegister<XRegister>(), slow_path->GetEntryLabel());
} else { // Check that pos >= 0.
XRegister pos_reg = pos.AsCoreRegister<XRegister>(); if (!position_sign_checked) {
__ Bltz(pos_reg, slow_path->GetEntryLabel());
}
// Calculate length(array) - pos. // Both operands are known to be non-negative `int32_t`, so the difference cannot underflow // as `int32_t`. If the result is negative, the BLT below shall go to the slow path.
__ Loadw(temp1, array, length_offset);
__ Sub(temp1, temp1, pos_reg);
// We can choose to use the native implementation there for longer copy lengths. static constexpr int32_t kSystemArrayCopyThreshold = 128;
void IntrinsicLocationsBuilderRISCV64::VisitSystemArrayCopy(HInvoke* invoke) { // The only read barrier implementation supporting the // SystemArrayCopy intrinsic is the Baker-style read barriers. if (codegen_->EmitNonBakerReadBarrier()) { return;
}
size_t num_temps = codegen_->EmitBakerReadBarrier() ? 4u : 2u;
LocationSummary* locations = CodeGenerator::CreateSystemArrayCopyLocationSummary(
invoke, kSystemArrayCopyThreshold, num_temps); if (locations != nullptr) { // We request position and length as constants only for small integral values.
locations->SetInAt(1, LocationForSystemArrayCopyInput(invoke->InputAt(1)));
locations->SetInAt(3, LocationForSystemArrayCopyInput(invoke->InputAt(3)));
locations->SetInAt(4, LocationForSystemArrayCopyInput(invoke->InputAt(4)));
}
}
// If source and destination are the same, then the copied arrays may overlap. // For overlapping arrays we can only guarantee correctness if `src_pos >= dst_pos`, otherwise // copying the elements at the beginning of source array may clobber the elements at the end. if (!optimizations.GetSourcePositionIsDestinationPosition()) { if (src_pos.IsConstant()) {
int32_t src_pos_constant = src_pos.GetConstant()->AsIntConstant()->GetValue(); if (dest_pos.IsConstant()) {
int32_t dest_pos_constant = dest_pos.GetConstant()->AsIntConstant()->GetValue(); if (optimizations.GetDestinationIsSource()) { // Checked when building locations.
DCHECK_GE(src_pos_constant, dest_pos_constant);
} elseif (src_pos_constant < dest_pos_constant) {
__ Beq(src, dest, slow_path->GetEntryLabel());
}
} else { if (!optimizations.GetDestinationIsSource()) {
__ Bne(src, dest, &conditions_on_positions_validated);
}
__ Li(temp, src_pos_constant);
__ Bgt(dest_pos.AsCoreRegister<XRegister>(), temp, slow_path->GetEntryLabel());
}
} else { if (!optimizations.GetDestinationIsSource()) {
__ Bne(src, dest, &conditions_on_positions_validated);
}
XRegister src_pos_reg = src_pos.AsCoreRegister<XRegister>();
EmitBlt32(assembler, src_pos_reg, dest_pos, slow_path->GetEntryLabel(), temp);
}
}
__ Bind(&conditions_on_positions_validated);
if (!optimizations.GetSourceIsNotNull()) { // Bail out if the source is null.
__ Beqz(src, slow_path->GetEntryLabel());
}
if (!optimizations.GetDestinationIsNotNull() && !optimizations.GetDestinationIsSource()) { // Bail out if the destination is null.
__ Beqz(dest, slow_path->GetEntryLabel());
}
// We have already checked in the LocationsBuilder for the constant case. if (!length.IsConstant()) { // Merge the following two comparisons into one: // If the length is negative, bail out (delegate to libcore's native implementation). // If the length >= 128 then (currently) prefer native implementation.
__ Li(temp, kSystemArrayCopyThreshold);
__ Bgeu(length.AsCoreRegister<XRegister>(), temp, slow_path->GetEntryLabel());
} else { // We have already checked in the LocationsBuilder for the constant case.
DCHECK_GE(length.GetConstant()->AsIntConstant()->GetValue(), 0);
DCHECK_LE(length.GetConstant()->AsIntConstant()->GetValue(), copy_threshold);
}
}
void IntrinsicCodeGeneratorRISCV64::VisitSystemArrayCopy(HInvoke* invoke) { // The only read barrier implementation supporting the // SystemArrayCopy intrinsic is the Baker-style read barriers.
DCHECK_IMPLIES(codegen_->EmitReadBarrier(), kUseBakerReadBarrier);
SlowPathCodeRISCV64* intrinsic_slow_path = new (codegen_->GetScopedAllocator()) IntrinsicSlowPathRISCV64(invoke);
codegen_->AddSlowPath(intrinsic_slow_path);
// Check that source and position are different, or if they are the same check that copy // direction is backward. Also check for null pointers.
CheckSystemArrayCopyNullOrOverlap(invoke,
assembler,
intrinsic_slow_path,
src,
dest,
temp1,
src_pos,
dest_pos,
length,
kSystemArrayCopyThreshold);
// Check that source position is within bounds.
CheckSystemArrayCopyPosition(assembler,
src,
src_pos,
length,
intrinsic_slow_path,
temp1,
temp2,
optimizations.GetCountIsSourceLength(), /*position_sign_checked=*/ false);
// Check that destination position is within bounds. bool dest_position_sign_checked = optimizations.GetSourcePositionIsDestinationPosition();
CheckSystemArrayCopyPosition(assembler,
dest,
dest_pos,
length,
intrinsic_slow_path,
temp1,
temp2,
optimizations.GetCountIsDestinationLength(),
dest_position_sign_checked);
auto check_non_primitive_array_class = [&](XRegister klass, XRegister temp) { // No read barrier is needed for reading a chain of constant references for comparing // with null, or for reading a constant primitive value, see `ReadBarrierOption`. // /* HeapReference<Class> */ temp = klass->component_type_
__ Loadwu(temp, klass, component_offset);
codegen_->MaybeUnpoisonHeapReference(temp); // Check that the component type is not null.
__ Beqz(temp, intrinsic_slow_path->GetEntryLabel()); // Check that the component type is not a primitive. // /* uint16_t */ temp = static_cast<uint16>(klass->primitive_type_);
__ Loadhu(temp, temp, primitive_offset);
static_assert(Primitive::kPrimNot == 0, "Expected 0 for kPrimNot");
__ Bnez(temp, intrinsic_slow_path->GetEntryLabel());
};
if (!optimizations.GetDoesNotNeedTypeCheck()) { // Check whether all elements of the source array are assignable to the component // type of the destination array. We do two checks: the classes are the same, // or the destination is Object[]. If none of these checks succeed, we go to the // slow path.
if (optimizations.GetDestinationIsTypedObjectArray()) {
DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
Riscv64Label do_copy; // For class match, we can skip the source type check regardless of the optimization flag.
__ Beq(temp1, temp2, &do_copy); // No read barrier is needed for reading a chain of constant references // for comparing with null, see `ReadBarrierOption`. // /* HeapReference<Class> */ temp1 = temp1->component_type_
__ Loadwu(temp1, temp1, component_offset);
codegen_->MaybeUnpoisonHeapReference(temp1); // /* HeapReference<Class> */ temp1 = temp1->super_class_
__ Loadwu(temp1, temp1, super_offset); // No need to unpoison the result, we're comparing against null.
__ Bnez(temp1, intrinsic_slow_path->GetEntryLabel()); // Bail out if the source is not a non primitive array. if (!optimizations.GetSourceIsNonPrimitiveArray()) {
check_non_primitive_array_class(temp2, temp2);
}
__ Bind(&do_copy);
} else {
DCHECK(!optimizations.GetDestinationIsTypedObjectArray()); // For class match, we can skip the array type check completely if at least one of source // and destination is known to be a non primitive array, otherwise one check is enough.
__ Bne(temp1, temp2, intrinsic_slow_path->GetEntryLabel()); if (!optimizations.GetDestinationIsNonPrimitiveArray() &&
!optimizations.GetSourceIsNonPrimitiveArray()) {
check_non_primitive_array_class(temp2, temp2);
}
}
} elseif (!optimizations.GetSourceIsNonPrimitiveArray()) {
DCHECK(optimizations.GetDestinationIsNonPrimitiveArray()); // Bail out if the source is not a non primitive array. // No read barrier is needed for reading a chain of constant references for comparing // with null, or for reading a constant primitive value, see `ReadBarrierOption`. // /* HeapReference<Class> */ temp2 = src->klass_
__ Loadwu(temp2, src, class_offset);
codegen_->MaybeUnpoisonHeapReference(temp2);
check_non_primitive_array_class(temp2, temp2);
}
if (length.IsConstant() && length.GetConstant()->AsIntConstant()->GetValue() == 0) { // Null constant length: not need to emit the loop code at all.
} else {
Riscv64Label skip_copy_and_write_barrier; if (length.IsCoreRegister()) { // Don't enter the copy loop if the length is null.
__ Beqz(length.AsCoreRegister<XRegister>(), &skip_copy_and_write_barrier);
}
{ // We use a block to end the scratch scope before the write barrier, thus // freeing the scratch registers so they can be used in `MarkGCCard`.
ScratchRegisterScope srs(assembler); bool emit_rb = codegen_->EmitBakerReadBarrier();
XRegister temp3 =
emit_rb ? locations->GetTemp(2).AsCoreRegister<XRegister>() : srs.AllocateXRegister();
XRegister tmp = kNoXRegister;
SlowPathCodeRISCV64* read_barrier_slow_path = nullptr; if (emit_rb) { // TODO: Also convert this intrinsic to the IsGcMarking strategy?
// SystemArrayCopy implementation for Baker read barriers (see // also CodeGeneratorRISCV64::GenerateReferenceLoadWithBakerReadBarrier): // // uint32_t rb_state = Lockword(src->monitor_).ReadBarrierState(); // lfence; // Load fence or artificial data dependency to prevent load-load reordering // bool is_gray = (rb_state == ReadBarrier::GrayState()); // if (is_gray) { // // Slow-path copy. // do { // *dest_ptr++ = MaybePoison(ReadBarrier::Mark(MaybeUnpoison(*src_ptr++))); // } while (src_ptr != end_ptr) // } else { // // Fast-path copy. // do { // *dest_ptr++ = *src_ptr++; // } while (src_ptr != end_ptr) // }
// /* uint32_t */ monitor = src->monitor_
tmp = locations->GetTemp(3).AsCoreRegister<XRegister>();
__ Loadwu(tmp, src, monitor_offset); // /* LockWord */ lock_word = LockWord(monitor)
static_assert(sizeof(LockWord) == sizeof(int32_t), "art::LockWord and int32_t have different sizes.");
// Shift the RB state bit to the sign bit while also clearing the low 32 bits // for the fake dependency below.
static_assert(LockWord::kReadBarrierStateShift < 31);
__ Slli(tmp, tmp, 63 - LockWord::kReadBarrierStateShift);
// Introduce a dependency on the lock_word including rb_state, to prevent load-load // reordering, and without using a memory barrier (which would be more expensive). // `src` is unchanged by this operation (since Adduw adds low 32 bits // which are zero after left shift), but its value now depends on `tmp`.
__ AddUw(src, tmp, src);
// Slow path used to copy array when `src` is gray.
read_barrier_slow_path = new (codegen_->GetScopedAllocator())
ReadBarrierSystemArrayCopySlowPathRISCV64(invoke, Location::CoreRegister(tmp));
codegen_->AddSlowPath(read_barrier_slow_path);
}
// Compute base source address, base destination address, and end source address for // System.arraycopy* intrinsics in `src_base`, `dst_base` and `src_end` respectively. // Note that `src_curr_addr` is computed from from `src` (and `src_pos`) here, and // thus honors the artificial dependency of `src` on `tmp` for read barriers.
GenSystemArrayCopyAddresses(codegen_,
type,
src,
src_pos,
dest,
dest_pos,
length,
src_curr_addr,
dst_curr_addr,
src_stop_addr);
if (emit_rb) { // Given the numeric representation, it's enough to check the low bit of the RB state.
static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
DCHECK_NE(tmp, kNoXRegister);
__ Bltz(tmp, read_barrier_slow_path->GetEntryLabel());
} else { // After allocating the last scrach register, we cannot use macro load/store instructions // such as `Loadwu()` and need to use raw instructions. However, all offsets below are 0.
DCHECK_EQ(tmp, kNoXRegister);
tmp = srs.AllocateXRegister();
}
// Iterate over the arrays and do a raw copy of the objects. We don't need to // poison/unpoison.
Riscv64Label loop;
__ Bind(&loop);
__ Lwu(tmp, src_curr_addr, 0);
__ Sw(tmp, dst_curr_addr, 0);
__ Addi(src_curr_addr, src_curr_addr, element_size);
__ Addi(dst_curr_addr, dst_curr_addr, element_size); // Bare: `TMP` shall not be clobbered.
__ Bne(src_curr_addr, src_stop_addr, &loop, /*is_bare=*/ true);
if (emit_rb) {
DCHECK(read_barrier_slow_path != nullptr);
__ Bind(read_barrier_slow_path->GetExitLabel());
}
}
// We only need one card marking on the destination array.
codegen_->MarkGCCard(dest);
__ Bind(&skip_copy_and_write_barrier);
}
__ Bind(intrinsic_slow_path->GetExitLabel());
}
// This value is in bytes and greater than ARRAYCOPY_SHORT_XXX_ARRAY_THRESHOLD // in libcore, so if we choose to jump to the slow path we will end up // in the native implementation. static constexpr int32_t kSystemArrayCopyPrimThreshold = 384;
// Check that source and position are different, or if they are the same check that copy // direction is backward. Also check for null pointers.
int32_t copy_threshold = kSystemArrayCopyPrimThreshold / DataType::Size(type);
CheckSystemArrayCopyNullOrOverlap(invoke,
assembler,
slow_path,
src,
dst, /*temp=*/ src_curr_addr,
src_pos,
dst_pos,
length,
copy_threshold);
// Check that source position is within bounds.
CheckSystemArrayCopyPosition(assembler,
src,
src_pos,
length,
slow_path,
src_curr_addr,
dst_curr_addr, /*length_is_array_length=*/ false, /*position_sign_checked=*/ false);
// Check that destination position is within bounds.
CheckSystemArrayCopyPosition(assembler,
dst,
dst_pos,
length,
slow_path,
src_curr_addr,
dst_curr_addr, /*length_is_array_length=*/ false, /*position_sign_checked=*/ false);
// We split processing of the array in two parts: head and tail. // A first loop handles the head by copying a block of elements per // iteration (see: elements_per_block). // A second loop handles the tail by copying the remaining elements. // If the copy length is not constant, we copy them one-by-one. // // Both loops are inverted for better performance, meaning they are // implemented as conditional do-while loops. // Here, the loop condition is first checked to determine if there are // sufficient elements to run an iteration, then we enter the do-while: an // iteration is performed followed by a conditional branch only if another // iteration is necessary. As opposed to a standard while-loop, this inversion // can save some branching (e.g. we don't branch back to the initial condition // at the end of every iteration only to potentially immediately branch // again). // // A full block of elements is subtracted and added before and after the head // loop, respectively. This ensures that any remaining length after each // head loop iteration means there is a full block remaining, reducing the // number of conditional checks required on every iteration.
ScratchRegisterScope temps(assembler);
constexpr int32_t bytes_copied_per_iteration = 16;
DCHECK_EQ(bytes_copied_per_iteration % element_size, 0);
int32_t elements_per_block = bytes_copied_per_iteration / element_size;
Riscv64Label done;
// Generate a GetAndUpdate operation. // // Only 32-bit and 64-bit atomics are currently supported, therefore smaller types need // special handling. The caller emits code to prepare aligned `ptr` and adjusted `arg` // and extract the needed bits from `old_value`. For bitwise operations, no extra // handling is needed here. For `GetAndUpdateOp::kSet` and `GetAndUpdateOp::kAdd` we // also use a special LR/SC sequence that uses a `mask` to update only the desired bits. // Note: The `mask` must contain the bits to keep for `GetAndUpdateOp::kSet` and // the bits to replace for `GetAndUpdateOp::kAdd`. staticvoid GenerateGetAndUpdate(CodeGeneratorRISCV64* codegen,
GetAndUpdateOp get_and_update_op,
DataType::Type type,
std::memory_order order,
XRegister ptr,
XRegister arg,
XRegister old_value,
XRegister mask,
XRegister temp) {
DCHECK_EQ(mask != kNoXRegister, temp != kNoXRegister);
DCHECK_IMPLIES(mask != kNoXRegister, type == DataType::Type::kInt32);
DCHECK_IMPLIES(
mask != kNoXRegister,
(get_and_update_op == GetAndUpdateOp::kSet) || (get_and_update_op == GetAndUpdateOp::kAdd));
Riscv64Assembler* assembler = codegen->GetAssembler();
AqRl amo_aqrl = GetAmoAqRl(order); switch (get_and_update_op) { case GetAndUpdateOp::kSet: if (type == DataType::Type::kInt64) {
__ AmoSwapD(old_value, arg, ptr, amo_aqrl);
} elseif (mask == kNoXRegister) {
DCHECK_EQ(type, DataType::Type::kInt32);
__ AmoSwapW(old_value, arg, ptr, amo_aqrl);
} else {
DCHECK_EQ(type, DataType::Type::kInt32);
DCHECK_NE(temp, kNoXRegister); auto [load_aqrl, store_aqrl] = GetLrScAqRl(order);
Riscv64Label retry;
__ Bind(&retry);
__ LrW(old_value, ptr, load_aqrl);
{
ScopedLrScExtensionsRestriction slser(assembler);
__ And(temp, old_value, mask);
__ Or(temp, temp, arg);
}
__ ScW(temp, temp, ptr, store_aqrl);
__ Bnez(temp, &retry, /*is_bare=*/ true); // Bare: `TMP` shall not be clobbered.
} break; case GetAndUpdateOp::kAdd: if (type == DataType::Type::kInt64) {
__ AmoAddD(old_value, arg, ptr, amo_aqrl);
} elseif (mask == kNoXRegister) {
DCHECK_EQ(type, DataType::Type::kInt32);
__ AmoAddW(old_value, arg, ptr, amo_aqrl);
} else {
DCHECK_EQ(type, DataType::Type::kInt32);
DCHECK_NE(temp, kNoXRegister); auto [load_aqrl, store_aqrl] = GetLrScAqRl(order);
Riscv64Label retry;
__ Bind(&retry);
__ LrW(old_value, ptr, load_aqrl);
{
ScopedLrScExtensionsRestriction slser(assembler);
__ Add(temp, old_value, arg); // We use `(A ^ B) ^ A == B` and with the masking `((A ^ B) & mask) ^ A`, the result // contains bits from `B` for bits specified in `mask` and bits from `A` elsewhere. // Note: These instructions directly depend on each other, so it's not necessarily the // fastest approach but for `(A ^ ~mask) | (B & mask)` we would need an extra register // for `~mask` because ANDN is not in the "I" instruction set as required for a LR/SC // sequence.
__ Xor(temp, temp, old_value);
__ And(temp, temp, mask);
__ Xor(temp, temp, old_value);
}
__ ScW(temp, temp, ptr, store_aqrl);
__ Bnez(temp, &retry, /*is_bare=*/ true); // Bare: `TMP` shall not be clobbered.
} break; case GetAndUpdateOp::kAnd: if (type == DataType::Type::kInt64) {
__ AmoAndD(old_value, arg, ptr, amo_aqrl);
} else {
DCHECK_EQ(type, DataType::Type::kInt32);
__ AmoAndW(old_value, arg, ptr, amo_aqrl);
} break; case GetAndUpdateOp::kOr: if (type == DataType::Type::kInt64) {
__ AmoOrD(old_value, arg, ptr, amo_aqrl);
} else {
DCHECK_EQ(type, DataType::Type::kInt32);
__ AmoOrW(old_value, arg, ptr, amo_aqrl);
} break; case GetAndUpdateOp::kXor: if (type == DataType::Type::kInt64) {
__ AmoXorD(old_value, arg, ptr, amo_aqrl);
} else {
DCHECK_EQ(type, DataType::Type::kInt32);
__ AmoXorW(old_value, arg, ptr, amo_aqrl);
} break;
}
}
{ // We use a block to end the scratch scope before the write barrier, thus // freeing the temporary registers so they can be used in `MarkGCCard()`.
ScratchRegisterScope srs(assembler); // Heap poisoning needs two scratch registers in `Store()`.
XRegister address = (kPoisonHeapReferences && type == DataType::Type::kReference)
? locations->GetTemp(0).AsCoreRegister<XRegister>()
: srs.AllocateXRegister();
__ Add(address, base, offset);
GenerateSet(codegen, order, value, address, /*offset=*/ 0, type);
}
if (type == DataType::Type::kReference) { bool value_can_be_null = true; // TODO: Worth finding out this information?
codegen->MaybeMarkGCCard(base, value.AsCoreRegister<XRegister>(), value_can_be_null);
}
}
// This needs to be before the temp registers, as MarkGCCard also uses scratch registers. if (type == DataType::Type::kReference) { // Mark card for object assuming new value is stored. bool new_value_can_be_null = true; // TODO: Worth finding out this information?
codegen->MaybeMarkGCCard(object, new_value, new_value_can_be_null);
}
ScratchRegisterScope srs(assembler);
XRegister tmp_ptr = srs.AllocateXRegister(); // Pointer to actual memory.
XRegister old_value; // Value in memory.
ReadBarrierCasSlowPathRISCV64* slow_path = nullptr; if (type == DataType::Type::kReference && codegen->EmitReadBarrier()) { // We need to store the `old_value` in a non-scratch register to make sure // the read barrier in the slow path does not clobber it.
old_value = locations->GetTemp(0).AsCoreRegister<XRegister>(); // The old value from main path. // The `old_value_temp` is used first for marking the `old_value` and then for the unmarked // reloaded old value for subsequent CAS in the slow path. We make this a scratch register // as we do have marking entrypoints on riscv64 even for scratch registers.
XRegister old_value_temp = srs.AllocateXRegister();
slow_path = new (codegen->GetScopedAllocator()) ReadBarrierCasSlowPathRISCV64(
invoke,
std::memory_order_seq_cst, /*strong=*/ true,
object,
offset,
expected,
new_value,
old_value,
old_value_temp, /*store_result=*/ old_value_temp, // Let the SC result clobber the reloaded old_value. /*update_old_value=*/ false,
codegen);
codegen->AddSlowPath(slow_path);
exit_loop = slow_path->GetExitLabel();
cmp_failure = slow_path->GetEntryLabel();
} else {
old_value = srs.AllocateXRegister();
}
__ Add(tmp_ptr, object, offset);
// Pre-populate the result register with failure.
__ Li(out, 0);
GenerateCompareAndSet(assembler,
type,
std::memory_order_seq_cst, /*strong=*/ true,
cmp_failure,
tmp_ptr,
new_value,
old_value, /*mask=*/ kNoXRegister, /*masked=*/ kNoXRegister, /*store_result=*/ old_value, // Let the SC result clobber the `old_value`.
expected);
DCHECK_EQ(slow_path != nullptr, type == DataType::Type::kReference && codegen->EmitReadBarrier()); if (slow_path != nullptr) {
__ Bind(slow_path->GetSuccessExitLabel());
}
// Indicate success if we successfully execute the SC.
__ Li(out, 1);
void IntrinsicLocationsBuilderRISCV64::VisitJdkUnsafeCompareAndSetReference(HInvoke* invoke) { // The only supported read barrier implementation is the Baker-style read barriers. if (codegen_->EmitNonBakerReadBarrier()) { return;
}
// TODO(riscv64): Fix this intrinsic for heap poisoning configuration. if (kPoisonHeapReferences) { return;
}
CreateUnsafeCASLocations(allocator_, invoke, codegen_); if (codegen_->EmitReadBarrier()) {
DCHECK(kUseBakerReadBarrier); // We need one non-scratch temporary register for read barrier.
LocationSummary* locations = invoke->GetLocations();
locations->AddTemp(Location::RequiresCoreRegister());
}
}
// Request another temporary register for methods that don't return a value.
DataType::Type return_type = invoke->GetType(); constbool is_void = return_type == DataType::Type::kVoid; if (is_void) {
locations->AddTemp(Location::RequiresCoreRegister());
} else {
locations->SetOut(Location::RequiresCoreRegister(), Location::kOutputOverlap);
}
}
staticvoid GenUnsafeGetAndUpdate(HInvoke* invoke,
DataType::Type type,
CodeGeneratorRISCV64* codegen,
GetAndUpdateOp get_and_update_op) { // Currently only used for these GetAndUpdateOp. Might be fine for other ops but double check // before using.
DCHECK(get_and_update_op == GetAndUpdateOp::kAdd || get_and_update_op == GetAndUpdateOp::kSet);
Riscv64Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
DataType::Type return_type = invoke->GetType(); constbool is_void = return_type == DataType::Type::kVoid; // We use a temporary for void methods, as we don't return the value.
Location out_or_temp_loc =
is_void ? locations->GetTemp(locations->GetTempCount() - 1u) : locations->Out();
XRegister out_or_temp = out_or_temp_loc.AsCoreRegister<XRegister>(); // Result.
XRegister base = locations->InAt(1).AsCoreRegister<XRegister>(); // Object pointer.
XRegister offset = locations->InAt(2).AsCoreRegister<XRegister>(); // Long offset.
XRegister arg = locations->InAt(3).AsCoreRegister<XRegister>(); // New value or addend.
// This needs to be before the temp registers, as MarkGCCard also uses scratch registers. if (type == DataType::Type::kReference) {
DCHECK(get_and_update_op == GetAndUpdateOp::kSet); // Mark card for object as a new value shall be stored. bool new_value_can_be_null = true; // TODO: Worth finding out this information?
codegen->MaybeMarkGCCard(base, /*value=*/arg, new_value_can_be_null);
}
// Get offsets of count and value fields within a string object. const int32_t count_offset = mirror::String::CountOffset().Int32Value(); const int32_t value_offset = mirror::String::ValueOffset().Int32Value();
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
// Take slow path and throw if input can be and is null.
SlowPathCodeRISCV64* slow_path = nullptr; constbool can_slow_path = invoke->InputAt(1)->CanBeNull(); if (can_slow_path) {
slow_path = new (codegen_->GetScopedAllocator()) IntrinsicSlowPathRISCV64(invoke);
codegen_->AddSlowPath(slow_path);
__ Beqz(arg, slow_path->GetEntryLabel());
}
// Reference equality check, return 0 if same reference.
__ Sub(out, str, arg);
__ Beqz(out, &end);
if (mirror::kUseStringCompression) { // Load `count` fields of this and argument strings.
__ Loadwu(temp3, str, count_offset);
__ Loadwu(temp2, arg, count_offset); // Clean out compression flag from lengths.
__ Srliw(temp0, temp3, 1u);
__ Srliw(temp1, temp2, 1u);
} else { // Load lengths of this and argument strings.
__ Loadwu(temp0, str, count_offset);
__ Loadwu(temp1, arg, count_offset);
} // out = length diff.
__ Subw(out, temp0, temp1);
// Find the length of the shorter string
__ Minu(temp0, temp0, temp1); // Shorter string is empty?
__ Beqz(temp0, &end);
if (mirror::kUseStringCompression) { // Extract both compression flags
__ Andi(temp3, temp3, 1);
__ Andi(temp2, temp2, 1);
__ Bne(temp2, temp3, &different_compression);
} // Store offset of string value in preparation for comparison loop.
__ Li(temp1, value_offset); if (mirror::kUseStringCompression) { // For string compression, calculate the number of bytes to compare (not chars).
__ Sll(temp0, temp0, temp3);
}
// Assertions that must hold in order to compare strings 8 bytes at a time.
DCHECK_ALIGNED(value_offset, 8);
static_assert(IsAligned<8>(kObjectAlignment), "String of odd length is not zero padded");
constexpr size_t char_size = DataType::Size(DataType::Type::kUint16);
static_assert(char_size == 2u, "Char expected to be 2 bytes wide");
// Loop to compare 4x16-bit characters at a time (ok because of string data alignment).
__ Bind(&loop);
__ Add(temp4, str, temp1);
__ Ld(temp4, temp4, 0);
__ Add(temp2, arg, temp1);
__ Ld(temp2, temp2, 0);
__ Bne(temp4, temp2, &find_char_diff);
__ Addi(temp1, temp1, char_size * 4); // With string compression, we have compared 8 bytes, otherwise 4 chars.
__ Addi(temp0, temp0, (mirror::kUseStringCompression) ? -8 : -4);
__ Bgtz(temp0, &loop);
__ J(&end);
// Find the single character difference.
__ Bind(&find_char_diff); // Get the bit position of the first character that differs.
__ Xor(temp1, temp2, temp4);
__ Ctz(temp1, temp1);
// If the number of chars remaining <= the index where the difference occurs (0-3), then // the difference occurs outside the remaining string data, so just return length diff (out).
__ Srliw(temp1, temp1, (mirror::kUseStringCompression) ? 3 : 4);
__ Ble(temp0, temp1, &end);
// Extract the characters and calculate the difference.
__ Slliw(temp1, temp1, (mirror::kUseStringCompression) ? 3 : 4); if (mirror:: kUseStringCompression) {
__ Slliw(temp3, temp3, 3u);
__ Andn(temp1, temp1, temp3);
}
__ Srl(temp2, temp2, temp1);
__ Srl(temp4, temp4, temp1); if (mirror::kUseStringCompression) {
__ Li(temp0, -256); // ~0xff
__ Sllw(temp0, temp0, temp3); // temp3 = 0 or 8, temp0 := ~0xff or ~0xffff
__ Andn(temp4, temp4, temp0); // Extract 8 or 16 bits.
__ Andn(temp2, temp2, temp0); // Extract 8 or 16 bits.
} else {
__ ZextH(temp4, temp4);
__ ZextH(temp2, temp2);
}
__ Subw(out, temp4, temp2);
if (mirror::kUseStringCompression) {
__ J(&end);
__ Bind(&different_compression);
// Comparison for different compression style.
constexpr size_t c_char_size = DataType::Size(DataType::Type::kInt8);
static_assert(c_char_size == 1u, "Compressed char expected to be 1 byte wide");
// `temp1` will hold the compressed data pointer, `temp2` the uncompressed data pointer.
__ Xor(temp4, str, arg);
__ Addi(temp3, temp3, -1); // -1 if str is compressed, 0 otherwise
__ And(temp2, temp4, temp3); // str^arg if str is compressed, 0 otherwise
__ Xor(temp1, temp2, arg); // str if str is compressed, arg otherwise
__ Xor(temp2, temp2, str); // arg if str is compressed, str otherwise
// We want to free up the temp3, currently holding `str` compression flag, for comparison. // So, we move it to the bottom bit of the iteration count `temp0` which we then need to treat // as unsigned. This will allow `addi temp0, temp0, -2; bgtz different_compression_loop` // to serve as the loop condition.
__ Sh1Add(temp0, temp0, temp3);
// Adjust temp1 and temp2 from string pointers to data pointers.
__ Addi(temp1, temp1, value_offset);
__ Addi(temp2, temp2, value_offset);
Riscv64Label byte_array_view_check_label_;
Riscv64Label native_byte_order_label_; // Shared parameter for all VarHandle intrinsics.
std::memory_order order_; // Extra arguments for GenerateVarHandleCompareAndSetOrExchange(). bool return_success_; bool strong_; // Extra argument for GenerateVarHandleGetAndUpdate().
GetAndUpdateOp get_and_update_op_;
};
// Note: The `type` can be `TMP`. We're using "bare" local branches to enforce that they shall // not be expanded and the scrach register `TMP` shall not be clobbered if taken. Taking the // branch to the slow path can theoretically clobber `TMP` (if outside the 1 MiB range).
__ Loadwu(temp, object, class_offset.Int32Value());
codegen->MaybeUnpoisonHeapReference(temp);
Riscv64Label loop;
__ Bind(&loop);
__ Beq(type, temp, &success, /*is_bare=*/ true); // We may not have another scratch register for `Loadwu()`. Use `Lwu()` directly.
DCHECK(IsInt<12>(super_class_offset.Int32Value()));
__ Lwu(temp, temp, super_class_offset.Int32Value());
codegen->MaybeUnpoisonHeapReference(temp);
__ Beqz(temp, slow_path->GetEntryLabel());
__ J(&loop, /*is_bare=*/ true);
__ Bind(&success);
}
// Check access mode and the primitive type from VarHandle.varType. // Check reference arguments against the VarHandle.varType; for references this is a subclass // check without read barrier, so it can have false negatives which we handle in the slow path. staticvoid GenerateVarHandleAccessModeAndVarTypeChecks(HInvoke* invoke,
CodeGeneratorRISCV64* codegen,
SlowPathCodeRISCV64* slow_path,
DataType::Type type) {
mirror::VarHandle::AccessMode access_mode =
mirror::VarHandle::GetAccessModeByIntrinsic(invoke->GetIntrinsic());
Primitive::Type primitive_type = DataTypeToPrimitive(type);
// Check that the operation is permitted.
__ Loadw(temp, varhandle, access_mode_bit_mask_offset.Int32Value());
DCHECK_LT(enum_cast<uint32_t>(access_mode), 31u); // We cannot avoid the shift below.
__ Slliw(temp, temp, 31 - enum_cast<uint32_t>(access_mode)); // Shift tested bit to sign bit.
__ Bgez(temp, slow_path->GetEntryLabel()); // If not permitted, go to slow path.
// For primitive types, we do not need a read barrier when loading a reference only for loading // constant field through the reference. For reference types, we deliberately avoid the read // barrier, letting the slow path handle the false negatives.
__ Loadwu(temp, varhandle, var_type_offset.Int32Value());
codegen->MaybeUnpoisonHeapReference(temp);
// Check the varType.primitiveType field against the type we're trying to use.
__ Loadhu(temp2, temp, primitive_type_offset.Int32Value()); if (primitive_type == Primitive::kPrimNot) {
static_assert(Primitive::kPrimNot == 0);
__ Bnez(temp2, slow_path->GetEntryLabel());
} else {
__ Li(temp, enum_cast<int32_t>(primitive_type)); // `temp` can be clobbered.
__ Bne(temp2, temp, slow_path->GetEntryLabel());
}
srs.FreeXRegister(temp2);
if (type == DataType::Type::kReference) { // Check reference arguments against the varType. // False negatives due to varType being an interface or array type // or due to the missing read barrier are handled by the slow path.
size_t expected_coordinates_count = GetExpectedVarHandleCoordinatesCount(invoke);
uint32_t arguments_start = /* VarHandle object */ 1u + expected_coordinates_count;
uint32_t number_of_arguments = invoke->GetNumberOfArguments(); for (size_t arg_index = arguments_start; arg_index != number_of_arguments; ++arg_index) {
HInstruction* arg = invoke->InputAt(arg_index);
DCHECK_EQ(arg->GetType(), DataType::Type::kReference); if (!arg->IsNullConstant()) {
XRegister arg_reg = locations->InAt(arg_index).AsCoreRegister<XRegister>();
GenerateSubTypeObjectCheckNoReadBarrier(codegen, slow_path, arg_reg, temp);
}
}
}
}
// Check that the VarHandle references a static field by checking that coordinateType0 == null. // Do not emit read barrier (or unpoison the reference) for comparing to null.
__ Loadwu(temp, varhandle, coordinate_type0_offset.Int32Value());
__ Bnez(temp, slow_path->GetEntryLabel());
}
// Null-check the object. if (!optimizations.GetSkipObjectNullCheck()) {
__ Beqz(object, slow_path->GetEntryLabel());
}
if (!optimizations.GetUseKnownImageVarHandle()) {
ScratchRegisterScope srs(assembler);
XRegister temp = srs.AllocateXRegister();
// Check that the VarHandle references an instance field by checking that // coordinateType1 == null. coordinateType0 should not be null, but this is handled by the // type compatibility check with the source object's type, which will fail for null.
__ Loadwu(temp, varhandle, coordinate_type1_offset.Int32Value()); // No need for read barrier or unpoisoning of coordinateType1 for comparison with null.
__ Bnez(temp, slow_path->GetEntryLabel());
// Check that the object has the correct type. // We deliberately avoid the read barrier, letting the slow path handle the false negatives.
__ Loadwu(temp, varhandle, coordinate_type0_offset.Int32Value());
codegen->MaybeUnpoisonHeapReference(temp);
GenerateSubTypeObjectCheckNoReadBarrier(
codegen, slow_path, object, temp, /*object_can_be_null=*/ false);
}
}
// Check that the VarHandle references an array, byte array view or ByteBuffer by checking // that coordinateType1 != null. If that's true, coordinateType1 shall be int.class and // coordinateType0 shall not be null but we do not explicitly verify that.
__ Loadwu(temp, varhandle, coordinate_type1_offset.Int32Value()); // No need for read barrier or unpoisoning of coordinateType1 for comparison with null.
__ Beqz(temp, slow_path->GetEntryLabel());
// Check object class against componentType0. // // This is an exact check and we defer other cases to the runtime. This includes // conversion to array of superclass references, which is valid but subsequently // requires all update operations to check that the value can indeed be stored. // We do not want to perform such extra checks in the intrinsified code. // // We do this check without read barrier, so there can be false negatives which we // defer to the slow path. There shall be no false negatives for array classes in the // boot image (including Object[] and primitive arrays) because they are non-movable.
__ Loadwu(temp, varhandle, coordinate_type0_offset.Int32Value());
__ Loadwu(temp2, object, class_offset.Int32Value());
__ Bne(temp, temp2, slow_path->GetEntryLabel());
// Check that the coordinateType0 is an array type. We do not need a read barrier // for loading constant reference fields (or chains of them) for comparison with null, // nor for finally loading a constant primitive field (primitive type) below.
codegen->MaybeUnpoisonHeapReference(temp);
__ Loadwu(temp2, temp, component_type_offset.Int32Value());
codegen->MaybeUnpoisonHeapReference(temp2);
__ Beqz(temp2, slow_path->GetEntryLabel());
// Check that the array component type matches the primitive type.
__ Loadhu(temp, temp2, primitive_type_offset.Int32Value()); if (primitive_type == Primitive::kPrimNot) {
static_assert(Primitive::kPrimNot == 0);
__ Bnez(temp, slow_path->GetEntryLabel());
} else { // With the exception of `kPrimNot` (handled above), `kPrimByte` and `kPrimBoolean`, // we shall check for a byte array view in the slow path. // The check requires the ByteArrayViewVarHandle.class to be in the boot image, // so we cannot emit that if we're JITting without boot image. bool boot_image_available =
codegen->GetCompilerOptions().IsBootImage() ||
!Runtime::Current()->GetHeap()->GetBootImageSpaces().empty(); bool can_be_view = (DataType::Size(value_type) != 1u) && boot_image_available;
Riscv64Label* slow_path_label =
can_be_view ? slow_path->GetByteArrayViewCheckLabel() : slow_path->GetEntryLabel();
__ Li(temp2, enum_cast<int32_t>(primitive_type));
__ Bne(temp, temp2, slow_path_label);
}
// Check for array index out of bounds.
__ Loadw(temp, object, array_length_offset.Int32Value());
__ Bgeu(index, temp, slow_path->GetEntryLabel());
}
struct VarHandleTarget {
XRegister object; // The object holding the value to operate on.
XRegister offset; // The offset of the value to operate on.
};
VarHandleTarget target; // The temporary allocated for loading the offset.
target.offset = locations->GetTemp(0u).AsCoreRegister<XRegister>(); // The reference to the object that holds the value to operate on.
target.object = (expected_coordinates_count == 0u)
? locations->GetTemp(1u).AsCoreRegister<XRegister>()
: locations->InAt(1).AsCoreRegister<XRegister>(); return target;
}
if (expected_coordinates_count <= 1u) { if (VarHandleOptimizations(invoke).GetUseKnownImageVarHandle()) {
ScopedObjectAccess soa(Thread::Current());
ArtField* target_field = GetImageVarHandleField(invoke); if (expected_coordinates_count == 0u) {
ObjPtr<mirror::Class> declaring_class = target_field->GetDeclaringClass(); if (Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(declaring_class)) {
uint32_t boot_image_offset = CodeGenerator::GetBootImageOffset(declaring_class);
codegen->LoadBootImageRelRoEntry(target.object, boot_image_offset);
} else {
codegen->LoadTypeForBootImageIntrinsic(
target.object,
TypeReference(&declaring_class->GetDexFile(), declaring_class->GetDexTypeIndex()));
}
}
__ Li(target.offset, target_field->GetOffset().Uint32Value());
} else { // For static fields, we need to fill the `target.object` with the declaring class, // so we can use `target.object` as temporary for the `ArtField*`. For instance fields, // we do not need the declaring class, so we can forget the `ArtField*` when // we load the `target.offset`, so use the `target.offset` to hold the `ArtField*`.
XRegister field = (expected_coordinates_count == 0) ? target.object : target.offset;
ArenaAllocator* allocator = codegen->GetGraph()->GetAllocator();
LocationSummary* locations =
LocationSummary::Create(allocator, invoke, LocationSummary::kCallOnSlowPath, kIntrinsified);
locations->SetInAt(0, Location::RequiresCoreRegister()); // Require coordinates in registers. These are the object holding the value // to operate on (except for static fields) and index (for arrays and views). for (size_t i = 0; i != expected_coordinates_count; ++i) {
locations->SetInAt(/* VarHandle object */ 1u + i, Location::RequiresCoreRegister());
} if (return_type != DataType::Type::kVoid) { if (DataType::IsFloatingPointType(return_type)) {
locations->SetOut(Location::RequiresFpuRegister());
} else {
locations->SetOut(Location::RequiresCoreRegister());
}
}
uint32_t arguments_start = /* VarHandle object */ 1u + expected_coordinates_count;
uint32_t number_of_arguments = invoke->GetNumberOfArguments(); for (size_t arg_index = arguments_start; arg_index != number_of_arguments; ++arg_index) {
HInstruction* arg = invoke->InputAt(arg_index); if (IsZeroBitPattern(arg)) {
locations->SetInAt(arg_index, Location::ConstantLocation(arg));
} elseif (DataType::IsFloatingPointType(arg->GetType())) {
locations->SetInAt(arg_index, Location::RequiresFpuRegister());
} else {
locations->SetInAt(arg_index, Location::RequiresCoreRegister());
}
}
// Add a temporary for offset. if (codegen->EmitNonBakerReadBarrier() &&
GetExpectedVarHandleCoordinatesCount(invoke) == 0u) { // For static fields. // To preserve the offset value across the non-Baker read barrier slow path // for loading the declaring class, use a fixed callee-save register.
constexpr int first_callee_save = CTZ(kRiscv64CalleeSaveRefSpills);
locations->AddTemp(Location::CoreRegister(first_callee_save));
} else {
locations->AddTemp(Location::RequiresCoreRegister());
} if (expected_coordinates_count == 0u) { // Add a temporary to hold the declaring class.
locations->AddTemp(Location::RequiresCoreRegister());
}
if (codegen->EmitNonBakerReadBarrier() &&
invoke->GetType() == DataType::Type::kReference &&
invoke->GetIntrinsic() != Intrinsics::kVarHandleGet &&
invoke->GetIntrinsic() != Intrinsics::kVarHandleGetOpaque) { // Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores // the passed reference and reloads it from the field. This gets the memory visibility // wrong for Acquire/Volatile operations. b/173104084 return;
}
if (byte_swap) {
DCHECK(!value.IsConstant()); // Zero uses the main path as it does not need a byte swap. // The offset is no longer needed, so reuse the offset temporary for the byte-swapped value.
Location new_value = Location::CoreRegister(target.offset); if (DataType::IsFloatingPointType(value_type)) {
value_type = IntTypeForFloatingPointType(value_type);
codegen->MoveLocation(new_value, value, value_type);
value = new_value;
}
GenerateReverseBytes(codegen, new_value, value.AsCoreRegister<XRegister>(), value_type);
value = new_value;
}
bool is_reference = (value_type == DataType::Type::kReference); if (is_reference && codegen->EmitNonBakerReadBarrier()) { // Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores // the passed reference and reloads it from the field. This breaks the read barriers // in slow path in different ways. The marked old value may not actually be a to-space // reference to the same object as `old_value`, breaking slow path assumptions. And // for CompareAndExchange, marking the old value after comparison failure may actually // return the reference to `expected`, erroneously indicating success even though we // did not set the new value. (And it also gets the memory visibility wrong.) b/173104084 return;
}
// TODO(riscv64): Fix this intrinsic for heap poisoning configuration. if (kPoisonHeapReferences && value_type == DataType::Type::kReference) { return;
}
if (codegen->EmitNonBakerReadBarrier()) { // We need callee-save registers for both the class object and offset instead of // the temporaries reserved in CreateVarHandleCommonLocations().
static_assert(POPCOUNT(kRiscv64CalleeSaveRefSpills) >= 2u);
uint32_t first_callee_save = CTZ(kRiscv64CalleeSaveRefSpills);
uint32_t second_callee_save = CTZ(kRiscv64CalleeSaveRefSpills ^ (1u << first_callee_save)); if (expected_index == 1u) { // For static fields.
DCHECK_EQ(locations->GetTempCount(), 2u);
DCHECK(locations->GetTemp(0u).Equals(Location::RequiresCoreRegister()));
DCHECK(locations->GetTemp(1u).Equals(Location::CoreRegister(first_callee_save)));
locations->SetTempAt(0u, Location::CoreRegister(second_callee_save));
} else {
DCHECK_EQ(locations->GetTempCount(), 1u);
DCHECK(locations->GetTemp(0u).Equals(Location::RequiresCoreRegister()));
locations->SetTempAt(0u, Location::CoreRegister(first_callee_save));
}
}
size_t old_temp_count = locations->GetTempCount();
DCHECK_EQ(old_temp_count, (expected_index == 1u) ? 2u : 1u);
Location expected = locations->InAt(expected_index);
Location new_value = locations->InAt(new_value_index);
size_t data_size = DataType::Size(value_type); bool is_small = (data_size < 4u); bool can_byte_swap =
(expected_index == 3u) && (value_type != DataType::Type::kReference && data_size != 1u); bool is_fp = DataType::IsFloatingPointType(value_type);
size_t temps_needed = // The offset temp is used for the `tmp_ptr`, except for the read barrier case. For read // barrier we must preserve the offset and class pointer (if any) for the slow path and // use a separate temp for `tmp_ptr` and we also need another temp for `old_value_temp`.
((is_reference && codegen->EmitReadBarrier()) ? old_temp_count + 2u : 1u) + // For small values, we need a temp for the `mask`, `masked` and maybe also for the `shift`.
(is_small ? (return_success ? 2u : 3u) : 0u) + // Some cases need modified copies of `new_value` and `expected`.
(ScratchXRegisterNeeded(expected, value_type, can_byte_swap) ? 1u : 0u) +
(ScratchXRegisterNeeded(new_value, value_type, can_byte_swap) ? 1u : 0u) + // We need a scratch register either for the old value or for the result of SC. // If we need to return a floating point old value, we need a temp for each.
((!return_success && is_fp) ? 2u : 1u);
size_t scratch_registers_available = 2u; // TMP(T6) and TMP2(T5). if (temps_needed > old_temp_count + scratch_registers_available) {
locations->AddRegisterTemps(temps_needed - (old_temp_count + scratch_registers_available));
}
}
static XRegister PrepareXRegister(CodeGeneratorRISCV64* codegen,
Location loc,
DataType::Type type,
XRegister shift,
XRegister mask, bool byte_swap,
ScratchRegisterScope* srs) {
DCHECK_IMPLIES(mask != kNoXRegister, shift != kNoXRegister);
DCHECK_EQ(shift == kNoXRegister, DataType::Size(type) >= 4u); if (loc.IsConstant()) { // The `shift`/`mask` and `byte_swap` are irrelevant for zero input.
DCHECK(loc.GetConstant()->IsZeroBitPattern()); return Zero;
}
Location result = loc; if (DataType::IsFloatingPointType(type)) {
type = IntTypeForFloatingPointType(type);
result = Location::CoreRegister(srs->AllocateXRegister());
codegen->MoveLocation(result, loc, type);
loc = result;
} elseif (byte_swap || shift != kNoXRegister) {
result = Location::CoreRegister(srs->AllocateXRegister());
} if (byte_swap) { if (type == DataType::Type::kInt16) {
type = DataType::Type::kUint16; // Do the masking as part of the byte swap.
}
GenerateReverseBytes(codegen, result, loc.AsCoreRegister<XRegister>(), type);
loc = result;
} if (shift != kNoXRegister) {
Riscv64Assembler* assembler = codegen->GetAssembler();
__ Sllw(result.AsCoreRegister<XRegister>(), loc.AsCoreRegister<XRegister>(), shift);
DCHECK_NE(type, DataType::Type::kUint8); if (mask != kNoXRegister && type != DataType::Type::kUint16 && type != DataType::Type::kBool) {
__ And(result.AsCoreRegister<XRegister>(), result.AsCoreRegister<XRegister>(), mask);
}
} return result.AsCoreRegister<XRegister>();
}
staticvoid GenerateByteSwapAndExtract(CodeGeneratorRISCV64* codegen,
Location rd,
XRegister rs1,
XRegister shift,
DataType::Type type) { // Apply shift before `GenerateReverseBytes()` for small types.
DCHECK_EQ(shift != kNoXRegister, DataType::Size(type) < 4u); if (shift != kNoXRegister) {
Riscv64Assembler* assembler = codegen->GetAssembler();
__ Srlw(rd.AsCoreRegister<XRegister>(), rs1, shift);
rs1 = rd.AsCoreRegister<XRegister>();
} // Also handles moving to FP registers.
GenerateReverseBytes(codegen, rd, rs1, type);
}
// This needs to be before we allocate the scratch registers, as MarkGCCard also uses them. if (CodeGenerator::StoreNeedsWriteBarrier(value_type, invoke->InputAt(new_value_index))) { // Mark card for object assuming new value is stored. bool new_value_can_be_null = true; // TODO: Worth finding out this information?
codegen->MaybeMarkGCCard(
target.object, new_value.AsCoreRegister<XRegister>(), new_value_can_be_null);
}
// Scratch registers may be needed for `new_value` and `expected`.
ScratchRegisterScope srs(assembler);
DCHECK_EQ(srs.AvailableXRegisters(), 2u);
size_t available_scratch_registers =
(ScratchXRegisterNeeded(expected, value_type, byte_swap) ? 0u : 1u) +
(ScratchXRegisterNeeded(new_value, value_type, byte_swap) ? 0u : 1u);
// Reuse the `offset` temporary for the pointer to the target location, // except for references that need the offset for the read barrier.
DCHECK_EQ(target.offset, locations->GetTemp(0u).AsCoreRegister<XRegister>());
size_t next_temp = 1u;
XRegister tmp_ptr = target.offset; bool is_reference = (value_type == DataType::Type::kReference); if (is_reference && codegen->EmitReadBarrier()) { // Reserve scratch registers for `tmp_ptr` and `old_value_temp`.
DCHECK_EQ(available_scratch_registers, 2u);
available_scratch_registers = 0u;
DCHECK_EQ(expected_index, 1u + GetExpectedVarHandleCoordinatesCount(invoke));
next_temp = expected_index == 1u ? 2u : 1u; // Preserve the class register for static field.
tmp_ptr = srs.AllocateXRegister();
}
__ Add(tmp_ptr, target.object, target.offset);
XRegister shift = kNoXRegister;
XRegister mask = kNoXRegister;
XRegister masked = kNoXRegister;
size_t data_size = DataType::Size(value_type); bool is_small = (data_size < 4u); if (is_small) { // When returning "success" and not the old value, we shall not need the `shift` after // the raw CAS operation, so use the output register as a temporary here.
shift = return_success ? locations->Out().AsCoreRegister<XRegister>() : get_temp();
mask = get_temp();
masked = get_temp(); // Upper bits of the shift are not used, so we do not need to clear them.
__ Slli(shift, tmp_ptr, WhichPowerOf2(kBitsPerByte));
__ Andi(tmp_ptr, tmp_ptr, -4);
__ Li(mask, (1 << (data_size * kBitsPerByte)) - 1);
__ Sllw(mask, mask, shift);
}
// Move floating point values to scratch registers and apply shift, mask and byte swap if needed. // Note that float/double CAS uses bitwise comparison, rather than the operator==.
XRegister expected_reg =
PrepareXRegister(codegen, expected, value_type, shift, mask, byte_swap, &srs);
XRegister new_value_reg =
PrepareXRegister(codegen, new_value, value_type, shift, mask, byte_swap, &srs); bool is_fp = DataType::IsFloatingPointType(value_type);
DataType::Type cas_type = is_fp
? IntTypeForFloatingPointType(value_type)
: (is_small ? DataType::Type::kInt32 : value_type);
// Prepare registers for old value and the result of the store conditional.
XRegister old_value;
XRegister store_result; if (return_success) { // Use a temp for the old value.
old_value = get_temp(); // For strong CAS, use the `old_value` temp also for the SC result. // For weak CAS, put the SC result directly to `out`.
store_result = strong ? old_value : out.AsCoreRegister<XRegister>();
} elseif (is_fp) { // We need two temporary registers.
old_value = get_temp();
store_result = get_temp();
} else { // Use the output register for the old value and a temp for the store conditional result.
old_value = out.AsCoreRegister<XRegister>();
store_result = get_temp();
}
ReadBarrierCasSlowPathRISCV64* rb_slow_path = nullptr; if (is_reference && codegen->EmitReadBarrier()) { // The `old_value_temp` is used first for marking the `old_value` and then for the unmarked // reloaded old value for subsequent CAS in the slow path. We make this a scratch register // as we do have marking entrypoints on riscv64 even for scratch registers.
XRegister old_value_temp = srs.AllocateXRegister(); // For strong CAS, use the `old_value_temp` also for the SC result as the reloaded old value // is no longer needed after the comparison. For weak CAS, store the SC result in the same // result register as the main path. // Note that for a strong CAS, a SC failure in the slow path can set the register to 1, so // we cannot use that register to indicate success without resetting it to 0 at the start of // the retry loop. Instead, we return to the success indicating instruction in the main path.
XRegister slow_path_store_result = strong ? old_value_temp : store_result;
rb_slow_path = new (codegen->GetScopedAllocator()) ReadBarrierCasSlowPathRISCV64(
invoke,
order,
strong,
target.object,
target.offset,
expected_reg,
new_value_reg,
old_value,
old_value_temp,
slow_path_store_result, /*update_old_value=*/ !return_success,
codegen);
codegen->AddSlowPath(rb_slow_path);
exit_loop = rb_slow_path->GetExitLabel();
cmp_failure = rb_slow_path->GetEntryLabel();
}
if (return_success) { // Pre-populate the output register with failure for the case when the old value // differs and we do not execute the store conditional.
__ Li(out.AsCoreRegister<XRegister>(), 0);
}
GenerateCompareAndSet(codegen->GetAssembler(),
cas_type,
order,
strong,
cmp_failure,
tmp_ptr,
new_value_reg,
old_value,
mask,
masked,
store_result,
expected_reg); if (return_success && strong) { if (rb_slow_path != nullptr) { // Slow path returns here on success.
__ Bind(rb_slow_path->GetSuccessExitLabel());
} // Load success value to the output register. // `GenerateCompareAndSet()` does not emit code to indicate success for a strong CAS.
__ Li(out.AsCoreRegister<XRegister>(), 1);
} elseif (rb_slow_path != nullptr) {
DCHECK(!rb_slow_path->GetSuccessExitLabel()->IsLinked());
}
__ Bind(exit_loop);
if (return_success) { // Nothing to do, the result register already contains 1 on success and 0 on failure.
} elseif (byte_swap) {
DCHECK_IMPLIES(is_small, out.AsCoreRegister<XRegister>() == old_value)
<< " " << value_type << " " << out.AsCoreRegister<XRegister>() << "!=" << old_value;
GenerateByteSwapAndExtract(codegen, out, old_value, shift, value_type);
} elseif (is_fp) {
codegen->MoveLocation(out, Location::CoreRegister(old_value), value_type);
} elseif (is_small) {
__ Srlw(old_value, masked, shift); if (value_type == DataType::Type::kInt8) {
__ SextB(old_value, old_value);
} elseif (value_type == DataType::Type::kInt16) {
__ SextH(old_value, old_value);
}
}
if (slow_path != nullptr) {
DCHECK(!byte_swap);
__ Bind(slow_path->GetExitLabel());
}
// Check that we have allocated the right number of temps. We may need more registers // for byte swapped CAS in the slow path, so skip this check for the main path in that case. bool has_byte_swap = (expected_index == 3u) && (!is_reference && data_size != 1u); if ((!has_byte_swap || byte_swap) && next_temp != locations->GetTempCount()) { // We allocate a temporary register for the class object for a static field `VarHandle` but // we do not update the `next_temp` if it's otherwise unused after the address calculation.
CHECK_EQ(expected_index, 1u);
CHECK_EQ(next_temp, 1u);
CHECK_EQ(locations->GetTempCount(), 2u);
}
}
// Get the type from the shorty as the invokes may not return a value.
uint32_t arg_index = invoke->GetNumberOfArguments() - 1;
DCHECK_EQ(arg_index, 1u + GetExpectedVarHandleCoordinatesCount(invoke));
DataType::Type value_type = GetDataTypeFromShorty(invoke, arg_index); if (value_type == DataType::Type::kReference && codegen->EmitNonBakerReadBarrier()) { // Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores // the passed reference and reloads it from the field, thus seeing the new value // that we have just stored. (And it also gets the memory visibility wrong.) b/173104084 return;
}
// TODO(riscv64): Fix this intrinsic for heap poisoning configuration. if (kPoisonHeapReferences && value_type == DataType::Type::kReference) { return;
}
bool is_fp = DataType::IsFloatingPointType(value_type); if (is_fp) { if (get_and_update_op == GetAndUpdateOp::kAdd) { // For ADD, do not use ZR for zero bit pattern (+0.0f or +0.0).
locations->SetInAt(arg_index, Location::RequiresFpuRegister());
} else {
DCHECK(get_and_update_op == GetAndUpdateOp::kSet);
}
}
size_t temps_needed = // The offset temp is used for the `tmp_ptr`. 1u + // For small values, we need temps for `shift` and maybe also `mask` and `temp`.
(is_small ? (is_bitwise ? 1u : 3u) : 0u) + // Some cases need modified copies of `arg`.
(is_small_and || ScratchXRegisterNeeded(arg, value_type, can_byte_swap) ? 1u : 0u) + // For FP types, we need a temp for `old_value` which cannot be loaded directly to `out`.
(is_fp ? 1u : 0u); if (can_use_cas) {
size_t cas_temps_needed = // The offset temp is used for the `tmp_ptr`. 1u + // For small values, we need a temp for `shift`.
(is_small ? 1u : 0u) + // And we always need temps for `old_value`, `new_value` and `reloaded_old_value`. 3u;
DCHECK_GE(cas_temps_needed, temps_needed);
temps_needed = cas_temps_needed;
}
// Request another temporary register for methods that don't return a value. // For the non-void case, we already set `out` in `CreateVarHandleCommonLocations`.
DataType::Type return_type = invoke->GetType(); constbool is_void = return_type == DataType::Type::kVoid;
DCHECK_IMPLIES(!is_void, return_type == value_type); if (is_void) { if (DataType::IsFloatingPointType(value_type)) {
locations->AddTemp(Location::RequiresFpuRegister());
} else {
locations->AddTemp(Location::RequiresCoreRegister());
}
}
}
staticvoid GenerateVarHandleGetAndUpdate(HInvoke* invoke,
CodeGeneratorRISCV64* codegen,
GetAndUpdateOp get_and_update_op,
std::memory_order order, bool byte_swap = false) { // Get the type from the shorty as the invokes may not return a value.
uint32_t arg_index = invoke->GetNumberOfArguments() - 1;
DCHECK_EQ(arg_index, 1u + GetExpectedVarHandleCoordinatesCount(invoke));
DataType::Type value_type = GetDataTypeFromShorty(invoke, arg_index);
Riscv64Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
Location arg = locations->InAt(arg_index);
DCHECK_IMPLIES(arg.IsConstant(), arg.GetConstant()->IsZeroBitPattern());
DataType::Type return_type = invoke->GetType(); constbool is_void = return_type == DataType::Type::kVoid;
DCHECK_IMPLIES(!is_void, return_type == value_type); // We use a temporary for void methods, as we don't return the value.
Location out_or_temp =
is_void ? locations->GetTemp(locations->GetTempCount() - 1u) : locations->Out();
// This needs to be before the temp registers, as MarkGCCard also uses scratch registers. if (CodeGenerator::StoreNeedsWriteBarrier(value_type, invoke->InputAt(arg_index))) {
DCHECK(get_and_update_op == GetAndUpdateOp::kSet); // Mark card for object, the new value shall be stored. bool new_value_can_be_null = true; // TODO: Worth finding out this information?
codegen->MaybeMarkGCCard(target.object, arg.AsCoreRegister<XRegister>(), new_value_can_be_null);
}
ScratchRegisterScope srs(assembler);
DCHECK_EQ(srs.AvailableXRegisters(), 2u);
size_t available_scratch_registers = use_cas // We use scratch registers differently for the CAS path.
? 0u // Reserve one scratch register for `PrepareXRegister()` or similar `arg_reg` allocation.
: (is_small_and || ScratchXRegisterNeeded(arg, value_type, byte_swap) ? 1u : 2u);
// Reuse the `target.offset` temporary for the pointer to the target location, // except for references that need the offset for the non-Baker read barrier.
DCHECK_EQ(target.offset, locations->GetTemp(0u).AsCoreRegister<XRegister>());
size_t next_temp = 1u;
XRegister tmp_ptr = target.offset; if (is_reference && codegen->EmitNonBakerReadBarrier()) {
DCHECK_EQ(available_scratch_registers, 2u);
available_scratch_registers -= 1u;
tmp_ptr = srs.AllocateXRegister();
}
__ Add(tmp_ptr, target.object, target.offset);
auto get_temp = [&]() { if (available_scratch_registers != 0u) {
available_scratch_registers -= 1u; return srs.AllocateXRegister();
} else {
DCHECK_IMPLIES(is_void, next_temp != locations->GetTempCount() - 1u)
<< "The last temp is special for the void case, as it represents the out register.";
XRegister temp = locations->GetTemp(next_temp).AsCoreRegister<XRegister>();
next_temp += 1u; return temp;
}
};
XRegister shift = kNoXRegister;
XRegister mask = kNoXRegister;
XRegister prepare_mask = kNoXRegister;
XRegister temp = kNoXRegister;
XRegister arg_reg = kNoXRegister; if (is_small) {
shift = get_temp(); // Upper bits of the shift are not used, so we do not need to clear them.
__ Slli(shift, tmp_ptr, WhichPowerOf2(kBitsPerByte));
__ Andi(tmp_ptr, tmp_ptr, -4); switch (get_and_update_op) { case GetAndUpdateOp::kAdd: if (byte_swap) { // The mask is not needed in the CAS path.
DCHECK(use_cas); break;
}
FALLTHROUGH_INTENDED; case GetAndUpdateOp::kSet:
mask = get_temp();
temp = get_temp();
__ Li(mask, (1 << (data_size * kBitsPerByte)) - 1);
__ Sllw(mask, mask, shift); // The argument does not need to be masked for `GetAndUpdateOp::kAdd`, // the mask shall be applied after the ADD instruction.
prepare_mask = (get_and_update_op == GetAndUpdateOp::kSet) ? mask : kNoXRegister; break; case GetAndUpdateOp::kAnd: // We need to set all other bits, so we always need a temp.
arg_reg = srs.AllocateXRegister(); if (data_size == 1u) {
__ Ori(arg_reg, InputXRegisterOrZero(arg), ~0xff);
DCHECK(!byte_swap);
} else {
DCHECK_EQ(data_size, 2u);
__ Li(arg_reg, ~0xffff);
__ Or(arg_reg, InputXRegisterOrZero(arg), arg_reg); if (byte_swap) {
__ Rev8(arg_reg, arg_reg);
__ Rori(arg_reg, arg_reg, 48);
}
}
__ Rolw(arg_reg, arg_reg, shift); break; case GetAndUpdateOp::kOr: case GetAndUpdateOp::kXor: // Signed values need to be truncated but we're keeping `prepare_mask == kNoXRegister`. if (value_type == DataType::Type::kInt8 && !arg.IsConstant()) {
DCHECK(!byte_swap);
arg_reg = srs.AllocateXRegister();
__ ZextB(arg_reg, arg.AsCoreRegister<XRegister>());
__ Sllw(arg_reg, arg_reg, shift);
} elseif (value_type == DataType::Type::kInt16 && !arg.IsConstant() && !byte_swap) {
arg_reg = srs.AllocateXRegister();
__ ZextH(arg_reg, arg.AsCoreRegister<XRegister>());
__ Sllw(arg_reg, arg_reg, shift);
} // else handled by `PrepareXRegister()` below. break;
}
} if (arg_reg == kNoXRegister && !use_cas) {
arg_reg = PrepareXRegister(codegen, arg, value_type, shift, prepare_mask, byte_swap, &srs);
} if (mask != kNoXRegister && get_and_update_op == GetAndUpdateOp::kSet) {
__ Not(mask, mask); // We need to flip the mask for `kSet`, see `GenerateGetAndUpdate()`.
}
if (use_cas) { // Allocate scratch registers for temps that can theoretically be clobbered on retry. // (Even though the `retry` label shall never be far enough for `TMP` to be clobbered.)
DCHECK_EQ(available_scratch_registers, 0u); // Reserved for the two uses below.
XRegister old_value = srs.AllocateXRegister();
XRegister new_value = srs.AllocateXRegister(); // Allocate other needed temporaries.
XRegister reloaded_old_value = get_temp();
XRegister store_result = reloaded_old_value; // Clobber reloaded old value by store result.
FRegister ftmp = is_fp ? srs.AllocateFRegister() : kNoFRegister;
Riscv64Label retry;
__ Bind(&retry);
codegen->GetInstructionVisitor()->Load(
Location::CoreRegister(old_value), tmp_ptr, /*offset=*/ 0, op_type); if (byte_swap) {
GenerateByteSwapAndExtract(codegen, out_or_temp, old_value, shift, value_type);
} else {
DCHECK(is_fp);
codegen->MoveLocation(out_or_temp, Location::CoreRegister(old_value), value_type);
} if (is_fp) {
codegen->GetInstructionVisitor()->FAdd(
ftmp, out_or_temp.AsFpuRegister<FRegister>(), arg.AsFpuRegister<FRegister>(), value_type);
codegen->MoveLocation(
Location::CoreRegister(new_value), Location::FpuRegister(ftmp), op_type);
} elseif (arg.IsConstant()) {
DCHECK(arg.GetConstant()->IsZeroBitPattern());
__ Mv(new_value, out_or_temp.AsCoreRegister<XRegister>());
} elseif (value_type == DataType::Type::kInt64) {
__ Add(new_value, out_or_temp.AsCoreRegister<XRegister>(), arg.AsCoreRegister<XRegister>());
} else {
DCHECK_EQ(op_type, DataType::Type::kInt32);
__ Addw(new_value, out_or_temp.AsCoreRegister<XRegister>(), arg.AsCoreRegister<XRegister>());
} if (byte_swap) {
DataType::Type swap_type = op_type; if (is_small) {
DCHECK_EQ(data_size, 2u); // We want to update only 16 bits of the 32-bit location. The 16 bits we want to replace // are present in both `old_value` and `out` but in different bits and byte order. // To update the 16 bits, we can XOR the new value with the `out`, byte swap as Uint16 // (extracting only the bits we want to update), shift and XOR with the old value.
swap_type = DataType::Type::kUint16;
__ Xor(new_value, new_value, out_or_temp.AsCoreRegister<XRegister>());
}
GenerateReverseBytes(codegen, Location::CoreRegister(new_value), new_value, swap_type); if (is_small) {
__ Sllw(new_value, new_value, shift);
__ Xor(new_value, new_value, old_value);
}
}
GenerateCompareAndSet(assembler,
op_type,
order, /*strong=*/ true, /*cmp_failure=*/ &retry,
tmp_ptr,
new_value, /*old_value=*/ reloaded_old_value, /*mask=*/ kNoXRegister, /*masked=*/ kNoXRegister,
store_result, /*expected=*/ old_value);
} else {
XRegister old_value = is_fp ? get_temp() : out_or_temp.AsCoreRegister<XRegister>();
GenerateGetAndUpdate(
codegen, get_and_update_op, op_type, order, tmp_ptr, arg_reg, old_value, mask, temp); if (byte_swap) {
DCHECK_IMPLIES(is_small, out_or_temp.AsCoreRegister<XRegister>() == old_value) << " "
<< value_type << " " << out_or_temp.AsCoreRegister<XRegister>() << "!=" << old_value;
GenerateByteSwapAndExtract(codegen, out_or_temp, old_value, shift, value_type);
} elseif (is_fp) {
codegen->MoveLocation(out_or_temp, Location::CoreRegister(old_value), value_type);
} elseif (is_small) {
__ Srlw(old_value, old_value, shift);
DCHECK_NE(value_type, DataType::Type::kUint8); if (value_type == DataType::Type::kInt8) {
__ SextB(old_value, old_value);
} elseif (value_type == DataType::Type::kBool) {
__ ZextB(old_value, old_value);
} elseif (value_type == DataType::Type::kInt16) {
__ SextH(old_value, old_value);
} else {
DCHECK_EQ(value_type, DataType::Type::kUint16);
__ ZextH(old_value, old_value);
}
} elseif (is_reference) {
__ ZextW(old_value, old_value); if (codegen->EmitBakerReadBarrier()) { // Use RA as temp. It is clobbered in the slow path anyway. static constexpr Location kBakerReadBarrierTemp = Location::CoreRegister(RA);
SlowPathCodeRISCV64* rb_slow_path = codegen->AddGcRootBakerBarrierBarrierSlowPath(
invoke, out_or_temp, kBakerReadBarrierTemp);
codegen->EmitBakerReadBarierMarkingCheck(rb_slow_path, out_or_temp, kBakerReadBarrierTemp);
} elseif (codegen->EmitNonBakerReadBarrier()) {
Location base_loc = Location::CoreRegister(target.object);
Location index = Location::CoreRegister(target.offset);
SlowPathCodeRISCV64* rb_slow_path = codegen->AddReadBarrierSlowPath(
invoke, out_or_temp, out_or_temp, base_loc, /*offset=*/ 0u, index);
__ J(rb_slow_path->GetEntryLabel());
__ Bind(rb_slow_path->GetExitLabel());
}
}
}
if (slow_path != nullptr) {
DCHECK(!byte_swap);
__ Bind(slow_path->GetExitLabel());
}
// Check that we have allocated the right number of temps. We may need more registers // for byte swapped CAS in the slow path, so skip this check for the main path in that case. // In the void case, we requested an extra register to mimic the `out` register. const size_t extra_temp_registers = is_void ? 1u : 0u; bool has_byte_swap = (arg_index == 3u) && (!is_reference && data_size != 1u); if ((!has_byte_swap || byte_swap) &&
next_temp != locations->GetTempCount() - extra_temp_registers) { // We allocate a temporary register for the class object for a static field `VarHandle` but // we do not update the `next_temp` if it's otherwise unused after the address calculation.
CHECK_EQ(arg_index, 1u);
CHECK_EQ(next_temp, 1u);
CHECK_EQ(locations->GetTempCount(), 2u + extra_temp_registers);
}
}
// The main path checked that the coordinateType0 is an array class that matches // the class of the actual coordinate argument but it does not match the value type. // Check if the `varhandle` references a ByteArrayViewVarHandle instance.
__ Loadwu(temp, varhandle, class_offset.Int32Value());
codegen->MaybeUnpoisonHeapReference(temp);
codegen->LoadClassRootForIntrinsic(temp2, ClassRoot::kJavaLangInvokeByteArrayViewVarHandle);
__ Bne(temp, temp2, GetEntryLabel());
// Check for array index out of bounds.
__ Loadw(temp, object, array_length_offset.Int32Value());
__ Bgeu(index, temp, GetEntryLabel());
__ Addi(temp2, index, size - 1u);
__ Bgeu(temp2, temp, GetEntryLabel());
// Construct the target.
__ Addi(target.offset, index, data_offset.Int32Value());
// Alignment check. For unaligned access, go to the runtime.
DCHECK(IsPowerOfTwo(size));
__ Andi(temp, target.offset, size - 1u);
__ Bnez(temp, GetEntryLabel());
// Byte order check. For native byte order return to the main path. if (access_mode_template == mirror::VarHandle::AccessModeTemplate::kSet &&
IsZeroBitPattern(invoke->InputAt(invoke->GetNumberOfArguments() - 1u))) { // There is no reason to differentiate between native byte order and byte-swap // for setting a zero bit pattern. Just return to the main path.
__ J(GetNativeByteOrderLabel()); return;
}
__ Loadbu(temp, varhandle, native_byte_order_offset.Int32Value());
__ Bnez(temp, GetNativeByteOrderLabel());
}
switch (access_mode_template) { case mirror::VarHandle::AccessModeTemplate::kGet:
GenerateVarHandleGet(invoke, codegen, order_, /*byte_swap=*/ true); break; case mirror::VarHandle::AccessModeTemplate::kSet:
GenerateVarHandleSet(invoke, codegen, order_, /*byte_swap=*/ true); break; case mirror::VarHandle::AccessModeTemplate::kCompareAndSet: case mirror::VarHandle::AccessModeTemplate::kCompareAndExchange:
GenerateVarHandleCompareAndSetOrExchange(
invoke, codegen, order_, return_success_, strong_, /*byte_swap=*/ true); break; case mirror::VarHandle::AccessModeTemplate::kGetAndUpdate:
GenerateVarHandleGetAndUpdate(
invoke, codegen, get_and_update_op_, order_, /*byte_swap=*/ true); break;
}
__ J(GetExitLabel());
}
XRegister x = locations->InAt(0).AsCoreRegister<XRegister>();
XRegister y = locations->InAt(1).AsCoreRegister<XRegister>();
XRegister out = locations->Out().AsCoreRegister<XRegister>();
// Get high 64 of the multiply
__ Mulh(out, x, y);
}
// In Java sizeof(Char) is 2.
constexpr size_t char_size = DataType::Size(DataType::Type::kUint16);
static_assert(char_size == 2u);
// Location of data in the destination char array buffer. const uint32_t array_data_offset = mirror::Array::DataOffset(char_size).Uint32Value();
// Location of char array data in the source string. const uint32_t string_value_offset = mirror::String::ValueOffset().Uint32Value();
// void getCharsNoCheck(int srcBegin, int srcEnd, char[] dst, int dstBegin);
// The source string.
XRegister source_string_object = locations->InAt(0).AsCoreRegister<XRegister>(); // Index of the first character.
XRegister source_begin_index = locations->InAt(1).AsCoreRegister<XRegister>(); // Index that immediately follows the last character.
XRegister source_end_index = locations->InAt(2).AsCoreRegister<XRegister>(); // The destination array.
XRegister destination_array_object = locations->InAt(3).AsCoreRegister<XRegister>(); // The start offset in the destination array.
XRegister destination_begin_offset = locations->InAt(4).AsCoreRegister<XRegister>();
// Calculate the length(number_of_chars) of the string.
__ Subw(number_of_chars, source_end_index, source_begin_index);
// If the string has zero length then exit.
__ Beqz(number_of_chars, &done);
// Prepare a register with the destination address // to start copying to the address: // 1. set the address from which the data in the // destination array begins (destination_array_object + array_data_offset);
__ Addi(destination_ptr, destination_array_object, array_data_offset); // 2. it is necessary to add the start offset relative to the beginning // of the data in the destination array, // yet, due to sizeof(Char) being 2, formerly scaling must be performed // (destination_begin_offset * 2 that equals to destination_begin_offset << 1);
__ Sh1Add(destination_ptr, destination_begin_offset, destination_ptr);
// Prepare a register with the source address // to start copying from the address: // 1. set the address from which the data in the // source string begins (source_string_object + string_value_offset). // Other manipulations will be performed later, // since they depend on whether the string is compressed or not.
__ Addi(source_ptr, source_string_object, string_value_offset);
// The string can be compressed. It is a way to store strings more compactly. // In this instance, every character is located in one byte (instead of two).
Riscv64Label compressed_string_preloop;
// Information about whether the string is compressed or not is located // in the area intended for storing the length of the string. // The least significant bit of the string's length is used // as the compression flag if STRING_COMPRESSION_ENABLED. if (mirror::kUseStringCompression) { // Location of count in string. const uint32_t count_offset = mirror::String::CountOffset().Uint32Value(); // String's length.
__ Loadwu(tmp, source_string_object, count_offset);
// Checking the string for compression. // If so, move to the "compressed_string_preloop".
__ Andi(tmp, tmp, 0x1);
__ Beqz(tmp, &compressed_string_preloop);
}
// Continue preparing the source register: // proceed similarly to what was done for the destination register.
__ Sh1Add(source_ptr, source_begin_index, source_ptr);
// If the string is not compressed, then perform ordinary copying. // Copying will occur 4 characters (8 bytes) at a time, immediately after there are // less than 4 characters left, move to the "remainder_loop" and copy the remaining // characters one character (2 bytes) at a time. // Note: Unaligned addresses are acceptable here and it is not required to embed // additional code to correct them.
Riscv64Label main_loop;
Riscv64Label remainder_loop;
// If initially there are less than 4 characters, // then we directly calculate the remainder.
__ Addi(tmp, number_of_chars, -4);
__ Bltz(tmp, &remainder_loop);
// Otherwise, save the value to the counter and continue.
__ Mv(number_of_chars, tmp);
// Main loop. Loads and stores 4 16-bit Java characters at a time.
__ Bind(&main_loop);
Riscv64Label compressed_string_loop; if (mirror::kUseStringCompression) {
__ J(&done);
// Below is the copying under the string compression circumstance mentioned above. // Every character in the source string occupies only one byte (instead of two).
constexpr size_t compressed_char_size = DataType::Size(DataType::Type::kInt8);
static_assert(compressed_char_size == 1u);
__ Bind(&compressed_string_preloop);
// Continue preparing the source register: // proceed identically to what was done for the destination register, // yet take into account that only one byte yields for every source character, // hence we need to extend it to two ones when copying it to the destination address. // Against this background scaling for source_begin_index is not needed.
__ Add(source_ptr, source_ptr, source_begin_index);
// Copy loop for compressed strings. Copying one 8-bit character to 16-bit one at a time.
__ Bind(&compressed_string_loop);
// Accomodating LocationSummary for underlying invoke-* call.
uint32_t number_of_args = invoke->GetNumberOfArguments(); for (uint32_t i = 1; i < number_of_args; ++i) {
locations->SetInAt(i, calling_convention.GetNextLocation(invoke->InputAt(i)->GetType()));
}
// Passing MethodHandle object as the last parameter: accessors implementation rely on it.
DCHECK_EQ(invoke->InputAt(0)->GetType(), DataType::Type::kReference);
Location receiver_mh_loc = calling_convention.GetNextLocation(DataType::Type::kReference);
locations->SetInAt(0, receiver_mh_loc);
if (invoke->AsInvokePolymorphic()->NeedsCallSiteTypeCheck()) { // The last input is MethodType object corresponding to the call-site.
locations->SetInAt(number_of_args, Location::RequiresCoreRegister());
}
if (invoke->AsInvokePolymorphic()->NeedsCallSiteTypeCheck()) {
XRegister call_site_type =
locations->InAt(invoke->GetNumberOfArguments()).AsCoreRegister<XRegister>();
// Call site should match with MethodHandle's type.
__ Loadwu(temp, method_handle, mirror::MethodHandle::MethodTypeOffset().Int32Value());
codegen_->MaybeUnpoisonHeapReference(temp);
__ Bne(call_site_type, temp, slow_path->GetEntryLabel());
}
if (invoke->AsInvokePolymorphic()->CanTargetInstanceMethod()) {
XRegister receiver = locations->InAt(1).AsCoreRegister<XRegister>();
// Receiver shouldn't be null for all the following cases.
__ Beqz(receiver, slow_path->GetEntryLabel());
__ Li(temp, mirror::MethodHandle::Kind::kInvokeDirect); // No dispatch is needed for invoke-direct.
__ Beq(method_handle_kind, temp, &execute_target_method);
// Skip virtual dispatch if `method` is private.
__ Loadwu(temp, method, ArtMethod::AccessFlagsOffset().Int32Value());
__ Andi(temp, temp, kAccPrivate);
__ Bnez(temp, &execute_target_method);
XRegister receiver_class = locations->GetTemp(2).AsCoreRegister<XRegister>(); // If method is defined in the receiver's class, execute it as it is.
__ Loadwu(temp, method, ArtMethod::DeclaringClassOffset().Int32Value());
__ Loadwu(receiver_class, receiver, mirror::Object::ClassOffset().Int32Value());
codegen_->MaybeUnpoisonHeapReference(receiver_class);
// We're not emitting the read barrier for the receiver_class, so false negatives just go // through the virtual dispath below.
__ Beq(temp, receiver_class, &execute_target_method);
// MethodIndex is uint16_t.
__ Loadhu(temp, method, ArtMethod::MethodIndexOffset().Int32Value());
// Skip virtual dispatch if `method` is private. // Re-use method_handle_kind to store access flags.
XRegister access_flags = locations->GetTemp(2).AsCoreRegister<XRegister>();
__ Loadwu(access_flags, method, ArtMethod::AccessFlagsOffset().Int32Value());
__ Andi(temp, access_flags, kAccPrivate);
__ Bnez(temp, &execute_target_method);
// The register T0 is required to be used for the hidden argument in // art_quick_imt_conflict_trampoline. So prevent the assembler from using it.
ScratchRegisterScope srs(assembler);
srs.ExcludeXRegister(T0);
// Get IMT index. // Not doing default conflict check as IMT index is set for all method which have // kAccAbstract bit.
__ Andi(temp, access_flags, kAccAbstract);
__ Beqz(temp, &get_imt_index_from_method_index);
// imt_index is uint16_t
__ Loadhu(temp, method, ArtMethod::ImtIndexOffset().Int32Value());
__ J(&do_imt_dispatch);
__ Bind(&do_imt_dispatch); // Re-using `method` to store receiver class and ImTableEntry.
__ Loadd(method, receiver, mirror::Object::ClassOffset().Int32Value());
codegen_->MaybeUnpoisonHeapReference(method);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.