Files
sdk/runtime/vm/instructions_arm.cc
T
Ryan Macnak 7ff2dd4117 Optimize AOT's switchable calls for the monomorphic case.
The call sequence is very similar to a classic IC call, except the guarded class and the target are loaded indirectly from the constant pool instead of as immediates. In the monomorphic case, we call directly to the expected target with a class check in the callee. In the unlinked, polymorphic and megamorphic cases, we call a stub; these case are now call-through instead of call-and-return.

Every code, except stubs involved in switchable calls, includes the class check sequence at the beginning. So we now distinguish between a checked and an unchecked entry point. Generated code except the switchable call continues to use the unchecked entry point.

PC offsets are calculated relative to the beginning of the instruction stream, rather than either entry point.

BUG=
R=fschneider@google.com

Review URL: https://codereview.chromium.org/2226893002 .
2016-08-12 11:18:35 -07:00

380 lines
13 KiB
C++

// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/globals.h" // Needed here to get TARGET_ARCH_ARM.
#if defined(TARGET_ARCH_ARM)
#include "vm/assembler.h"
#include "vm/constants_arm.h"
#include "vm/cpu.h"
#include "vm/instructions.h"
#include "vm/object.h"
namespace dart {
CallPattern::CallPattern(uword pc, const Code& code)
: object_pool_(ObjectPool::Handle(code.GetObjectPool())),
end_(pc),
ic_data_load_end_(0),
target_code_pool_index_(-1),
ic_data_(ICData::Handle()) {
ASSERT(code.ContainsInstructionAt(pc));
// Last instruction: blx lr.
ASSERT(*(reinterpret_cast<uword*>(end_) - 1) == 0xe12fff3e);
Register reg;
ic_data_load_end_ =
InstructionPattern::DecodeLoadWordFromPool(end_ - 2 * Instr::kInstrSize,
&reg,
&target_code_pool_index_);
ASSERT(reg == CODE_REG);
}
int CallPattern::DeoptCallPatternLengthInInstructions() {
const ARMVersion version = TargetCPUFeatures::arm_version();
if ((version == ARMv5TE) || (version == ARMv6)) {
return 5;
} else {
ASSERT(version == ARMv7);
return 3;
}
}
int CallPattern::DeoptCallPatternLengthInBytes() {
return DeoptCallPatternLengthInInstructions() * Instr::kInstrSize;
}
NativeCallPattern::NativeCallPattern(uword pc, const Code& code)
: object_pool_(ObjectPool::Handle(code.GetObjectPool())),
end_(pc),
native_function_pool_index_(-1),
target_code_pool_index_(-1) {
ASSERT(code.ContainsInstructionAt(pc));
// Last instruction: blx lr.
ASSERT(*(reinterpret_cast<uword*>(end_) - 1) == 0xe12fff3e);
Register reg;
uword native_function_load_end =
InstructionPattern::DecodeLoadWordFromPool(end_ - 2 * Instr::kInstrSize,
&reg,
&target_code_pool_index_);
ASSERT(reg == CODE_REG);
InstructionPattern::DecodeLoadWordFromPool(native_function_load_end,
&reg,
&native_function_pool_index_);
ASSERT(reg == R9);
}
RawCode* NativeCallPattern::target() const {
return reinterpret_cast<RawCode*>(
object_pool_.ObjectAt(target_code_pool_index_));
}
void NativeCallPattern::set_target(const Code& new_target) const {
object_pool_.SetObjectAt(target_code_pool_index_, new_target);
// No need to flush the instruction cache, since the code is not modified.
}
NativeFunction NativeCallPattern::native_function() const {
return reinterpret_cast<NativeFunction>(
object_pool_.RawValueAt(native_function_pool_index_));
}
void NativeCallPattern::set_native_function(NativeFunction func) const {
object_pool_.SetRawValueAt(native_function_pool_index_,
reinterpret_cast<uword>(func));
}
// Decodes a load sequence ending at 'end' (the last instruction of the load
// sequence is the instruction before the one at end). Returns a pointer to
// the first instruction in the sequence. Returns the register being loaded
// and the loaded object in the output parameters 'reg' and 'obj'
// respectively.
uword InstructionPattern::DecodeLoadObject(uword end,
const ObjectPool& object_pool,
Register* reg,
Object* obj) {
uword start = 0;
Instr* instr = Instr::At(end - Instr::kInstrSize);
if ((instr->InstructionBits() & 0xfff00000) == 0xe5900000) {
// ldr reg, [reg, #+offset]
intptr_t index = 0;
start = DecodeLoadWordFromPool(end, reg, &index);
*obj = object_pool.ObjectAt(index);
} else {
intptr_t value = 0;
start = DecodeLoadWordImmediate(end, reg, &value);
*obj = reinterpret_cast<RawObject*>(value);
}
return start;
}
// Decodes a load sequence ending at 'end' (the last instruction of the load
// sequence is the instruction before the one at end). Returns a pointer to
// the first instruction in the sequence. Returns the register being loaded
// and the loaded immediate value in the output parameters 'reg' and 'value'
// respectively.
uword InstructionPattern::DecodeLoadWordImmediate(uword end,
Register* reg,
intptr_t* value) {
uword start = end - Instr::kInstrSize;
int32_t instr = Instr::At(start)->InstructionBits();
intptr_t imm = 0;
const ARMVersion version = TargetCPUFeatures::arm_version();
if ((version == ARMv5TE) || (version == ARMv6)) {
ASSERT((instr & 0xfff00000) == 0xe3800000); // orr rd, rd, byte0
imm |= (instr & 0x000000ff);
start -= Instr::kInstrSize;
instr = Instr::At(start)->InstructionBits();
ASSERT((instr & 0xfff00000) == 0xe3800c00); // orr rd, rd, (byte1 rot 12)
imm |= (instr & 0x000000ff);
start -= Instr::kInstrSize;
instr = Instr::At(start)->InstructionBits();
ASSERT((instr & 0xfff00f00) == 0xe3800800); // orr rd, rd, (byte2 rot 8)
imm |= (instr & 0x000000ff);
start -= Instr::kInstrSize;
instr = Instr::At(start)->InstructionBits();
ASSERT((instr & 0xffff0f00) == 0xe3a00400); // mov rd, (byte3 rot 4)
imm |= (instr & 0x000000ff);
*reg = static_cast<Register>((instr & 0x0000f000) >> 12);
*value = imm;
} else {
ASSERT(version == ARMv7);
if ((instr & 0xfff00000) == 0xe3400000) { // movt reg, #imm_hi
imm |= (instr & 0xf0000) << 12;
imm |= (instr & 0xfff) << 16;
start -= Instr::kInstrSize;
instr = Instr::At(start)->InstructionBits();
}
ASSERT((instr & 0xfff00000) == 0xe3000000); // movw reg, #imm_lo
imm |= (instr & 0xf0000) >> 4;
imm |= instr & 0xfff;
*reg = static_cast<Register>((instr & 0xf000) >> 12);
*value = imm;
}
return start;
}
static bool IsLoadWithOffset(int32_t instr, Register base,
intptr_t* offset, Register* dst) {
if ((instr & 0xffff0000) == (0xe5900000 | (base << 16))) {
// ldr reg, [base, #+offset]
*offset = instr & 0xfff;
*dst = static_cast<Register>((instr & 0xf000) >> 12);
return true;
}
return false;
}
// Decodes a load sequence ending at 'end' (the last instruction of the load
// sequence is the instruction before the one at end). Returns a pointer to
// the first instruction in the sequence. Returns the register being loaded
// and the index in the pool being read from in the output parameters 'reg'
// and 'index' respectively.
uword InstructionPattern::DecodeLoadWordFromPool(uword end,
Register* reg,
intptr_t* index) {
uword start = end - Instr::kInstrSize;
int32_t instr = Instr::At(start)->InstructionBits();
intptr_t offset = 0;
if (IsLoadWithOffset(instr, PP, &offset, reg)) {
// ldr reg, [PP, #+offset]
} else {
ASSERT((instr & 0xfff00000) == 0xe5900000); // ldr reg, [reg, #+offset]
offset = instr & 0xfff;
start -= Instr::kInstrSize;
instr = Instr::At(start)->InstructionBits();
if ((instr & 0xffff0000) == (0xe2850000 | (PP << 16))) {
// add reg, pp, operand
const intptr_t rot = (instr & 0xf00) >> 7;
const intptr_t imm8 = instr & 0xff;
offset += (imm8 >> rot) | (imm8 << (32 - rot));
*reg = static_cast<Register>((instr & 0xf000) >> 12);
} else {
ASSERT((instr & 0xffff0000) == (0xe0800000 | (PP << 16)));
// add reg, pp, reg
end = DecodeLoadWordImmediate(end, reg, &offset);
}
}
*index = ObjectPool::IndexFromOffset(offset);
return start;
}
bool DecodeLoadObjectFromPoolOrThread(uword pc,
const Code& code,
Object* obj) {
ASSERT(code.ContainsInstructionAt(pc));
int32_t instr = Instr::At(pc)->InstructionBits();
intptr_t offset;
Register dst;
if (IsLoadWithOffset(instr, PP, &offset, &dst)) {
intptr_t index = ObjectPool::IndexFromOffset(offset);
const ObjectPool& pool = ObjectPool::Handle(code.object_pool());
if (pool.InfoAt(index) == ObjectPool::kTaggedObject) {
*obj = pool.ObjectAt(index);
return true;
}
} else if (IsLoadWithOffset(instr, THR, &offset, &dst)) {
return Thread::ObjectAtOffset(offset, obj);
}
// TODO(rmacnak): Sequence for loads beyond 12 bits.
return false;
}
RawICData* CallPattern::IcData() {
if (ic_data_.IsNull()) {
Register reg;
InstructionPattern::DecodeLoadObject(ic_data_load_end_,
object_pool_,
&reg,
&ic_data_);
ASSERT(reg == R9);
}
return ic_data_.raw();
}
RawCode* CallPattern::TargetCode() const {
return reinterpret_cast<RawCode*>(
object_pool_.ObjectAt(target_code_pool_index_));
}
void CallPattern::SetTargetCode(const Code& target_code) const {
object_pool_.SetObjectAt(target_code_pool_index_, target_code);
}
void CallPattern::InsertDeoptCallAt(uword pc, uword target_address) {
const ARMVersion version = TargetCPUFeatures::arm_version();
if ((version == ARMv5TE) || (version == ARMv6)) {
const uint32_t byte0 = (target_address & 0x000000ff);
const uint32_t byte1 = (target_address & 0x0000ff00) >> 8;
const uint32_t byte2 = (target_address & 0x00ff0000) >> 16;
const uint32_t byte3 = (target_address & 0xff000000) >> 24;
const uword mov_ip = 0xe3a0c400 | byte3; // mov ip, (byte3 rot 4)
const uword or1_ip = 0xe38cc800 | byte2; // orr ip, ip, (byte2 rot 8)
const uword or2_ip = 0xe38ccc00 | byte1; // orr ip, ip, (byte1 rot 12)
const uword or3_ip = 0xe38cc000 | byte0; // orr ip, ip, byte0
const uword blx_ip = 0xe12fff3c;
*reinterpret_cast<uword*>(pc + (0 * Instr::kInstrSize)) = mov_ip;
*reinterpret_cast<uword*>(pc + (1 * Instr::kInstrSize)) = or1_ip;
*reinterpret_cast<uword*>(pc + (2 * Instr::kInstrSize)) = or2_ip;
*reinterpret_cast<uword*>(pc + (3 * Instr::kInstrSize)) = or3_ip;
*reinterpret_cast<uword*>(pc + (4 * Instr::kInstrSize)) = blx_ip;
ASSERT(DeoptCallPatternLengthInBytes() == 5 * Instr::kInstrSize);
CPU::FlushICache(pc, DeoptCallPatternLengthInBytes());
} else {
ASSERT(version == ARMv7);
const uint16_t target_lo = target_address & 0xffff;
const uint16_t target_hi = target_address >> 16;
const uword movw_ip =
0xe300c000 | ((target_lo >> 12) << 16) | (target_lo & 0xfff);
const uword movt_ip =
0xe340c000 | ((target_hi >> 12) << 16) | (target_hi & 0xfff);
const uword blx_ip = 0xe12fff3c;
*reinterpret_cast<uword*>(pc + (0 * Instr::kInstrSize)) = movw_ip;
*reinterpret_cast<uword*>(pc + (1 * Instr::kInstrSize)) = movt_ip;
*reinterpret_cast<uword*>(pc + (2 * Instr::kInstrSize)) = blx_ip;
ASSERT(DeoptCallPatternLengthInBytes() == 3 * Instr::kInstrSize);
CPU::FlushICache(pc, DeoptCallPatternLengthInBytes());
}
}
SwitchableCallPattern::SwitchableCallPattern(uword pc, const Code& code)
: object_pool_(ObjectPool::Handle(code.GetObjectPool())),
data_pool_index_(-1),
target_pool_index_(-1) {
ASSERT(code.ContainsInstructionAt(pc));
// Last instruction: blx lr.
ASSERT(*(reinterpret_cast<uword*>(pc) - 1) == 0xe12fff3e);
Register reg;
uword stub_load_end =
InstructionPattern::DecodeLoadWordFromPool(pc - 2 * Instr::kInstrSize,
&reg,
&target_pool_index_);
ASSERT(reg == CODE_REG);
InstructionPattern::DecodeLoadWordFromPool(stub_load_end,
&reg,
&data_pool_index_);
ASSERT(reg == R9);
}
RawObject* SwitchableCallPattern::data() const {
return object_pool_.ObjectAt(data_pool_index_);
}
RawCode* SwitchableCallPattern::target() const {
return reinterpret_cast<RawCode*>(
object_pool_.ObjectAt(target_pool_index_));
}
void SwitchableCallPattern::SetData(const Object& data) const {
ASSERT(!Object::Handle(object_pool_.ObjectAt(data_pool_index_)).IsCode());
object_pool_.SetObjectAt(data_pool_index_, data);
}
void SwitchableCallPattern::SetTarget(const Code& target) const {
ASSERT(Object::Handle(object_pool_.ObjectAt(target_pool_index_)).IsCode());
object_pool_.SetObjectAt(target_pool_index_, target);
}
ReturnPattern::ReturnPattern(uword pc)
: pc_(pc) {
}
bool ReturnPattern::IsValid() const {
Instr* bx_lr = Instr::At(pc_);
const int32_t B4 = 1 << 4;
const int32_t B21 = 1 << 21;
const int32_t B24 = 1 << 24;
int32_t instruction = (static_cast<int32_t>(AL) << kConditionShift) |
B24 | B21 | (0xfff << 8) | B4 |
(static_cast<int32_t>(LR) << kRmShift);
const ARMVersion version = TargetCPUFeatures::arm_version();
if ((version == ARMv5TE) || (version == ARMv6)) {
return bx_lr->InstructionBits() == instruction;
} else {
ASSERT(version == ARMv7);
return bx_lr->InstructionBits() == instruction;
}
return false;
}
} // namespace dart
#endif // defined TARGET_ARCH_ARM