blob: 8b6328f097ca53d9f657916707a568082ada2032 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "code_generator_mips64.h"
#include "arch/mips64/asm_support_mips64.h"
#include "art_method.h"
#include "class_table.h"
#include "code_generator_utils.h"
#include "compiled_method.h"
#include "entrypoints/quick/quick_entrypoints.h"
#include "entrypoints/quick/quick_entrypoints_enum.h"
#include "gc/accounting/card_table.h"
#include "gc/space/image_space.h"
#include "heap_poisoning.h"
#include "intrinsics.h"
#include "intrinsics_mips64.h"
#include "linker/linker_patch.h"
#include "mirror/array-inl.h"
#include "mirror/class-inl.h"
#include "offsets.h"
#include "stack_map_stream.h"
#include "thread.h"
#include "utils/assembler.h"
#include "utils/mips64/assembler_mips64.h"
#include "utils/stack_checks.h"
namespace art {
namespace mips64 {
static constexpr int kCurrentMethodStackOffset = 0;
static constexpr GpuRegister kMethodRegisterArgument = A0;
// Flags controlling the use of thunks for Baker read barriers.
constexpr bool kBakerReadBarrierThunksEnableForFields = true;
constexpr bool kBakerReadBarrierThunksEnableForArrays = true;
constexpr bool kBakerReadBarrierThunksEnableForGcRoots = true;
Location Mips64ReturnLocation(DataType::Type return_type) {
switch (return_type) {
case DataType::Type::kBool:
case DataType::Type::kUint8:
case DataType::Type::kInt8:
case DataType::Type::kUint16:
case DataType::Type::kInt16:
case DataType::Type::kUint32:
case DataType::Type::kInt32:
case DataType::Type::kReference:
case DataType::Type::kUint64:
case DataType::Type::kInt64:
return Location::RegisterLocation(V0);
case DataType::Type::kFloat32:
case DataType::Type::kFloat64:
return Location::FpuRegisterLocation(F0);
case DataType::Type::kVoid:
return Location();
}
UNREACHABLE();
}
Location InvokeDexCallingConventionVisitorMIPS64::GetReturnLocation(DataType::Type type) const {
return Mips64ReturnLocation(type);
}
Location InvokeDexCallingConventionVisitorMIPS64::GetMethodLocation() const {
return Location::RegisterLocation(kMethodRegisterArgument);
}
Location InvokeDexCallingConventionVisitorMIPS64::GetNextLocation(DataType::Type type) {
Location next_location;
if (type == DataType::Type::kVoid) {
LOG(FATAL) << "Unexpected parameter type " << type;
}
if (DataType::IsFloatingPointType(type) &&
(float_index_ < calling_convention.GetNumberOfFpuRegisters())) {
next_location = Location::FpuRegisterLocation(
calling_convention.GetFpuRegisterAt(float_index_++));
gp_index_++;
} else if (!DataType::IsFloatingPointType(type) &&
(gp_index_ < calling_convention.GetNumberOfRegisters())) {
next_location = Location::RegisterLocation(calling_convention.GetRegisterAt(gp_index_++));
float_index_++;
} else {
size_t stack_offset = calling_convention.GetStackOffsetOf(stack_index_);
next_location = DataType::Is64BitType(type) ? Location::DoubleStackSlot(stack_offset)
: Location::StackSlot(stack_offset);
}
// Space on the stack is reserved for all arguments.
stack_index_ += DataType::Is64BitType(type) ? 2 : 1;
return next_location;
}
Location InvokeRuntimeCallingConvention::GetReturnLocation(DataType::Type type) {
return Mips64ReturnLocation(type);
}
static RegisterSet OneRegInReferenceOutSaveEverythingCallerSaves() {
InvokeRuntimeCallingConvention calling_convention;
RegisterSet caller_saves = RegisterSet::Empty();
caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0)));
// The reference is returned in the same register. This differs from the standard return location.
return caller_saves;
}
// NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
#define __ down_cast<CodeGeneratorMIPS64*>(codegen)->GetAssembler()-> // NOLINT
#define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kMips64PointerSize, x).Int32Value()
class BoundsCheckSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
explicit BoundsCheckSlowPathMIPS64(HBoundsCheck* instruction) : SlowPathCodeMIPS64(instruction) {}
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
__ Bind(GetEntryLabel());
if (instruction_->CanThrowIntoCatchBlock()) {
// Live registers will be restored in the catch block if caught.
SaveLiveRegisters(codegen, instruction_->GetLocations());
}
// We're moving two locations to locations that could overlap, so we need a parallel
// move resolver.
InvokeRuntimeCallingConvention calling_convention;
codegen->EmitParallelMoves(locations->InAt(0),
Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
DataType::Type::kInt32,
locations->InAt(1),
Location::RegisterLocation(calling_convention.GetRegisterAt(1)),
DataType::Type::kInt32);
QuickEntrypointEnum entrypoint = instruction_->AsBoundsCheck()->IsStringCharAt()
? kQuickThrowStringBounds
: kQuickThrowArrayBounds;
mips64_codegen->InvokeRuntime(entrypoint, instruction_, instruction_->GetDexPc(), this);
CheckEntrypointTypes<kQuickThrowStringBounds, void, int32_t, int32_t>();
CheckEntrypointTypes<kQuickThrowArrayBounds, void, int32_t, int32_t>();
}
bool IsFatal() const override { return true; }
const char* GetDescription() const override { return "BoundsCheckSlowPathMIPS64"; }
private:
DISALLOW_COPY_AND_ASSIGN(BoundsCheckSlowPathMIPS64);
};
class DivZeroCheckSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
explicit DivZeroCheckSlowPathMIPS64(HDivZeroCheck* instruction)
: SlowPathCodeMIPS64(instruction) {}
void EmitNativeCode(CodeGenerator* codegen) override {
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
__ Bind(GetEntryLabel());
mips64_codegen->InvokeRuntime(kQuickThrowDivZero, instruction_, instruction_->GetDexPc(), this);
CheckEntrypointTypes<kQuickThrowDivZero, void, void>();
}
bool IsFatal() const override { return true; }
const char* GetDescription() const override { return "DivZeroCheckSlowPathMIPS64"; }
private:
DISALLOW_COPY_AND_ASSIGN(DivZeroCheckSlowPathMIPS64);
};
class LoadClassSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
LoadClassSlowPathMIPS64(HLoadClass* cls, HInstruction* at)
: SlowPathCodeMIPS64(at), cls_(cls) {
DCHECK(at->IsLoadClass() || at->IsClinitCheck());
DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_);
}
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
Location out = locations->Out();
const uint32_t dex_pc = instruction_->GetDexPc();
bool must_resolve_type = instruction_->IsLoadClass() && cls_->MustResolveTypeOnSlowPath();
bool must_do_clinit = instruction_->IsClinitCheck() || cls_->MustGenerateClinitCheck();
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
__ Bind(GetEntryLabel());
SaveLiveRegisters(codegen, locations);
InvokeRuntimeCallingConvention calling_convention;
if (must_resolve_type) {
DCHECK(IsSameDexFile(cls_->GetDexFile(), mips64_codegen->GetGraph()->GetDexFile()));
dex::TypeIndex type_index = cls_->GetTypeIndex();
__ LoadConst32(calling_convention.GetRegisterAt(0), type_index.index_);
mips64_codegen->InvokeRuntime(kQuickResolveType, instruction_, dex_pc, this);
CheckEntrypointTypes<kQuickResolveType, void*, uint32_t>();
// If we also must_do_clinit, the resolved type is now in the correct register.
} else {
DCHECK(must_do_clinit);
Location source = instruction_->IsLoadClass() ? out : locations->InAt(0);
mips64_codegen->MoveLocation(Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
source,
cls_->GetType());
}
if (must_do_clinit) {
mips64_codegen->InvokeRuntime(kQuickInitializeStaticStorage, instruction_, dex_pc, this);
CheckEntrypointTypes<kQuickInitializeStaticStorage, void*, mirror::Class*>();
}
// Move the class to the desired location.
if (out.IsValid()) {
DCHECK(out.IsRegister() && !locations->GetLiveRegisters()->ContainsCoreRegister(out.reg()));
DataType::Type type = instruction_->GetType();
mips64_codegen->MoveLocation(out,
Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
type);
}
RestoreLiveRegisters(codegen, locations);
__ Bc(GetExitLabel());
}
const char* GetDescription() const override { return "LoadClassSlowPathMIPS64"; }
private:
// The class this slow path will load.
HLoadClass* const cls_;
DISALLOW_COPY_AND_ASSIGN(LoadClassSlowPathMIPS64);
};
class LoadStringSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
explicit LoadStringSlowPathMIPS64(HLoadString* instruction)
: SlowPathCodeMIPS64(instruction) {}
void EmitNativeCode(CodeGenerator* codegen) override {
DCHECK(instruction_->IsLoadString());
DCHECK_EQ(instruction_->AsLoadString()->GetLoadKind(), HLoadString::LoadKind::kBssEntry);
LocationSummary* locations = instruction_->GetLocations();
DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg()));
const dex::StringIndex string_index = instruction_->AsLoadString()->GetStringIndex();
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
InvokeRuntimeCallingConvention calling_convention;
__ Bind(GetEntryLabel());
SaveLiveRegisters(codegen, locations);
__ LoadConst32(calling_convention.GetRegisterAt(0), string_index.index_);
mips64_codegen->InvokeRuntime(kQuickResolveString,
instruction_,
instruction_->GetDexPc(),
this);
CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>();
DataType::Type type = instruction_->GetType();
mips64_codegen->MoveLocation(locations->Out(),
Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
type);
RestoreLiveRegisters(codegen, locations);
__ Bc(GetExitLabel());
}
const char* GetDescription() const override { return "LoadStringSlowPathMIPS64"; }
private:
DISALLOW_COPY_AND_ASSIGN(LoadStringSlowPathMIPS64);
};
class NullCheckSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
explicit NullCheckSlowPathMIPS64(HNullCheck* instr) : SlowPathCodeMIPS64(instr) {}
void EmitNativeCode(CodeGenerator* codegen) override {
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
__ Bind(GetEntryLabel());
if (instruction_->CanThrowIntoCatchBlock()) {
// Live registers will be restored in the catch block if caught.
SaveLiveRegisters(codegen, instruction_->GetLocations());
}
mips64_codegen->InvokeRuntime(kQuickThrowNullPointer,
instruction_,
instruction_->GetDexPc(),
this);
CheckEntrypointTypes<kQuickThrowNullPointer, void, void>();
}
bool IsFatal() const override { return true; }
const char* GetDescription() const override { return "NullCheckSlowPathMIPS64"; }
private:
DISALLOW_COPY_AND_ASSIGN(NullCheckSlowPathMIPS64);
};
class SuspendCheckSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
SuspendCheckSlowPathMIPS64(HSuspendCheck* instruction, HBasicBlock* successor)
: SlowPathCodeMIPS64(instruction), successor_(successor) {}
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
__ Bind(GetEntryLabel());
SaveLiveRegisters(codegen, locations); // Only saves live vector registers for SIMD.
mips64_codegen->InvokeRuntime(kQuickTestSuspend, instruction_, instruction_->GetDexPc(), this);
CheckEntrypointTypes<kQuickTestSuspend, void, void>();
RestoreLiveRegisters(codegen, locations); // Only restores live vector registers for SIMD.
if (successor_ == nullptr) {
__ Bc(GetReturnLabel());
} else {
__ Bc(mips64_codegen->GetLabelOf(successor_));
}
}
Mips64Label* GetReturnLabel() {
DCHECK(successor_ == nullptr);
return &return_label_;
}
const char* GetDescription() const override { return "SuspendCheckSlowPathMIPS64"; }
HBasicBlock* GetSuccessor() const {
return successor_;
}
private:
// If not null, the block to branch to after the suspend check.
HBasicBlock* const successor_;
// If `successor_` is null, the label to branch to after the suspend check.
Mips64Label return_label_;
DISALLOW_COPY_AND_ASSIGN(SuspendCheckSlowPathMIPS64);
};
class TypeCheckSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
explicit TypeCheckSlowPathMIPS64(HInstruction* instruction, bool is_fatal)
: SlowPathCodeMIPS64(instruction), is_fatal_(is_fatal) {}
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
uint32_t dex_pc = instruction_->GetDexPc();
DCHECK(instruction_->IsCheckCast()
|| !locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg()));
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
__ Bind(GetEntryLabel());
if (!is_fatal_ || instruction_->CanThrowIntoCatchBlock()) {
SaveLiveRegisters(codegen, locations);
}
// We're moving two locations to locations that could overlap, so we need a parallel
// move resolver.
InvokeRuntimeCallingConvention calling_convention;
codegen->EmitParallelMoves(locations->InAt(0),
Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
DataType::Type::kReference,
locations->InAt(1),
Location::RegisterLocation(calling_convention.GetRegisterAt(1)),
DataType::Type::kReference);
if (instruction_->IsInstanceOf()) {
mips64_codegen->InvokeRuntime(kQuickInstanceofNonTrivial, instruction_, dex_pc, this);
CheckEntrypointTypes<kQuickInstanceofNonTrivial, size_t, mirror::Object*, mirror::Class*>();
DataType::Type ret_type = instruction_->GetType();
Location ret_loc = calling_convention.GetReturnLocation(ret_type);
mips64_codegen->MoveLocation(locations->Out(), ret_loc, ret_type);
} else {
DCHECK(instruction_->IsCheckCast());
mips64_codegen->InvokeRuntime(kQuickCheckInstanceOf, instruction_, dex_pc, this);
CheckEntrypointTypes<kQuickCheckInstanceOf, void, mirror::Object*, mirror::Class*>();
}
if (!is_fatal_) {
RestoreLiveRegisters(codegen, locations);
__ Bc(GetExitLabel());
}
}
const char* GetDescription() const override { return "TypeCheckSlowPathMIPS64"; }
bool IsFatal() const override { return is_fatal_; }
private:
const bool is_fatal_;
DISALLOW_COPY_AND_ASSIGN(TypeCheckSlowPathMIPS64);
};
class DeoptimizationSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
explicit DeoptimizationSlowPathMIPS64(HDeoptimize* instruction)
: SlowPathCodeMIPS64(instruction) {}
void EmitNativeCode(CodeGenerator* codegen) override {
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
__ Bind(GetEntryLabel());
LocationSummary* locations = instruction_->GetLocations();
SaveLiveRegisters(codegen, locations);
InvokeRuntimeCallingConvention calling_convention;
__ LoadConst32(calling_convention.GetRegisterAt(0),
static_cast<uint32_t>(instruction_->AsDeoptimize()->GetDeoptimizationKind()));
mips64_codegen->InvokeRuntime(kQuickDeoptimize, instruction_, instruction_->GetDexPc(), this);
CheckEntrypointTypes<kQuickDeoptimize, void, DeoptimizationKind>();
}
const char* GetDescription() const override { return "DeoptimizationSlowPathMIPS64"; }
private:
DISALLOW_COPY_AND_ASSIGN(DeoptimizationSlowPathMIPS64);
};
class ArraySetSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
explicit ArraySetSlowPathMIPS64(HInstruction* instruction) : SlowPathCodeMIPS64(instruction) {}
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
__ Bind(GetEntryLabel());
SaveLiveRegisters(codegen, locations);
InvokeRuntimeCallingConvention calling_convention;
HParallelMove parallel_move(codegen->GetGraph()->GetAllocator());
parallel_move.AddMove(
locations->InAt(0),
Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
DataType::Type::kReference,
nullptr);
parallel_move.AddMove(
locations->InAt(1),
Location::RegisterLocation(calling_convention.GetRegisterAt(1)),
DataType::Type::kInt32,
nullptr);
parallel_move.AddMove(
locations->InAt(2),
Location::RegisterLocation(calling_convention.GetRegisterAt(2)),
DataType::Type::kReference,
nullptr);
codegen->GetMoveResolver()->EmitNativeCode(&parallel_move);
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
mips64_codegen->InvokeRuntime(kQuickAputObject, instruction_, instruction_->GetDexPc(), this);
CheckEntrypointTypes<kQuickAputObject, void, mirror::Array*, int32_t, mirror::Object*>();
RestoreLiveRegisters(codegen, locations);
__ Bc(GetExitLabel());
}
const char* GetDescription() const override { return "ArraySetSlowPathMIPS64"; }
private:
DISALLOW_COPY_AND_ASSIGN(ArraySetSlowPathMIPS64);
};
// Slow path marking an object reference `ref` during a read
// barrier. The field `obj.field` in the object `obj` holding this
// reference does not get updated by this slow path after marking (see
// ReadBarrierMarkAndUpdateFieldSlowPathMIPS64 below for that).
//
// This means that after the execution of this slow path, `ref` will
// always be up-to-date, but `obj.field` may not; i.e., after the
// flip, `ref` will be a to-space reference, but `obj.field` will
// probably still be a from-space reference (unless it gets updated by
// another thread, or if another thread installed another object
// reference (different from `ref`) in `obj.field`).
//
// If `entrypoint` is a valid location it is assumed to already be
// holding the entrypoint. The case where the entrypoint is passed in
// is for the GcRoot read barrier.
class ReadBarrierMarkSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
ReadBarrierMarkSlowPathMIPS64(HInstruction* instruction,
Location ref,
Location entrypoint = Location::NoLocation())
: SlowPathCodeMIPS64(instruction), ref_(ref), entrypoint_(entrypoint) {
DCHECK(kEmitCompilerReadBarrier);
}
const char* GetDescription() const override { return "ReadBarrierMarkSlowPathMIPS"; }
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
GpuRegister ref_reg = ref_.AsRegister<GpuRegister>();
DCHECK(locations->CanCall());
DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_reg)) << ref_reg;
DCHECK(instruction_->IsInstanceFieldGet() ||
instruction_->IsStaticFieldGet() ||
instruction_->IsArrayGet() ||
instruction_->IsArraySet() ||
instruction_->IsLoadClass() ||
instruction_->IsLoadString() ||
instruction_->IsInstanceOf() ||
instruction_->IsCheckCast() ||
(instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()) ||
(instruction_->IsInvokeStaticOrDirect() && instruction_->GetLocations()->Intrinsified()))
<< "Unexpected instruction in read barrier marking slow path: "
<< instruction_->DebugName();
__ Bind(GetEntryLabel());
// 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.
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
DCHECK((V0 <= ref_reg && ref_reg <= T2) ||
(S2 <= ref_reg && ref_reg <= S7) ||
(ref_reg == S8)) << ref_reg;
// "Compact" slow path, saving two moves.
//
// Instead of using the standard runtime calling convention (input
// and output in A0 and V0 respectively):
//
// A0 <- ref
// V0 <- ReadBarrierMark(A0)
// ref <- V0
//
// we just use rX (the register containing `ref`) as input and output
// of a dedicated entrypoint:
//
// rX <- ReadBarrierMarkRegX(rX)
//
if (entrypoint_.IsValid()) {
mips64_codegen->ValidateInvokeRuntimeWithoutRecordingPcInfo(instruction_, this);
DCHECK_EQ(entrypoint_.AsRegister<GpuRegister>(), T9);
__ Jalr(entrypoint_.AsRegister<GpuRegister>());
__ Nop();
} else {
int32_t entry_point_offset =
Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(ref_reg - 1);
// This runtime call does not require a stack map.
mips64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset,
instruction_,
this);
}
__ Bc(GetExitLabel());
}
private:
// The location (register) of the marked object reference.
const Location ref_;
// The location of the entrypoint if already loaded.
const Location entrypoint_;
DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkSlowPathMIPS64);
};
// Slow path marking an object reference `ref` during a read barrier,
// and if needed, atomically updating the field `obj.field` in the
// object `obj` holding this reference after marking (contrary to
// ReadBarrierMarkSlowPathMIPS64 above, which never tries to update
// `obj.field`).
//
// This means that after the execution of this slow path, both `ref`
// and `obj.field` will be up-to-date; i.e., after the flip, both will
// hold the same to-space reference (unless another thread installed
// another object reference (different from `ref`) in `obj.field`).
class ReadBarrierMarkAndUpdateFieldSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
ReadBarrierMarkAndUpdateFieldSlowPathMIPS64(HInstruction* instruction,
Location ref,
GpuRegister obj,
Location field_offset,
GpuRegister temp1)
: SlowPathCodeMIPS64(instruction),
ref_(ref),
obj_(obj),
field_offset_(field_offset),
temp1_(temp1) {
DCHECK(kEmitCompilerReadBarrier);
}
const char* GetDescription() const override {
return "ReadBarrierMarkAndUpdateFieldSlowPathMIPS64";
}
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
GpuRegister ref_reg = ref_.AsRegister<GpuRegister>();
DCHECK(locations->CanCall());
DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_reg)) << ref_reg;
// This slow path is only used by the UnsafeCASObject intrinsic.
DCHECK((instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()))
<< "Unexpected instruction in read barrier marking and field updating slow path: "
<< instruction_->DebugName();
DCHECK(instruction_->GetLocations()->Intrinsified());
DCHECK_EQ(instruction_->AsInvoke()->GetIntrinsic(), Intrinsics::kUnsafeCASObject);
DCHECK(field_offset_.IsRegister()) << field_offset_;
__ Bind(GetEntryLabel());
// Save the old reference.
// Note that we cannot use AT or TMP to save the old reference, as those
// are used by the code that follows, but we need the old reference after
// the call to the ReadBarrierMarkRegX entry point.
DCHECK_NE(temp1_, AT);
DCHECK_NE(temp1_, TMP);
__ Move(temp1_, ref_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.
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
DCHECK((V0 <= ref_reg && ref_reg <= T2) ||
(S2 <= ref_reg && ref_reg <= S7) ||
(ref_reg == S8)) << ref_reg;
// "Compact" slow path, saving two moves.
//
// Instead of using the standard runtime calling convention (input
// and output in A0 and V0 respectively):
//
// A0 <- ref
// V0 <- ReadBarrierMark(A0)
// ref <- V0
//
// we just use rX (the register containing `ref`) as input and output
// of a dedicated entrypoint:
//
// rX <- ReadBarrierMarkRegX(rX)
//
int32_t entry_point_offset =
Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(ref_reg - 1);
// This runtime call does not require a stack map.
mips64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset,
instruction_,
this);
// If the new reference is different from the old reference,
// update the field in the holder (`*(obj_ + field_offset_)`).
//
// Note that this field could also hold a different object, if
// another thread had concurrently changed it. In that case, the
// the compare-and-set (CAS) loop below would abort, leaving the
// field as-is.
Mips64Label done;
__ Beqc(temp1_, ref_reg, &done);
// Update the the holder's field atomically. This may fail if
// mutator updates before us, but it's OK. This is achieved
// using a strong compare-and-set (CAS) operation with relaxed
// memory synchronization ordering, where the expected value is
// the old reference and the desired value is the new reference.
// Convenience aliases.
GpuRegister base = obj_;
GpuRegister offset = field_offset_.AsRegister<GpuRegister>();
GpuRegister expected = temp1_;
GpuRegister value = ref_reg;
GpuRegister tmp_ptr = TMP; // Pointer to actual memory.
GpuRegister tmp = AT; // Value in memory.
__ Daddu(tmp_ptr, base, offset);
if (kPoisonHeapReferences) {
__ PoisonHeapReference(expected);
// Do not poison `value` if it is the same register as
// `expected`, which has just been poisoned.
if (value != expected) {
__ PoisonHeapReference(value);
}
}
// do {
// tmp = [r_ptr] - expected;
// } while (tmp == 0 && failure([r_ptr] <- r_new_value));
Mips64Label loop_head, exit_loop;
__ Bind(&loop_head);
__ Ll(tmp, tmp_ptr);
// The LL instruction sign-extends the 32-bit value, but
// 32-bit references must be zero-extended. Zero-extend `tmp`.
__ Dext(tmp, tmp, 0, 32);
__ Bnec(tmp, expected, &exit_loop);
__ Move(tmp, value);
__ Sc(tmp, tmp_ptr);
__ Beqzc(tmp, &loop_head);
__ Bind(&exit_loop);
if (kPoisonHeapReferences) {
__ UnpoisonHeapReference(expected);
// Do not unpoison `value` if it is the same register as
// `expected`, which has just been unpoisoned.
if (value != expected) {
__ UnpoisonHeapReference(value);
}
}
__ Bind(&done);
__ Bc(GetExitLabel());
}
private:
// The location (register) of the marked object reference.
const Location ref_;
// The register containing the object holding the marked object reference field.
const GpuRegister obj_;
// The location of the offset of the marked reference field within `obj_`.
Location field_offset_;
const GpuRegister temp1_;
DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkAndUpdateFieldSlowPathMIPS64);
};
// Slow path generating a read barrier for a heap reference.
class ReadBarrierForHeapReferenceSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
ReadBarrierForHeapReferenceSlowPathMIPS64(HInstruction* instruction,
Location out,
Location ref,
Location obj,
uint32_t offset,
Location index)
: SlowPathCodeMIPS64(instruction),
out_(out),
ref_(ref),
obj_(obj),
offset_(offset),
index_(index) {
DCHECK(kEmitCompilerReadBarrier);
// If `obj` is equal to `out` or `ref`, it means the initial object
// has been overwritten by (or after) the heap object reference load
// to be instrumented, e.g.:
//
// __ LoadFromOffset(kLoadWord, out, out, offset);
// codegen_->GenerateReadBarrierSlow(instruction, out_loc, out_loc, out_loc, offset);
//
// In that case, we have lost the information about the original
// object, and the emitted read barrier cannot work properly.
DCHECK(!obj.Equals(out)) << "obj=" << obj << " out=" << out;
DCHECK(!obj.Equals(ref)) << "obj=" << obj << " ref=" << ref;
}
void EmitNativeCode(CodeGenerator* codegen) override {
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
LocationSummary* locations = instruction_->GetLocations();
DataType::Type type = DataType::Type::kReference;
GpuRegister reg_out = out_.AsRegister<GpuRegister>();
DCHECK(locations->CanCall());
DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(reg_out));
DCHECK(instruction_->IsInstanceFieldGet() ||
instruction_->IsStaticFieldGet() ||
instruction_->IsArrayGet() ||
instruction_->IsInstanceOf() ||
instruction_->IsCheckCast() ||
(instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()))
<< "Unexpected instruction in read barrier for heap reference slow path: "
<< instruction_->DebugName();
__ Bind(GetEntryLabel());
SaveLiveRegisters(codegen, locations);
// We may have to change the index's value, but as `index_` is a
// constant member (like other "inputs" of this slow path),
// introduce a copy of it, `index`.
Location index = index_;
if (index_.IsValid()) {
// Handle `index_` for HArrayGet and UnsafeGetObject/UnsafeGetObjectVolatile intrinsics.
if (instruction_->IsArrayGet()) {
// Compute the actual memory offset and store it in `index`.
GpuRegister index_reg = index_.AsRegister<GpuRegister>();
DCHECK(locations->GetLiveRegisters()->ContainsCoreRegister(index_reg));
if (codegen->IsCoreCalleeSaveRegister(index_reg)) {
// We are about to change the value of `index_reg` (see the
// calls to art::mips64::Mips64Assembler::Sll and
// art::mips64::MipsAssembler::Addiu32 below), but it has
// not been saved by the previous call to
// art::SlowPathCode::SaveLiveRegisters, as it is a
// callee-save register --
// art::SlowPathCode::SaveLiveRegisters does not consider
// callee-save registers, as it has been designed with the
// assumption that callee-save registers are supposed to be
// handled by the called function. So, as a callee-save
// register, `index_reg` _would_ eventually be saved onto
// the stack, but it would be too late: we would have
// changed its value earlier. Therefore, we manually save
// it here into another freely available register,
// `free_reg`, chosen of course among the caller-save
// registers (as a callee-save `free_reg` register would
// exhibit the same problem).
//
// Note we could have requested a temporary register from
// the register allocator instead; but we prefer not to, as
// this is a slow path, and we know we can find a
// caller-save register that is available.
GpuRegister free_reg = FindAvailableCallerSaveRegister(codegen);
__ Move(free_reg, index_reg);
index_reg = free_reg;
index = Location::RegisterLocation(index_reg);
} else {
// The initial register stored in `index_` has already been
// saved in the call to art::SlowPathCode::SaveLiveRegisters
// (as it is not a callee-save register), so we can freely
// use it.
}
// Shifting the index value contained in `index_reg` by the scale
// factor (2) cannot overflow in practice, as the runtime is
// unable to allocate object arrays with a size larger than
// 2^26 - 1 (that is, 2^28 - 4 bytes).
__ Sll(index_reg, index_reg, TIMES_4);
static_assert(
sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
"art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
__ Addiu32(index_reg, index_reg, offset_);
} else {
// In the case of the UnsafeGetObject/UnsafeGetObjectVolatile
// intrinsics, `index_` is not shifted by a scale factor of 2
// (as in the case of ArrayGet), as it is actually an offset
// to an object field within an object.
DCHECK(instruction_->IsInvoke()) << instruction_->DebugName();
DCHECK(instruction_->GetLocations()->Intrinsified());
DCHECK((instruction_->AsInvoke()->GetIntrinsic() == Intrinsics::kUnsafeGetObject) ||
(instruction_->AsInvoke()->GetIntrinsic() == Intrinsics::kUnsafeGetObjectVolatile))
<< instruction_->AsInvoke()->GetIntrinsic();
DCHECK_EQ(offset_, 0U);
DCHECK(index_.IsRegister());
}
}
// We're moving two or three locations to locations that could
// overlap, so we need a parallel move resolver.
InvokeRuntimeCallingConvention calling_convention;
HParallelMove parallel_move(codegen->GetGraph()->GetAllocator());
parallel_move.AddMove(ref_,
Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
DataType::Type::kReference,
nullptr);
parallel_move.AddMove(obj_,
Location::RegisterLocation(calling_convention.GetRegisterAt(1)),
DataType::Type::kReference,
nullptr);
if (index.IsValid()) {
parallel_move.AddMove(index,
Location::RegisterLocation(calling_convention.GetRegisterAt(2)),
DataType::Type::kInt32,
nullptr);
codegen->GetMoveResolver()->EmitNativeCode(&parallel_move);
} else {
codegen->GetMoveResolver()->EmitNativeCode(&parallel_move);
__ LoadConst32(calling_convention.GetRegisterAt(2), offset_);
}
mips64_codegen->InvokeRuntime(kQuickReadBarrierSlow,
instruction_,
instruction_->GetDexPc(),
this);
CheckEntrypointTypes<
kQuickReadBarrierSlow, mirror::Object*, mirror::Object*, mirror::Object*, uint32_t>();
mips64_codegen->MoveLocation(out_, calling_convention.GetReturnLocation(type), type);
RestoreLiveRegisters(codegen, locations);
__ Bc(GetExitLabel());
}
const char* GetDescription() const override {
return "ReadBarrierForHeapReferenceSlowPathMIPS64";
}
private:
GpuRegister FindAvailableCallerSaveRegister(CodeGenerator* codegen) {
size_t ref = static_cast<int>(ref_.AsRegister<GpuRegister>());
size_t obj = static_cast<int>(obj_.AsRegister<GpuRegister>());
for (size_t i = 0, e = codegen->GetNumberOfCoreRegisters(); i < e; ++i) {
if (i != ref &&
i != obj &&
!codegen->IsCoreCalleeSaveRegister(i) &&
!codegen->IsBlockedCoreRegister(i)) {
return static_cast<GpuRegister>(i);
}
}
// We shall never fail to find a free caller-save register, as
// there are more than two core caller-save registers on MIPS64
// (meaning it is possible to find one which is different from
// `ref` and `obj`).
DCHECK_GT(codegen->GetNumberOfCoreCallerSaveRegisters(), 2u);
LOG(FATAL) << "Could not find a free caller-save register";
UNREACHABLE();
}
const Location out_;
const Location ref_;
const Location obj_;
const uint32_t offset_;
// An additional location containing an index to an array.
// Only used for HArrayGet and the UnsafeGetObject &
// UnsafeGetObjectVolatile intrinsics.
const Location index_;
DISALLOW_COPY_AND_ASSIGN(ReadBarrierForHeapReferenceSlowPathMIPS64);
};
// Slow path generating a read barrier for a GC root.
class ReadBarrierForRootSlowPathMIPS64 : public SlowPathCodeMIPS64 {
public:
ReadBarrierForRootSlowPathMIPS64(HInstruction* instruction, Location out, Location root)
: SlowPathCodeMIPS64(instruction), out_(out), root_(root) {
DCHECK(kEmitCompilerReadBarrier);
}
void EmitNativeCode(CodeGenerator* codegen) override {
LocationSummary* locations = instruction_->GetLocations();
DataType::Type type = DataType::Type::kReference;
GpuRegister reg_out = out_.AsRegister<GpuRegister>();
DCHECK(locations->CanCall());
DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(reg_out));
DCHECK(instruction_->IsLoadClass() || instruction_->IsLoadString())
<< "Unexpected instruction in read barrier for GC root slow path: "
<< instruction_->DebugName();
__ Bind(GetEntryLabel());
SaveLiveRegisters(codegen, locations);
InvokeRuntimeCallingConvention calling_convention;
CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen);
mips64_codegen->MoveLocation(Location::RegisterLocation(calling_convention.GetRegisterAt(0)),
root_,
DataType::Type::kReference);
mips64_codegen->InvokeRuntime(kQuickReadBarrierForRootSlow,
instruction_,
instruction_->GetDexPc(),
this);
CheckEntrypointTypes<kQuickReadBarrierForRootSlow, mirror::Object*, GcRoot<mirror::Object>*>();
mips64_codegen->MoveLocation(out_, calling_convention.GetReturnLocation(type), type);
RestoreLiveRegisters(codegen, locations);
__ Bc(GetExitLabel());
}
const char* GetDescription() const override { return "ReadBarrierForRootSlowPathMIPS64"; }
private:
const Location out_;
const Location root_;
DISALLOW_COPY_AND_ASSIGN(ReadBarrierForRootSlowPathMIPS64);
};
CodeGeneratorMIPS64::CodeGeneratorMIPS64(HGraph* graph,
const CompilerOptions& compiler_options,
OptimizingCompilerStats* stats)
: CodeGenerator(graph,
kNumberOfGpuRegisters,
kNumberOfFpuRegisters,
/* number_of_register_pairs= */ 0,
ComputeRegisterMask(reinterpret_cast<const int*>(kCoreCalleeSaves),
arraysize(kCoreCalleeSaves)),
ComputeRegisterMask(reinterpret_cast<const int*>(kFpuCalleeSaves),
arraysize(kFpuCalleeSaves)),
compiler_options,
stats),
block_labels_(nullptr),
location_builder_(graph, this),
instruction_visitor_(graph, this),
move_resolver_(graph->GetAllocator(), this),
assembler_(graph->GetAllocator(),
compiler_options.GetInstructionSetFeatures()->AsMips64InstructionSetFeatures()),
uint32_literals_(std::less<uint32_t>(),
graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
uint64_literals_(std::less<uint64_t>(),
graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
boot_image_method_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
method_bss_entry_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
boot_image_type_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
type_bss_entry_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
boot_image_string_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
string_bss_entry_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
boot_image_intrinsic_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
jit_string_patches_(StringReferenceValueComparator(),
graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
jit_class_patches_(TypeReferenceValueComparator(),
graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)) {
// Save RA (containing the return address) to mimic Quick.
AddAllocatedRegister(Location::RegisterLocation(RA));
}
#undef __
// NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
#define __ down_cast<Mips64Assembler*>(GetAssembler())-> // NOLINT
#define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kMips64PointerSize, x).Int32Value()
void CodeGeneratorMIPS64::Finalize(CodeAllocator* allocator) {
// Ensure that we fix up branches.
__ FinalizeCode();
// Adjust native pc offsets in stack maps.
StackMapStream* stack_map_stream = GetStackMapStream();
for (size_t i = 0, num = stack_map_stream->GetNumberOfStackMaps(); i != num; ++i) {
uint32_t old_position = stack_map_stream->GetStackMapNativePcOffset(i);
uint32_t new_position = __ GetAdjustedPosition(old_position);
DCHECK_GE(new_position, old_position);
stack_map_stream->SetStackMapNativePcOffset(i, new_position);
}
// Adjust pc offsets for the disassembly information.
if (disasm_info_ != nullptr) {
GeneratedCodeInterval* frame_entry_interval = disasm_info_->GetFrameEntryInterval();
frame_entry_interval->start = __ GetAdjustedPosition(frame_entry_interval->start);
frame_entry_interval->end = __ GetAdjustedPosition(frame_entry_interval->end);
for (auto& it : *disasm_info_->GetInstructionIntervals()) {
it.second.start = __ GetAdjustedPosition(it.second.start);
it.second.end = __ GetAdjustedPosition(it.second.end);
}
for (auto& it : *disasm_info_->GetSlowPathIntervals()) {
it.code_interval.start = __ GetAdjustedPosition(it.code_interval.start);
it.code_interval.end = __ GetAdjustedPosition(it.code_interval.end);
}
}
CodeGenerator::Finalize(allocator);
}
Mips64Assembler* ParallelMoveResolverMIPS64::GetAssembler() const {
return codegen_->GetAssembler();
}
void ParallelMoveResolverMIPS64::EmitMove(size_t index) {
MoveOperands* move = moves_[index];
codegen_->MoveLocation(move->GetDestination(), move->GetSource(), move->GetType());
}
void ParallelMoveResolverMIPS64::EmitSwap(size_t index) {
MoveOperands* move = moves_[index];
codegen_->SwapLocations(move->GetDestination(), move->GetSource(), move->GetType());
}
void ParallelMoveResolverMIPS64::RestoreScratch(int reg) {
// Pop reg
__ Ld(GpuRegister(reg), SP, 0);
__ DecreaseFrameSize(kMips64DoublewordSize);
}
void ParallelMoveResolverMIPS64::SpillScratch(int reg) {
// Push reg
__ IncreaseFrameSize(kMips64DoublewordSize);
__ Sd(GpuRegister(reg), SP, 0);
}
void ParallelMoveResolverMIPS64::Exchange(int index1, int index2, bool double_slot) {
LoadOperandType load_type = double_slot ? kLoadDoubleword : kLoadWord;
StoreOperandType store_type = double_slot ? kStoreDoubleword : kStoreWord;
// Allocate a scratch register other than TMP, if available.
// Else, spill V0 (arbitrary choice) and use it as a scratch register (it will be
// automatically unspilled when the scratch scope object is destroyed).
ScratchRegisterScope ensure_scratch(this, TMP, V0, codegen_->GetNumberOfCoreRegisters());
// If V0 spills onto the stack, SP-relative offsets need to be adjusted.
int stack_offset = ensure_scratch.IsSpilled() ? kMips64DoublewordSize : 0;
__ LoadFromOffset(load_type,
GpuRegister(ensure_scratch.GetRegister()),
SP,
index1 + stack_offset);
__ LoadFromOffset(load_type,
TMP,
SP,
index2 + stack_offset);
__ StoreToOffset(store_type,
GpuRegister(ensure_scratch.GetRegister()),
SP,
index2 + stack_offset);
__ StoreToOffset(store_type, TMP, SP, index1 + stack_offset);
}
void ParallelMoveResolverMIPS64::ExchangeQuadSlots(int index1, int index2) {
__ LoadFpuFromOffset(kLoadQuadword, FTMP, SP, index1);
__ LoadFpuFromOffset(kLoadQuadword, FTMP2, SP, index2);
__ StoreFpuToOffset(kStoreQuadword, FTMP, SP, index2);
__ StoreFpuToOffset(kStoreQuadword, FTMP2, SP, index1);
}
static dwarf::Reg DWARFReg(GpuRegister reg) {
return dwarf::Reg::Mips64Core(static_cast<int>(reg));
}
static dwarf::Reg DWARFReg(FpuRegister reg) {
return dwarf::Reg::Mips64Fp(static_cast<int>(reg));
}
void CodeGeneratorMIPS64::GenerateFrameEntry() {
__ Bind(&frame_entry_label_);
if (GetCompilerOptions().CountHotnessInCompiledCode()) {
__ Lhu(TMP, kMethodRegisterArgument, ArtMethod::HotnessCountOffset().Int32Value());
__ Addiu(TMP, TMP, 1);
__ Sh(TMP, kMethodRegisterArgument, ArtMethod::HotnessCountOffset().Int32Value());
}
bool do_overflow_check =
FrameNeedsStackCheck(GetFrameSize(), InstructionSet::kMips64) || !IsLeafMethod();
if (do_overflow_check) {
__ LoadFromOffset(
kLoadWord,
ZERO,
SP,
-static_cast<int32_t>(GetStackOverflowReservedBytes(InstructionSet::kMips64)));
RecordPcInfo(nullptr, 0);
}
if (HasEmptyFrame()) {
return;
}
// Make sure the frame size isn't unreasonably large.
if (GetFrameSize() > GetStackOverflowReservedBytes(InstructionSet::kMips64)) {
LOG(FATAL) << "Stack frame larger than "
<< GetStackOverflowReservedBytes(InstructionSet::kMips64) << " bytes";
}
// Spill callee-saved registers.
uint32_t ofs = GetFrameSize();
__ IncreaseFrameSize(ofs);
for (int i = arraysize(kCoreCalleeSaves) - 1; i >= 0; --i) {
GpuRegister reg = kCoreCalleeSaves[i];
if (allocated_registers_.ContainsCoreRegister(reg)) {
ofs -= kMips64DoublewordSize;
__ StoreToOffset(kStoreDoubleword, reg, SP, ofs);
__ cfi().RelOffset(DWARFReg(reg), ofs);
}
}
for (int i = arraysize(kFpuCalleeSaves) - 1; i >= 0; --i) {
FpuRegister reg = kFpuCalleeSaves[i];
if (allocated_registers_.ContainsFloatingPointRegister(reg)) {
ofs -= kMips64DoublewordSize;
__ StoreFpuToOffset(kStoreDoubleword, reg, SP, ofs);
__ cfi().RelOffset(DWARFReg(reg), ofs);
}
}
// Save the current method if we need it. Note that we do not
// do this in HCurrentMethod, as the instruction might have been removed
// in the SSA graph.
if (RequiresCurrentMethod()) {
__ StoreToOffset(kStoreDoubleword, kMethodRegisterArgument, SP, kCurrentMethodStackOffset);
}
if (GetGraph()->HasShouldDeoptimizeFlag()) {
// Initialize should_deoptimize flag to 0.
__ StoreToOffset(kStoreWord, ZERO, SP, GetStackOffsetOfShouldDeoptimizeFlag());
}
}
void CodeGeneratorMIPS64::GenerateFrameExit() {
__ cfi().RememberState();
if (!HasEmptyFrame()) {
// Restore callee-saved registers.
// For better instruction scheduling restore RA before other registers.
uint32_t ofs = GetFrameSize();
for (int i = arraysize(kCoreCalleeSaves) - 1; i >= 0; --i) {
GpuRegister reg = kCoreCalleeSaves[i];
if (allocated_registers_.ContainsCoreRegister(reg)) {
ofs -= kMips64DoublewordSize;
__ LoadFromOffset(kLoadDoubleword, reg, SP, ofs);
__ cfi().Restore(DWARFReg(reg));
}
}
for (int i = arraysize(kFpuCalleeSaves) - 1; i >= 0; --i) {
FpuRegister reg = kFpuCalleeSaves[i];
if (allocated_registers_.ContainsFloatingPointRegister(reg)) {
ofs -= kMips64DoublewordSize;
__ LoadFpuFromOffset(kLoadDoubleword, reg, SP, ofs);
__ cfi().Restore(DWARFReg(reg));
}
}
__ DecreaseFrameSize(GetFrameSize());
}
__ Jic(RA, 0);
__ cfi().RestoreState();
__ cfi().DefCFAOffset(GetFrameSize());
}
void CodeGeneratorMIPS64::Bind(HBasicBlock* block) {
__ Bind(GetLabelOf(block));
}
void CodeGeneratorMIPS64::MoveLocation(Location destination,
Location source,
DataType::Type dst_type) {
if (source.Equals(destination)) {
return;
}
// A valid move can always be inferred from the destination and source
// locations. When moving from and to a register, the argument type can be
// used to generate 32bit instead of 64bit moves.
bool unspecified_type = (dst_type == DataType::Type::kVoid);
DCHECK_EQ(unspecified_type, false);
if (destination.IsRegister() || destination.IsFpuRegister()) {
if (unspecified_type) {
HConstant* src_cst = source.IsConstant() ? source.GetConstant() : nullptr;
if (source.IsStackSlot() ||
(src_cst != nullptr && (src_cst->IsIntConstant()
|| src_cst->IsFloatConstant()
|| src_cst->IsNullConstant()))) {
// For stack slots and 32bit constants, a 64bit type is appropriate.
dst_type = destination.IsRegister() ? DataType::Type::kInt32 : DataType::Type::kFloat32;
} else {
// If the source is a double stack slot or a 64bit constant, a 64bit
// type is appropriate. Else the source is a register, and since the
// type has not been specified, we chose a 64bit type to force a 64bit
// move.
dst_type = destination.IsRegister() ? DataType::Type::kInt64 : DataType::Type::kFloat64;
}
}
DCHECK((destination.IsFpuRegister() && DataType::IsFloatingPointType(dst_type)) ||
(destination.IsRegister() && !DataType::IsFloatingPointType(dst_type)));
if (source.IsStackSlot() || source.IsDoubleStackSlot()) {
// Move to GPR/FPR from stack
LoadOperandType load_type = source.IsStackSlot() ? kLoadWord : kLoadDoubleword;
if (DataType::IsFloatingPointType(dst_type)) {
__ LoadFpuFromOffset(load_type,
destination.AsFpuRegister<FpuRegister>(),
SP,
source.GetStackIndex());
} else {
// TODO: use load_type = kLoadUnsignedWord when type == DataType::Type::kReference.
__ LoadFromOffset(load_type,
destination.AsRegister<GpuRegister>(),
SP,
source.GetStackIndex());
}
} else if (source.IsSIMDStackSlot()) {
__ LoadFpuFromOffset(kLoadQuadword,
destination.AsFpuRegister<FpuRegister>(),
SP,
source.GetStackIndex());
} else if (source.IsConstant()) {
// Move to GPR/FPR from constant
GpuRegister gpr = AT;
if (!DataType::IsFloatingPointType(dst_type)) {
gpr = destination.AsRegister<GpuRegister>();
}
if (dst_type == DataType::Type::kInt32 || dst_type == DataType::Type::kFloat32) {
int32_t value = GetInt32ValueOf(source.GetConstant()->AsConstant());
if (DataType::IsFloatingPointType(dst_type) && value == 0) {
gpr = ZERO;
} else {
__ LoadConst32(gpr, value);
}
} else {
int64_t value = GetInt64ValueOf(source.GetConstant()->AsConstant());
if (DataType::IsFloatingPointType(dst_type) && value == 0) {
gpr = ZERO;
} else {
__ LoadConst64(gpr, value);
}
}
if (dst_type == DataType::Type::kFloat32) {
__ Mtc1(gpr, destination.AsFpuRegister<FpuRegister>());
} else if (dst_type == DataType::Type::kFloat64) {
__ Dmtc1(gpr, destination.AsFpuRegister<FpuRegister>());
}
} else if (source.IsRegister()) {
if (destination.IsRegister()) {
// Move to GPR from GPR
__ Move(destination.AsRegister<GpuRegister>(), source.AsRegister<GpuRegister>());
} else {
DCHECK(destination.IsFpuRegister());
if (DataType::Is64BitType(dst_type)) {
__ Dmtc1(source.AsRegister<GpuRegister>(), destination.AsFpuRegister<FpuRegister>());
} else {
__ Mtc1(source.AsRegister<GpuRegister>(), destination.AsFpuRegister<FpuRegister>());
}
}
} else if (source.IsFpuRegister()) {
if (destination.IsFpuRegister()) {
if (GetGraph()->HasSIMD()) {
__ MoveV(VectorRegisterFrom(destination),
VectorRegisterFrom(source));
} else {
// Move to FPR from FPR
if (dst_type == DataType::Type::kFloat32) {
__ MovS(destination.AsFpuRegister<FpuRegister>(), source.AsFpuRegister<FpuRegister>());
} else {
DCHECK_EQ(dst_type, DataType::Type::kFloat64);
__ MovD(destination.AsFpuRegister<FpuRegister>(), source.AsFpuRegister<FpuRegister>());
}
}
} else {
DCHECK(destination.IsRegister());
if (DataType::Is64BitType(dst_type)) {
__ Dmfc1(destination.AsRegister<GpuRegister>(), source.AsFpuRegister<FpuRegister>());
} else {
__ Mfc1(destination.AsRegister<GpuRegister>(), source.AsFpuRegister<FpuRegister>());
}
}
}
} else if (destination.IsSIMDStackSlot()) {
if (source.IsFpuRegister()) {
__ StoreFpuToOffset(kStoreQuadword,
source.AsFpuRegister<FpuRegister>(),
SP,
destination.GetStackIndex());
} else {
DCHECK(source.IsSIMDStackSlot());
__ LoadFpuFromOffset(kLoadQuadword,
FTMP,
SP,
source.GetStackIndex());
__ StoreFpuToOffset(kStoreQuadword,
FTMP,
SP,
destination.GetStackIndex());
}
} else { // The destination is not a register. It must be a stack slot.
DCHECK(destination.IsStackSlot() || destination.IsDoubleStackSlot());
if (source.IsRegister() || source.IsFpuRegister()) {
if (unspecified_type) {
if (source.IsRegister()) {
dst_type = destination.IsStackSlot() ? DataType::Type::kInt32 : DataType::Type::kInt64;
} else {
dst_type =
destination.IsStackSlot() ? DataType::Type::kFloat32 : DataType::Type::kFloat64;
}
}
DCHECK((destination.IsDoubleStackSlot() == DataType::Is64BitType(dst_type)) &&
(source.IsFpuRegister() == DataType::IsFloatingPointType(dst_type)));
// Move to stack from GPR/FPR
StoreOperandType store_type = destination.IsStackSlot() ? kStoreWord : kStoreDoubleword;
if (source.IsRegister()) {
__ StoreToOffset(store_type,
source.AsRegister<GpuRegister>(),
SP,
destination.GetStackIndex());
} else {
__ StoreFpuToOffset(store_type,
source.AsFpuRegister<FpuRegister>(),
SP,
destination.GetStackIndex());
}
} else if (source.IsConstant()) {
// Move to stack from constant
HConstant* src_cst = source.GetConstant();
StoreOperandType store_type = destination.IsStackSlot() ? kStoreWord : kStoreDoubleword;
GpuRegister gpr = ZERO;
if (destination.IsStackSlot()) {
int32_t value = GetInt32ValueOf(src_cst->AsConstant());
if (value != 0) {
gpr = TMP;
__ LoadConst32(gpr, value);
}
} else {
DCHECK(destination.IsDoubleStackSlot());
int64_t value = GetInt64ValueOf(src_cst->AsConstant());
if (value != 0) {
gpr = TMP;
__ LoadConst64(gpr, value);
}
}
__ StoreToOffset(store_type, gpr, SP, destination.GetStackIndex());
} else {
DCHECK(source.IsStackSlot() || source.IsDoubleStackSlot());
DCHECK_EQ(source.IsDoubleStackSlot(), destination.IsDoubleStackSlot());
// Move to stack from stack
if (destination.IsStackSlot()) {
__ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex());
__ StoreToOffset(kStoreWord, TMP, SP, destination.GetStackIndex());
} else {
__ LoadFromOffset(kLoadDoubleword, TMP, SP, source.GetStackIndex());
__ StoreToOffset(kStoreDoubleword, TMP, SP, destination.GetStackIndex());
}
}
}
}
void CodeGeneratorMIPS64::SwapLocations(Location loc1, Location loc2, DataType::Type type) {
DCHECK(!loc1.IsConstant());
DCHECK(!loc2.IsConstant());
if (loc1.Equals(loc2)) {
return;
}
bool is_slot1 = loc1.IsStackSlot() || loc1.IsDoubleStackSlot();
bool is_slot2 = loc2.IsStackSlot() || loc2.IsDoubleStackSlot();
bool is_simd1 = loc1.IsSIMDStackSlot();
bool is_simd2 = loc2.IsSIMDStackSlot();
bool is_fp_reg1 = loc1.IsFpuRegister();
bool is_fp_reg2 = loc2.IsFpuRegister();
if (loc2.IsRegister() && loc1.IsRegister()) {
// Swap 2 GPRs
GpuRegister r1 = loc1.AsRegister<GpuRegister>();
GpuRegister r2 = loc2.AsRegister<GpuRegister>();
__ Move(TMP, r2);
__ Move(r2, r1);
__ Move(r1, TMP);
} else if (is_fp_reg2 && is_fp_reg1) {
// Swap 2 FPRs
if (GetGraph()->HasSIMD()) {
__ MoveV(static_cast<VectorRegister>(FTMP), VectorRegisterFrom(loc1));
__ MoveV(VectorRegisterFrom(loc1), VectorRegisterFrom(loc2));
__ MoveV(VectorRegisterFrom(loc2), static_cast<VectorRegister>(FTMP));
} else {
FpuRegister r1 = loc1.AsFpuRegister<FpuRegister>();
FpuRegister r2 = loc2.AsFpuRegister<FpuRegister>();
if (type == DataType::Type::kFloat32) {
__ MovS(FTMP, r1);
__ MovS(r1, r2);
__ MovS(r2, FTMP);
} else {
DCHECK_EQ(type, DataType::Type::kFloat64);
__ MovD(FTMP, r1);
__ MovD(r1, r2);
__ MovD(r2, FTMP);
}
}
} else if (is_slot1 != is_slot2) {
// Swap GPR/FPR and stack slot
Location reg_loc = is_slot1 ? loc2 : loc1;
Location mem_loc = is_slot1 ? loc1 : loc2;
LoadOperandType load_type = mem_loc.IsStackSlot() ? kLoadWord : kLoadDoubleword;
StoreOperandType store_type = mem_loc.IsStackSlot() ? kStoreWord : kStoreDoubleword;
// TODO: use load_type = kLoadUnsignedWord when type == DataType::Type::kReference.
__ LoadFromOffset(load_type, TMP, SP, mem_loc.GetStackIndex());
if (reg_loc.IsFpuRegister()) {
__ StoreFpuToOffset(store_type,
reg_loc.AsFpuRegister<FpuRegister>(),
SP,
mem_loc.GetStackIndex());
if (mem_loc.IsStackSlot()) {
__ Mtc1(TMP, reg_loc.AsFpuRegister<FpuRegister>());
} else {
DCHECK(mem_loc.IsDoubleStackSlot());
__ Dmtc1(TMP, reg_loc.AsFpuRegister<FpuRegister>());
}
} else {
__ StoreToOffset(store_type, reg_loc.AsRegister<GpuRegister>(), SP, mem_loc.GetStackIndex());
__ Move(reg_loc.AsRegister<GpuRegister>(), TMP);
}
} else if (is_slot1 && is_slot2) {
move_resolver_.Exchange(loc1.GetStackIndex(),
loc2.GetStackIndex(),
loc1.IsDoubleStackSlot());
} else if (is_simd1 && is_simd2) {
move_resolver_.ExchangeQuadSlots(loc1.GetStackIndex(), loc2.GetStackIndex());
} else if ((is_fp_reg1 && is_simd2) || (is_fp_reg2 && is_simd1)) {
Location fp_reg_loc = is_fp_reg1 ? loc1 : loc2;
Location mem_loc = is_fp_reg1 ? loc2 : loc1;
__ LoadFpuFromOffset(kLoadQuadword, FTMP, SP, mem_loc.GetStackIndex());
__ StoreFpuToOffset(kStoreQuadword,
fp_reg_loc.AsFpuRegister<FpuRegister>(),
SP,
mem_loc.GetStackIndex());
__ MoveV(VectorRegisterFrom(fp_reg_loc), static_cast<VectorRegister>(FTMP));
} else {
LOG(FATAL) << "Unimplemented swap between locations " << loc1 << " and " << loc2;
}
}
void CodeGeneratorMIPS64::MoveConstant(Location location, int32_t value) {
DCHECK(location.IsRegister());
__ LoadConst32(location.AsRegister<GpuRegister>(), value);
}
void CodeGeneratorMIPS64::AddLocationAsTemp(Location location, LocationSummary* locations) {
if (location.IsRegister()) {
locations->AddTemp(location);
} else {
UNIMPLEMENTED(FATAL) << "AddLocationAsTemp not implemented for location " << location;
}
}
void CodeGeneratorMIPS64::MarkGCCard(GpuRegister object,
GpuRegister value,
bool value_can_be_null) {
Mips64Label done;
GpuRegister card = AT;
GpuRegister temp = TMP;
if (value_can_be_null) {
__ Beqzc(value, &done);
}
// Load the address of the card table into `card`.
__ LoadFromOffset(kLoadDoubleword,
card,
TR,
Thread::CardTableOffset<kMips64PointerSize>().Int32Value());
// Calculate the address of the card corresponding to `object`.
__ Dsrl(temp, object, gc::accounting::CardTable::kCardShift);
__ Daddu(temp, card, temp);
// Write the `art::gc::accounting::CardTable::kCardDirty` value into the
// `object`'s card.
//
// Register `card` contains the address of the card table. Note that the card
// table's base is biased during its creation so that it always starts at an
// address whose least-significant byte is equal to `kCardDirty` (see
// art::gc::accounting::CardTable::Create). Therefore the SB instruction
// below writes the `kCardDirty` (byte) value into the `object`'s card
// (located at `card + object >> kCardShift`).
//
// This dual use of the value in register `card` (1. to calculate the location
// of the card to mark; and 2. to load the `kCardDirty` value) saves a load
// (no need to explicitly load `kCardDirty` as an immediate value).
__ Sb(card, temp, 0);
if (value_can_be_null) {
__ Bind(&done);
}
}
template <linker::LinkerPatch (*Factory)(size_t, const DexFile*, uint32_t, uint32_t)>
inline void CodeGeneratorMIPS64::EmitPcRelativeLinkerPatches(
const ArenaDeque<PcRelativePatchInfo>& infos,
ArenaVector<linker::LinkerPatch>* linker_patches) {
for (const PcRelativePatchInfo& info : infos) {
const DexFile* dex_file = info.target_dex_file;
size_t offset_or_index = info.offset_or_index;
DCHECK(info.label.IsBound());
uint32_t literal_offset = __ GetLabelLocation(&info.label);
const PcRelativePatchInfo& info_high = info.patch_info_high ? *info.patch_info_high : info;
uint32_t pc_rel_offset = __ GetLabelLocation(&info_high.label);
linker_patches->push_back(Factory(literal_offset, dex_file, pc_rel_offset, offset_or_index));
}
}
template <linker::LinkerPatch (*Factory)(size_t, uint32_t, uint32_t)>
linker::LinkerPatch NoDexFileAdapter(size_t literal_offset,
const DexFile* target_dex_file,
uint32_t pc_insn_offset,
uint32_t boot_image_offset) {
DCHECK(target_dex_file == nullptr); // Unused for these patches, should be null.
return Factory(literal_offset, pc_insn_offset, boot_image_offset);
}
void CodeGeneratorMIPS64::EmitLinkerPatches(ArenaVector<linker::LinkerPatch>* linker_patches) {
DCHECK(linker_patches->empty());
size_t size =
boot_image_method_patches_.size() +
method_bss_entry_patches_.size() +
boot_image_type_patches_.size() +
type_bss_entry_patches_.size() +
boot_image_string_patches_.size() +
string_bss_entry_patches_.size() +
boot_image_intrinsic_patches_.size();
linker_patches->reserve(size);
if (GetCompilerOptions().IsBootImage()) {
EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeMethodPatch>(
boot_image_method_patches_, linker_patches);
EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeTypePatch>(
boot_image_type_patches_, linker_patches);
EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeStringPatch>(
boot_image_string_patches_, linker_patches);
EmitPcRelativeLinkerPatches<NoDexFileAdapter<linker::LinkerPatch::IntrinsicReferencePatch>>(
boot_image_intrinsic_patches_, linker_patches);
} else {
EmitPcRelativeLinkerPatches<NoDexFileAdapter<linker::LinkerPatch::DataBimgRelRoPatch>>(
boot_image_method_patches_, linker_patches);
DCHECK(boot_image_type_patches_.empty());
DCHECK(boot_image_string_patches_.empty());
DCHECK(boot_image_intrinsic_patches_.empty());
}
EmitPcRelativeLinkerPatches<linker::LinkerPatch::MethodBssEntryPatch>(
method_bss_entry_patches_, linker_patches);
EmitPcRelativeLinkerPatches<linker::LinkerPatch::TypeBssEntryPatch>(
type_bss_entry_patches_, linker_patches);
EmitPcRelativeLinkerPatches<linker::LinkerPatch::StringBssEntryPatch>(
string_bss_entry_patches_, linker_patches);
DCHECK_EQ(size, linker_patches->size());
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewBootImageIntrinsicPatch(
uint32_t intrinsic_data,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(
/* dex_file= */ nullptr, intrinsic_data, info_high, &boot_image_intrinsic_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewBootImageRelRoPatch(
uint32_t boot_image_offset,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(
/* dex_file= */ nullptr, boot_image_offset, info_high, &boot_image_method_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewBootImageMethodPatch(
MethodReference target_method,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(
target_method.dex_file, target_method.index, info_high, &boot_image_method_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewMethodBssEntryPatch(
MethodReference target_method,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(
target_method.dex_file, target_method.index, info_high, &method_bss_entry_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewBootImageTypePatch(
const DexFile& dex_file,
dex::TypeIndex type_index,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(&dex_file, type_index.index_, info_high, &boot_image_type_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewTypeBssEntryPatch(
const DexFile& dex_file,
dex::TypeIndex type_index,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(&dex_file, type_index.index_, info_high, &type_bss_entry_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewBootImageStringPatch(
const DexFile& dex_file,
dex::StringIndex string_index,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(
&dex_file, string_index.index_, info_high, &boot_image_string_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewStringBssEntryPatch(
const DexFile& dex_file,
dex::StringIndex string_index,
const PcRelativePatchInfo* info_high) {
return NewPcRelativePatch(&dex_file, string_index.index_, info_high, &string_bss_entry_patches_);
}
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewPcRelativePatch(
const DexFile* dex_file,
uint32_t offset_or_index,
const PcRelativePatchInfo* info_high,
ArenaDeque<PcRelativePatchInfo>* patches) {
patches->emplace_back(dex_file, offset_or_index, info_high);
return &patches->back();
}
Literal* CodeGeneratorMIPS64::DeduplicateUint32Literal(uint32_t value, Uint32ToLiteralMap* map) {
return map->GetOrCreate(
value,
[this, value]() { return __ NewLiteral<uint32_t>(value); });
}
Literal* CodeGeneratorMIPS64::DeduplicateUint64Literal(uint64_t value) {
return uint64_literals_.GetOrCreate(
value,
[this, value]() { return __ NewLiteral<uint64_t>(value); });
}
Literal* CodeGeneratorMIPS64::DeduplicateBootImageAddressLiteral(uint64_t address) {
return DeduplicateUint32Literal(dchecked_integral_cast<uint32_t>(address), &uint32_literals_);
}
void CodeGeneratorMIPS64::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high,
GpuRegister out,
PcRelativePatchInfo* info_low) {
DCHECK(!info_high->patch_info_high);
__ Bind(&info_high->label);
// Add the high half of a 32-bit offset to PC.
__ Auipc(out, /* imm16= */ 0x1234);
// A following instruction will add the sign-extended low half of the 32-bit
// offset to `out` (e.g. ld, jialc, daddiu).
if (info_low != nullptr) {
DCHECK_EQ(info_low->patch_info_high, info_high);
__ Bind(&info_low->label);
}
}
void CodeGeneratorMIPS64::LoadBootImageAddress(GpuRegister reg, uint32_t boot_image_reference) {
if (GetCompilerOptions().IsBootImage()) {
PcRelativePatchInfo* info_high = NewBootImageIntrinsicPatch(boot_image_reference);
PcRelativePatchInfo* info_low = NewBootImageIntrinsicPatch(boot_image_reference, info_high);
EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low);
__ Daddiu(reg, AT, /* imm16= */ 0x5678);
} else if (GetCompilerOptions().GetCompilePic()) {
PcRelativePatchInfo* info_high = NewBootImageRelRoPatch(boot_image_reference);
PcRelativePatchInfo* info_low = NewBootImageRelRoPatch(boot_image_reference, info_high);
EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low);
// Note: Boot image is in the low 4GiB and the entry is 32-bit, so emit a 32-bit load.
__ Lwu(reg, AT, /* imm16= */ 0x5678);
} else {
DCHECK(Runtime::Current()->UseJitCompilation());
gc::Heap* heap = Runtime::Current()->GetHeap();
DCHECK(!heap->GetBootImageSpaces().empty());
uintptr_t address =
reinterpret_cast<uintptr_t>(heap->GetBootImageSpaces()[0]->Begin() + boot_image_reference);
__ LoadLiteral(reg, kLoadDoubleword, DeduplicateBootImageAddressLiteral(address));
}
}
void CodeGeneratorMIPS64::AllocateInstanceForIntrinsic(HInvokeStaticOrDirect* invoke,
uint32_t boot_image_offset) {
DCHECK(invoke->IsStatic());
InvokeRuntimeCallingConvention calling_convention;
GpuRegister argument = calling_convention.GetRegisterAt(0);
if (GetCompilerOptions().IsBootImage()) {
DCHECK_EQ(boot_image_offset, IntrinsicVisitor::IntegerValueOfInfo::kInvalidReference);
// Load the class the same way as for HLoadClass::LoadKind::kBootImageLinkTimePcRelative.
MethodReference target_method = invoke->GetTargetMethod();
dex::TypeIndex type_idx = target_method.dex_file->GetMethodId(target_method.index).class_idx_;
PcRelativePatchInfo* info_high = NewBootImageTypePatch(*target_method.dex_file, type_idx);
PcRelativePatchInfo* info_low =
NewBootImageTypePatch(*target_method.dex_file, type_idx, info_high);
EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low);
__ Daddiu(argument, AT, /* imm16= */ 0x5678);
} else {
LoadBootImageAddress(argument, boot_image_offset);
}
InvokeRuntime(kQuickAllocObjectInitialized, invoke, invoke->GetDexPc());
CheckEntrypointTypes<kQuickAllocObjectWithChecks, void*, mirror::Class*>();
}
Literal* CodeGeneratorMIPS64::DeduplicateJitStringLiteral(const DexFile& dex_file,
dex::StringIndex string_index,
Handle<mirror::String> handle) {
ReserveJitStringRoot(StringReference(&dex_file, string_index), handle);
return jit_string_patches_.GetOrCreate(
StringReference(&dex_file, string_index),
[this]() { return __ NewLiteral<uint32_t>(/* value= */ 0u); });
}
Literal* CodeGeneratorMIPS64::DeduplicateJitClassLiteral(const DexFile& dex_file,
dex::TypeIndex type_index,
Handle<mirror::Class> handle) {
ReserveJitClassRoot(TypeReference(&dex_file, type_index), handle);
return jit_class_patches_.GetOrCreate(
TypeReference(&dex_file, type_index),
[this]() { return __ NewLiteral<uint32_t>(/* value= */ 0u); });
}
void CodeGeneratorMIPS64::PatchJitRootUse(uint8_t* code,
const uint8_t* roots_data,
const Literal* literal,
uint64_t index_in_table) const {
uint32_t literal_offset = GetAssembler().GetLabelLocation(literal->GetLabel());
uintptr_t address =
reinterpret_cast<uintptr_t>(roots_data) + index_in_table * sizeof(GcRoot<mirror::Object>);
reinterpret_cast<uint32_t*>(code + literal_offset)[0] = dchecked_integral_cast<uint32_t>(address);
}
void CodeGeneratorMIPS64::EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) {
for (const auto& entry : jit_string_patches_) {
const StringReference& string_reference = entry.first;
Literal* table_entry_literal = entry.second;
uint64_t index_in_table = GetJitStringRootIndex(string_reference);
PatchJitRootUse(code, roots_data, table_entry_literal, index_in_table);
}
for (const auto& entry : jit_class_patches_) {
const TypeReference& type_reference = entry.first;
Literal* table_entry_literal = entry.second;
uint64_t index_in_table = GetJitClassRootIndex(type_reference);
PatchJitRootUse(code, roots_data, table_entry_literal, index_in_table);
}
}
void CodeGeneratorMIPS64::SetupBlockedRegisters() const {
// ZERO, K0, K1, GP, SP, RA are always reserved and can't be allocated.
blocked_core_registers_[ZERO] = true;
blocked_core_registers_[K0] = true;
blocked_core_registers_[K1] = true;
blocked_core_registers_[GP] = true;
blocked_core_registers_[SP] = true;
blocked_core_registers_[RA] = true;
// AT, TMP(T8) and TMP2(T3) are used as temporary/scratch
// registers (similar to how AT is used by MIPS assemblers).
blocked_core_registers_[AT] = true;
blocked_core_registers_[TMP] = true;
blocked_core_registers_[TMP2] = true;
blocked_fpu_registers_[FTMP] = true;
if (GetInstructionSetFeatures().HasMsa()) {
// To be used just for MSA instructions.
blocked_fpu_registers_[FTMP2] = true;
}
// Reserve suspend and thread registers.
blocked_core_registers_[S0] = true;
blocked_core_registers_[TR] = true;
// Reserve T9 for function calls
blocked_core_registers_[T9] = true;
if (GetGraph()->IsDebuggable()) {
// Stubs do not save callee-save floating point registers. If the graph
// is debuggable, we need to deal with these registers differently. For
// now, just block them.
for (size_t i = 0; i < arraysize(kFpuCalleeSaves); ++i) {
blocked_fpu_registers_[kFpuCalleeSaves[i]] = true;
}
}
}
size_t CodeGeneratorMIPS64::SaveCoreRegister(size_t stack_index, uint32_t reg_id) {
__ StoreToOffset(kStoreDoubleword, GpuRegister(reg_id), SP, stack_index);
return kMips64DoublewordSize;
}
size_t CodeGeneratorMIPS64::RestoreCoreRegister(size_t stack_index, uint32_t reg_id) {
__ LoadFromOffset(kLoadDoubleword, GpuRegister(reg_id), SP, stack_index);
return kMips64DoublewordSize;
}
size_t CodeGeneratorMIPS64::SaveFloatingPointRegister(size_t stack_index, uint32_t reg_id) {
__ StoreFpuToOffset(GetGraph()->HasSIMD() ? kStoreQuadword : kStoreDoubleword,
FpuRegister(reg_id),
SP,
stack_index);
return GetFloatingPointSpillSlotSize();
}
size_t CodeGeneratorMIPS64::RestoreFloatingPointRegister(size_t stack_index, uint32_t reg_id) {
__ LoadFpuFromOffset(GetGraph()->HasSIMD() ? kLoadQuadword : kLoadDoubleword,
FpuRegister(reg_id),
SP,
stack_index);
return GetFloatingPointSpillSlotSize();
}
void CodeGeneratorMIPS64::DumpCoreRegister(std::ostream& stream, int reg) const {
stream << GpuRegister(reg);
}
void CodeGeneratorMIPS64::DumpFloatingPointRegister(std::ostream& stream, int reg) const {
stream << FpuRegister(reg);
}
const Mips64InstructionSetFeatures& CodeGeneratorMIPS64::GetInstructionSetFeatures() const {
return *GetCompilerOptions().GetInstructionSetFeatures()->AsMips64InstructionSetFeatures();
}
void CodeGeneratorMIPS64::InvokeRuntime(QuickEntrypointEnum entrypoint,
HInstruction* instruction,
uint32_t dex_pc,
SlowPathCode* slow_path) {
ValidateInvokeRuntime(entrypoint, instruction, slow_path);
GenerateInvokeRuntime(GetThreadOffset<kMips64PointerSize>(entrypoint).Int32Value());
if (EntrypointRequiresStackMap(entrypoint)) {
RecordPcInfo(instruction, dex_pc, slow_path);
}
}
void CodeGeneratorMIPS64::InvokeRuntimeWithoutRecordingPcInfo(int32_t entry_point_offset,
HInstruction* instruction,
SlowPathCode* slow_path) {
ValidateInvokeRuntimeWithoutRecordingPcInfo(instruction, slow_path);
GenerateInvokeRuntime(entry_point_offset);
}
void CodeGeneratorMIPS64::GenerateInvokeRuntime(int32_t entry_point_offset) {
__ LoadFromOffset(kLoadDoubleword, T9, TR, entry_point_offset);
__ Jalr(T9);
__ Nop();
}
void InstructionCodeGeneratorMIPS64::GenerateClassInitializationCheck(SlowPathCodeMIPS64* slow_path,
GpuRegister class_reg) {
constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
const size_t status_byte_offset =
mirror::Class::StatusOffset().SizeValue() + (status_lsb_position / kBitsPerByte);
constexpr uint32_t shifted_initialized_value =
enum_cast<uint32_t>(ClassStatus::kInitialized) << (status_lsb_position % kBitsPerByte);
__ LoadFromOffset(kLoadUnsignedByte, TMP, class_reg, status_byte_offset);
__ Sltiu(TMP, TMP, shifted_initialized_value);
__ Bnezc(TMP, slow_path->GetEntryLabel());
// Even if the initialized flag is set, we need to ensure consistent memory ordering.
__ Sync(0);
__ Bind(slow_path->GetExitLabel());
}
void InstructionCodeGeneratorMIPS64::GenerateBitstringTypeCheckCompare(HTypeCheckInstruction* check,
GpuRegister temp) {
uint32_t path_to_root = check->GetBitstringPathToRoot();
uint32_t mask = check->GetBitstringMask();
DCHECK(IsPowerOfTwo(mask + 1));
size_t mask_bits = WhichPowerOf2(mask + 1);
if (mask_bits == 16u) {
// Load only the bitstring part of the status word.
__ LoadFromOffset(
kLoadUnsignedHalfword, temp, temp, mirror::Class::StatusOffset().Int32Value());
// Compare the bitstring bits using XOR.
__ Xori(temp, temp, dchecked_integral_cast<uint16_t>(path_to_root));
} else {
// /* uint32_t */ temp = temp->status_
__ LoadFromOffset(kLoadWord, temp, temp, mirror::Class::StatusOffset().Int32Value());
// Compare the bitstring bits using XOR.
if (IsUint<16>(path_to_root)) {
__ Xori(temp, temp, dchecked_integral_cast<uint16_t>(path_to_root));
} else {
__ LoadConst32(TMP, path_to_root);
__ Xor(temp, temp, TMP);
}
// Shift out bits that do not contribute to the comparison.
__ Sll(temp, temp, 32 - mask_bits);
}
}
void InstructionCodeGeneratorMIPS64::GenerateMemoryBarrier(MemBarrierKind kind ATTRIBUTE_UNUSED) {
__ Sync(0); // only stype 0 is supported
}
void InstructionCodeGeneratorMIPS64::GenerateSuspendCheck(HSuspendCheck* instruction,
HBasicBlock* successor) {
SuspendCheckSlowPathMIPS64* slow_path =
down_cast<SuspendCheckSlowPathMIPS64*>(instruction->GetSlowPath());
if (slow_path == nullptr) {
slow_path =
new (codegen_->GetScopedAllocator()) SuspendCheckSlowPathMIPS64(instruction, successor);
instruction->SetSlowPath(slow_path);
codegen_->AddSlowPath(slow_path);
if (successor != nullptr) {
DCHECK(successor->IsLoopHeader());
}
} else {
DCHECK_EQ(slow_path->GetSuccessor(), successor);
}
__ LoadFromOffset(kLoadUnsignedHalfword,
TMP,
TR,
Thread::ThreadFlagsOffset<kMips64PointerSize>().Int32Value());
if (successor == nullptr) {
__ Bnezc(TMP, slow_path->GetEntryLabel());
__ Bind(slow_path->GetReturnLabel());
} else {
__ Beqzc(TMP, codegen_->GetLabelOf(successor));
__ Bc(slow_path->GetEntryLabel());
// slow_path will return to GetLabelOf(successor).
}
}
InstructionCodeGeneratorMIPS64::InstructionCodeGeneratorMIPS64(HGraph* graph,
CodeGeneratorMIPS64* codegen)
: InstructionCodeGenerator(graph, codegen),
assembler_(codegen->GetAssembler()),
codegen_(codegen) {}
void LocationsBuilderMIPS64::HandleBinaryOp(HBinaryOperation* instruction) {
DCHECK_EQ(instruction->InputCount(), 2U);
LocationSummary* locations = new (GetGraph()->GetAllocator()) LocationSummary(instruction);
DataType::Type type = instruction->GetResultType();
switch (type) {
case DataType::Type::kInt32:
case DataType::Type::kInt64: {
locations->SetInAt(0, Location::RequiresRegister());
HInstruction* right = instruction->InputAt(1);
bool can_use_imm = false;
if (right->IsConstant()) {
int64_t imm = CodeGenerator::GetInt64ValueOf(right->AsConstant());
if (instruction->IsAnd() || instruction->IsOr() || instruction->IsXor()) {
can_use_imm = IsUint<16>(imm);
} else {
DCHECK(instruction->IsAdd() || instruction->IsSub());
bool single_use = right->GetUses().HasExactlyOneElement();
if (instruction->IsSub()) {
if (!(type == DataType::Type::kInt32 && imm == INT32_MIN)) {
imm = -imm;
}
}
if (type == DataType::Type::kInt32) {
can_use_imm = IsInt<16>(imm) || (Low16Bits(imm) == 0) || single_use;
} else {
can_use_imm = IsInt<16>(imm) || (IsInt<32>(imm) && (Low16Bits(imm) == 0)) || single_use;
}
}
}
if (can_use_imm)
locations->SetInAt(1, Location::ConstantLocation(right->AsConstant()));
else
locations->SetInAt(1, Location::RequiresRegister());
locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
}
break;
case DataType::Type::kFloat32:
case DataType::Type::kFloat64:
locations->SetInAt(0, Location::RequiresFpuRegister());
locations->SetInAt(1, Location::RequiresFpuRegister());
locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap);
break;
default:
LOG(FATAL) << "Unexpected " << instruction->DebugName() << " type " << type;
}
}
void InstructionCodeGeneratorMIPS64::HandleBinaryOp(HBinaryOperation* instruction) {
DataType::Type type = instruction->GetType();
LocationSummary* locations = instruction->GetLocations();
switch (type) {
case DataType::Type::kInt32:
case DataType::Type::kInt64: {
GpuRegister dst = locations->Out().AsRegister<GpuRegister>();
GpuRegister lhs = locations->InAt(0).AsRegister<GpuRegister>();
Location rhs_location = locations->InAt(1);
GpuRegister rhs_reg = ZERO;
int64_t rhs_imm = 0;
bool use_imm = rhs_location.IsConstant();
if (use_imm) {
rhs_imm = CodeGenerator::GetInt64ValueOf(rhs_location.GetConstant());
} else {
rhs_reg = rhs_location.AsRegister<GpuRegister>();
}
if (instruction->IsAnd()) {
if (use_imm)
__ Andi(dst, lhs, rhs_imm);
else
__ And(dst, lhs, rhs_reg);
} else if (instruction->IsOr()) {
if (use_imm)
__ Ori(dst, lhs, rhs_imm);
else
__ Or(dst, lhs, rhs_reg);
} else if (instruction->IsXor()) {
if (use_imm)
__ Xori(dst, lhs, rhs_imm);
else
__ Xor(dst, lhs, rhs_reg);
} else if (instruction->IsAdd() || instruction->IsSub()) {
if (instruction->IsSub()) {
rhs_imm = -rhs_imm;
}
if (type == DataType::Type::kInt32) {
if (use_imm) {
if (IsInt<16>(rhs_imm)) {
__ Addiu(dst, lhs, rhs_imm);
} else {
int16_t rhs_imm_high = High16Bits(rhs_imm);
int16_t rhs_imm_low = Low16Bits(rhs_imm);
if (rhs_imm_low < 0) {
rhs_imm_high += 1;
}
__ Aui(dst, lhs, rhs_imm_high);
if (rhs_imm_low != 0) {
__ Addiu(dst, dst, rhs_imm_low);
}
}
} else {
if (instruction->IsAdd()) {
__ Addu(dst, lhs, rhs_reg);
} else {
DCHECK(instruction->IsSub());
__ Subu(dst, lhs, rhs_reg);
}
}
} else {
if (use_imm) {
if (IsInt<16>(rhs_imm)) {
__ Daddiu(dst, lhs, rhs_imm);
} else if (IsInt<32>(rhs_imm)) {
int16_t rhs_imm_high = High16Bits(rhs_imm);
int16_t rhs_imm_low = Low16Bits(rhs_imm);
bool overflow_hi16 = false;
if (rhs_imm_low < 0) {
rhs_imm_high += 1;
overflow_hi16 = (rhs_imm_high == -32768);
}
__ Daui(dst, lhs, rhs_imm_high);
if (rhs_imm_low != 0) {
__ Daddiu(dst, dst, rhs_imm_low);
}
if (overflow_hi16) {
__ Dahi(dst, 1);
}
} else {
int16_t rhs_imm_low = Low16Bits(Low32Bits(rhs_imm));
if (rhs_imm_low < 0) {
rhs_imm += (INT64_C(1) << 16);
}
int16_t rhs_imm_upper = High16Bits(Low32Bits(rhs_imm));
if (rhs_imm_upper < 0) {
rhs_imm += (INT64_C(1) << 32);
}
int16_t rhs_imm_high = Low16Bits(High32Bits(rhs_imm));
if (rhs_imm_high < 0) {
rhs_imm += (INT64_C(1) << 48);
}
int16_t rhs_imm_top = High16Bits(High32Bits(rhs_imm));
GpuRegister tmp = lhs;
if (rhs_imm_low != 0) {
__ Daddiu(dst, tmp, rhs_imm_low);
tmp = dst;
}
// Dahi and Dati must use the same input and output register, so we have to initialize
// the dst register using Daddiu or Daui, even when the intermediate value is zero:
// Daui(dst, lhs, 0).
if ((rhs_imm_upper != 0) || (rhs_imm_low == 0)) {
__ Daui(dst, tmp, rhs_imm_upper);
}
if (rhs_imm_high != 0) {
__ Dahi(dst, rhs_imm_high);
}
if (rhs_imm_top != 0) {
__ Dati(dst, rhs_imm_top);
}
}
} else if (instruction->IsAdd()) {
__ Daddu(dst, lhs, rhs_reg);
} else {
DCHECK(instruction->IsSub());
__ Dsubu(dst, lhs, rhs_reg);
}
}
}
break;
}
case DataType::Type::kFloat32:
case DataType::Type::kFloat64: {
FpuRegister dst = locations->Out().AsFpuRegister<FpuRegister>();
FpuRegister lhs = locations->InAt(0).AsFpuRegister<FpuRegister>();
FpuRegister rhs = locations->InAt(1).AsFpuRegister<FpuRegister>();
if (instruction->IsAdd()) {
if (type == DataType::Type::kFloat32)
__ AddS(dst, lhs, rhs);
else
__ AddD(dst, lhs, rhs);
} else if (instruction->IsSub()) {
if (type == DataType::Type::kFloat32)
__ SubS(dst, lhs, rhs);
else
__ SubD(dst, lhs, rhs);
} else {
LOG(FATAL) << "Unexpected floating-point binary operation";
}
break;
}
default:
LOG(FATAL) << "Unexpected binary operation type " << type;
}
}
void LocationsBuilderMIPS64::HandleShift(HBinaryOperation* instr) {
DCHECK(instr->IsShl() || instr->IsShr() || instr->IsUShr() || instr->IsRor());
LocationSummary* locations = new (GetGraph()->GetAllocator()) LocationSummary(instr);
DataType::Type type = instr->GetResultType();
switch (type) {
case DataType::Type::kInt32:
case DataType::Type::kInt64: {
locations->SetInAt(0, Location::RequiresRegister());
locations->SetInAt(1, Location::RegisterOrConstant(instr->InputAt(1)));
locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
break;
}
default:
LOG(FATAL) << "Unexpected shift type " << type;
}
}
void InstructionCodeGeneratorMIPS64::HandleShift(HBinaryOperation* instr) {
DCHECK(instr->IsShl() || instr->IsShr() || instr->IsUShr() || instr->IsRor());
LocationSummary* locations = instr->GetLocations();
DataType::Type type = instr->GetType();
switch (type) {
case DataType::Type::kInt32:
case DataType::Type::kInt64: {
GpuRegister dst = locations->Out().AsRegister<GpuRegister>();
GpuRegister lhs = locations->InAt(0).AsRegister<GpuRegister>();
Location rhs_location = locations->InAt(1);
GpuRegister rhs_reg = ZERO;
int64_t rhs_imm = 0;
bool use_imm = rhs_location.IsConstant();
if (use_imm) {
rhs_imm = CodeGenerator::GetInt64ValueOf(rhs_location.GetConstant());
} else {
rhs_reg = rhs_location.AsRegister<GpuRegister>();
}
if (use_imm) {
uint32_t shift_value = rhs_imm &
(type == DataType::Type::kInt32 ? kMaxIntShiftDistance : kMaxLongShiftDistance);
if (shift_value == 0) {
if (dst != lhs) {
__ Move(dst, lhs);
}
} else if (type == DataType::Type::kInt32) {
if (instr->IsShl()) {
__ Sll(dst, lhs, shift_value);
} else if (instr->IsShr()) {
__ Sra(dst, lhs, shift_value);
} else if (instr->IsUShr()) {
__ Srl(dst, lhs, shift_value);
} else {
__ Rotr(dst, lhs, shift_value);
}
} else {
if (shift_value < 32) {
if (instr->IsShl()) {
__ Dsll(dst, lhs, shift_value);
} else if (instr->IsShr()) {
__ Dsra(dst, lhs, shift_value);
} else if (instr->IsUShr()) {
__ Dsrl(dst, lhs, shift_value);
} else {
__ Drotr(dst, lhs, shift_value);
}
} else {
shift_value -= 32;
if (instr->IsShl()) {
__ Dsll32(dst, lhs, shift_value);
} else if (instr->IsShr()) {
__ Dsra32(dst, lhs, shift_value);
} else if (instr->IsUShr()) {
__ Dsrl32(dst, lhs, shift_value);
} else {
__ Drotr32(dst, lhs, shift_value);
}
}
}
} else {
if (type == DataType::Type::kInt32) {
if (instr->IsShl()) {
__ Sllv(dst, lhs, rhs_reg);
} else if (instr->IsShr()) {
__ Srav(dst, lhs, rhs_reg);
} else if (instr->IsUShr()) {
__ Srlv(dst, lhs, rhs_reg);
} else {
__ Rotrv(dst, lhs, rhs_reg);
}
} else {
if (instr->IsShl()) {
__ Dsllv(dst, lhs, rhs_reg);
} else if (instr->IsShr()) {
__ Dsrav(dst, lhs, rhs_reg);
} else if (instr->IsUShr()) {
__ Dsrlv(dst, lhs, rhs_reg);
} else {
__ Drotrv(dst, lhs, rhs_reg);
}
}
}
break;
}
default:
LOG(FATAL) << "Unexpected shift operation type " << type;
}
}
void LocationsBuilderMIPS64::VisitAdd(HAdd* instruction) {
HandleBinaryOp(instruction);
}
void InstructionCodeGeneratorMIPS64::VisitAdd(HAdd* instruction) {
HandleBinaryOp(instruction);
}
void LocationsBuilderMIPS64::VisitAnd(HAnd* instruction) {
HandleBinaryOp(instruction);
}
void InstructionCodeGeneratorMIPS64::VisitAnd(HAnd* instruction) {
HandleBinaryOp(instruction);
}
void LocationsBuilderMIPS64::VisitArrayGet(HArrayGet* instruction) {
DataType::Type type = instruction->GetType();
bool object_array_get_with_read_barrier =
kEmitCompilerReadBarrier && (type == DataType::Type::kReference);
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
object_array_get_with_read_barrier
? LocationSummary::kCallOnSlowPath
: LocationSummary::kNoCall);
if (object_array_get_with_read_barrier && kUseBakerReadBarrier) {
locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty()); // No caller-save registers.
}
locations->SetInAt(0, Location::RequiresRegister());
locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
if (DataType::IsFloatingPointType(type)) {
locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap);
} else {
// The output overlaps in the case of an object array get with
// read barriers enabled: we do not want the move to overwrite the
// array's location, as we need it to emit the read barrier.
locations->SetOut(Location::RequiresRegister(),
object_array_get_with_read_barrier
? Location::kOutputOverlap
: Location::kNoOutputOverlap);
}
// We need a temporary register for the read barrier marking slow
// path in CodeGeneratorMIPS64::GenerateArrayLoadWithBakerReadBarrier.
if (object_array_get_with_read_barrier && kUseBakerReadBarrier) {
bool temp_needed = instruction->GetIndex()->IsConstant()
? !kBakerReadBarrierThunksEnableForFields
: !kBakerReadBarrierThunksEnableForArrays;
if (temp_needed) {
locations->AddTemp(Location::RequiresRegister());
}
}
}
static auto GetImplicitNullChecker(HInstruction* instruction, CodeGeneratorMIPS64* codegen) {
auto null_checker = [codegen, instruction]() {
codegen->MaybeRecordImplicitNullCheck(instruction);
};
return null_checker;
}
void InstructionCodeGeneratorMIPS64::VisitArrayGet(HArrayGet* instruction) {
LocationSummary* locations = instruction->GetLocations();
Location obj_loc = locations->InAt(0);
GpuRegister obj = obj_loc.AsRegister<GpuRegister>();
Location out_loc = locations->Out();
Location index = locations->InAt(1);
uint32_t data_offset = CodeGenerator::GetArrayDataOffset(instruction);
auto null_checker = GetImplicitNullChecker(instruction, codegen_);
DataType::Type type = instruction->GetType();
const bool maybe_compressed_char_at = mirror::kUseStringCompression &&
instruction->IsStringCharAt();
switch (type) {
case DataType::Type::kBool:
case DataType::Type::kUint8: {
GpuRegister out = out_loc.AsRegister<GpuRegister>();
if (index.IsConstant()) {
size_t offset =
(index.GetConstant()->AsIntConstant()->GetValue() << TIMES_1) + data_offset;
__ LoadFromOffset(kLoadUnsignedByte, out, obj, offset, null_checker);
} else {
__ Daddu(TMP, obj, index.AsRegister<GpuRegister>());
__ LoadFromOffset(kLoadUnsignedByte, out, TMP, data_offset, null_checker);
}
break;
}
case DataType::Type::kInt8: {
GpuRegister out = out_loc.AsRegister<GpuRegister>();
if (index.IsConstant()) {
size_t offset =
(index.GetConstant()->AsIntConstant()->GetValue() << TIMES_1) + data_offset;
__ LoadFromOffset(kLoadSignedByte, out, obj, offset, null_checker);
} else {
__ Daddu(TMP, obj, index.AsRegister<GpuRegister>());
__ LoadFromOffset(kLoadSignedByte, out, TMP, data_offset, null_checker);
}
break;
}
case DataType::Type::kUint16: {
GpuRegister out = out_loc.AsRegister<GpuRegister>();
if (maybe_compressed_char_at) {
uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
__ LoadFromOffset(kLoadWord, TMP, obj, count_offset, null_checker);
__ Dext(TMP, TMP, 0, 1);
static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
"Expecting 0=compressed, 1=uncompressed");
}
if (index.IsConstant()) {
int32_t const_index = index.GetConstant()->AsIntConstant()->GetValue();
if (maybe_compressed_char_at) {
Mips64Label uncompressed_load, done;
__ Bnezc(TMP, &uncompressed_load);
__ LoadFromOffset(kLoadUnsignedByte,
out,
obj,
data_offset + (const_index << TIMES_1));
__ Bc(&done);
__ Bind(&uncompressed_load);
__ LoadFromOffset(kLoadUnsignedHalfword,
out,
obj,
data_offset + (const_index << TIMES_2));
__ Bind(&done);
} else {
__ LoadFromOffset(kLoadUnsignedHalfword,
out,
obj,
data_offset + (const_index << TIMES_2),
null_checker);
}
} else {
GpuRegister index_reg = index.AsRegister<GpuRegister>();
if (maybe_compressed_char_at) {
Mips64Label uncompressed_load, done;
__ Bnezc(TMP, &uncompressed_load);
__ Daddu(TMP, obj, index_reg);
__ LoadFromOffset(kLoadUnsignedByte, out, TMP, data_offset);
__ Bc(&done);
__ Bind(&uncompressed_load);
__ Dlsa(TMP, index_reg, obj, TIMES_2);
__ LoadFromOffset(kLoadUnsignedHalfword, out, TMP, data_offset);
__ Bind(&done);
} else {
__ Dlsa(TMP, index_reg, obj, TIMES_2);
__ LoadFromOffset(kLoadUnsignedHalfword, out, TMP, data_offset, null_checker);
}
}
break;
}
case DataType::Type::kInt16: {
GpuRegister out = out_loc.AsRegister<GpuRegister>();
if (index.IsConstant()) {
size_t offset =
(index.GetConstant()->AsIntConstant()->GetValue() << TIMES_2) + data_offset;
__ LoadFromOffset(kLoadSignedHalfword, out, obj, offset, null_checker);
} else {
__ Dlsa(TMP, index.AsRegister<GpuRegister>(), obj, TIMES_2);
__ LoadFromOffset(kLoadSignedHalfword, out, TMP, data_offset, null_checker);
}
break;
}
case DataType::Type::kInt32: {
DCHECK_EQ(sizeof(mirror::HeapReference<mirror::Object>), sizeof(int32_t));
GpuRegister out = out_loc.AsRegister<GpuRegister>();
LoadOperandType load_type =
(type == DataType::Type::kReference) ? kLoadUnsignedWord : kLoadWord;
if (index.IsConstant()) {
size_t offset =
(index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
__ LoadFromOffset(load_type, out, obj, offset, null_checker);
} else {
__ Dlsa(TMP, index.AsRegister<