7ff2dd4117
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 .
369 lines
11 KiB
C++
369 lines
11 KiB
C++
// Copyright (c) 2012, 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_X64.
|
|
#if defined(TARGET_ARCH_X64)
|
|
|
|
#include "vm/assembler.h"
|
|
#include "vm/code_patcher.h"
|
|
#include "vm/cpu.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/flow_graph_compiler.h"
|
|
#include "vm/instructions.h"
|
|
#include "vm/object.h"
|
|
#include "vm/raw_object.h"
|
|
|
|
namespace dart {
|
|
|
|
|
|
static bool MatchesPattern(uword addr, int16_t* pattern, intptr_t size) {
|
|
uint8_t* bytes = reinterpret_cast<uint8_t*>(addr);
|
|
for (intptr_t i = 0; i < size; i++) {
|
|
int16_t val = pattern[i];
|
|
if ((val >= 0) && (val != bytes[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
intptr_t IndexFromPPLoad(uword start) {
|
|
int32_t offset = *reinterpret_cast<int32_t*>(start);
|
|
return ObjectPool::IndexFromOffset(offset);
|
|
}
|
|
|
|
|
|
intptr_t IndexFromPPLoadDisp8(uword start) {
|
|
int8_t offset = *reinterpret_cast<int8_t*>(start);
|
|
return ObjectPool::IndexFromOffset(offset);
|
|
}
|
|
|
|
|
|
class UnoptimizedCall : public ValueObject {
|
|
public:
|
|
UnoptimizedCall(uword return_address, const Code& code)
|
|
: object_pool_(ObjectPool::Handle(code.GetObjectPool())),
|
|
start_(return_address - kCallPatternSize) {
|
|
ASSERT((kCallPatternSize - 7) == Assembler::kCallExternalLabelSize);
|
|
ASSERT(IsValid());
|
|
}
|
|
|
|
static const int kCallPatternSize = 22;
|
|
|
|
bool IsValid() const {
|
|
static int16_t pattern[kCallPatternSize] = {
|
|
0x49, 0x8b, 0x9f, -1, -1, -1, -1, // movq RBX, [PP + offs]
|
|
0x4d, 0x8b, 0xa7, -1, -1, -1, -1, // movq CR, [PP + offs]
|
|
0x4d, 0x8b, 0x5c, 0x24, 0x07, // movq TMP, [CR + entry_point_offs]
|
|
0x41, 0xff, 0xd3 // callq TMP
|
|
};
|
|
return MatchesPattern(start_, pattern, kCallPatternSize);
|
|
}
|
|
|
|
intptr_t argument_index() const {
|
|
return IndexFromPPLoad(start_ + 3);
|
|
}
|
|
|
|
RawObject* ic_data() const {
|
|
return object_pool_.ObjectAt(argument_index());
|
|
}
|
|
|
|
RawCode* target() const {
|
|
intptr_t index = IndexFromPPLoad(start_ + 10);
|
|
Code& code = Code::Handle();
|
|
code ^= object_pool_.ObjectAt(index);
|
|
return code.raw();
|
|
}
|
|
|
|
void set_target(const Code& target) const {
|
|
intptr_t index = IndexFromPPLoad(start_ + 10);
|
|
object_pool_.SetObjectAt(index, target);
|
|
// No need to flush the instruction cache, since the code is not modified.
|
|
}
|
|
|
|
protected:
|
|
const ObjectPool& object_pool_;
|
|
|
|
private:
|
|
uword start_;
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(UnoptimizedCall);
|
|
};
|
|
|
|
|
|
class NativeCall : public UnoptimizedCall {
|
|
public:
|
|
NativeCall(uword return_address, const Code& code)
|
|
: UnoptimizedCall(return_address, code) {
|
|
}
|
|
|
|
NativeFunction native_function() const {
|
|
return reinterpret_cast<NativeFunction>(
|
|
object_pool_.RawValueAt(argument_index()));
|
|
}
|
|
|
|
void set_native_function(NativeFunction func) const {
|
|
object_pool_.SetRawValueAt(argument_index(),
|
|
reinterpret_cast<uword>(func));
|
|
}
|
|
|
|
private:
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(NativeCall);
|
|
};
|
|
|
|
|
|
class InstanceCall : public UnoptimizedCall {
|
|
public:
|
|
InstanceCall(uword return_address, const Code& code)
|
|
: UnoptimizedCall(return_address, code) {
|
|
#if defined(DEBUG)
|
|
ICData& test_ic_data = ICData::Handle();
|
|
test_ic_data ^= ic_data();
|
|
ASSERT(test_ic_data.NumArgsTested() > 0);
|
|
#endif // DEBUG
|
|
}
|
|
|
|
private:
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(InstanceCall);
|
|
};
|
|
|
|
|
|
class UnoptimizedStaticCall : public UnoptimizedCall {
|
|
public:
|
|
UnoptimizedStaticCall(uword return_address, const Code& code)
|
|
: UnoptimizedCall(return_address, code) {
|
|
#if defined(DEBUG)
|
|
ICData& test_ic_data = ICData::Handle();
|
|
test_ic_data ^= ic_data();
|
|
ASSERT(test_ic_data.NumArgsTested() >= 0);
|
|
#endif // DEBUG
|
|
}
|
|
|
|
private:
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(UnoptimizedStaticCall);
|
|
};
|
|
|
|
|
|
// The expected pattern of a call where the target is loaded from
|
|
// the object pool.
|
|
class PoolPointerCall : public ValueObject {
|
|
public:
|
|
explicit PoolPointerCall(uword return_address, const Code& code)
|
|
: start_(return_address - kCallPatternSize),
|
|
object_pool_(ObjectPool::Handle(code.GetObjectPool())) {
|
|
ASSERT(IsValid());
|
|
}
|
|
|
|
static const int kCallPatternSize = 15;
|
|
|
|
bool IsValid() const {
|
|
static int16_t pattern[kCallPatternSize] = {
|
|
0x4d, 0x8b, 0xa7, -1, -1, -1, -1, // movq CR, [PP + offs]
|
|
0x4d, 0x8b, 0x5c, 0x24, 0x07, // movq TMP, [CR + entry_point_off]
|
|
0x41, 0xff, 0xd3 // callq TMP
|
|
};
|
|
return MatchesPattern(start_, pattern, kCallPatternSize);
|
|
}
|
|
|
|
intptr_t pp_index() const {
|
|
return IndexFromPPLoad(start_ + 3);
|
|
}
|
|
|
|
RawCode* Target() const {
|
|
Code& code = Code::Handle();
|
|
code ^= object_pool_.ObjectAt(pp_index());
|
|
return code.raw();
|
|
}
|
|
|
|
void SetTarget(const Code& target) const {
|
|
object_pool_.SetObjectAt(pp_index(), target);
|
|
// No need to flush the instruction cache, since the code is not modified.
|
|
}
|
|
|
|
protected:
|
|
uword start_;
|
|
const ObjectPool& object_pool_;
|
|
|
|
private:
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(PoolPointerCall);
|
|
};
|
|
|
|
|
|
// Instance call that can switch between a direct monomorphic call, an IC call,
|
|
// and a megamorphic call.
|
|
// load guarded cid load ICData load MegamorphicCache
|
|
// load monomorphic target <-> load ICLookup stub -> load MMLookup stub
|
|
// call target.entry call stub.entry call stub.entry
|
|
class SwitchableCall : public ValueObject {
|
|
public:
|
|
SwitchableCall(uword return_address, const Code& code)
|
|
: start_(return_address - kCallPatternSize),
|
|
object_pool_(ObjectPool::Handle(code.GetObjectPool())) {
|
|
ASSERT(IsValid());
|
|
}
|
|
|
|
static const int kCallPatternSize = 21;
|
|
|
|
bool IsValid() const {
|
|
static int16_t pattern[kCallPatternSize] = {
|
|
0x49, 0x8b, 0x9f, -1, -1, -1, -1, // movq rbx, [PP + cache_offs]
|
|
0x4d, 0x8b, 0xa7, -1, -1, -1, -1, // movq r12, [PP + code_offs]
|
|
0x49, 0x8b, 0x4c, 0x24, 0x0f, // movq rcx, [r12 + entrypoint_off]
|
|
0xff, 0xd1, // call rcx
|
|
};
|
|
ASSERT(ARRAY_SIZE(pattern) == kCallPatternSize);
|
|
return MatchesPattern(start_, pattern, kCallPatternSize);
|
|
}
|
|
|
|
intptr_t data_index() const {
|
|
return IndexFromPPLoad(start_ + 3);
|
|
}
|
|
intptr_t target_index() const {
|
|
return IndexFromPPLoad(start_ + 10);
|
|
}
|
|
|
|
RawObject* data() const {
|
|
return object_pool_.ObjectAt(data_index());
|
|
}
|
|
RawCode* target() const {
|
|
return reinterpret_cast<RawCode*>(object_pool_.ObjectAt(target_index()));
|
|
}
|
|
|
|
void SetData(const Object& data) const {
|
|
ASSERT(!Object::Handle(object_pool_.ObjectAt(data_index())).IsCode());
|
|
object_pool_.SetObjectAt(data_index(), data);
|
|
// No need to flush the instruction cache, since the code is not modified.
|
|
}
|
|
|
|
void SetTarget(const Code& target) const {
|
|
ASSERT(Object::Handle(object_pool_.ObjectAt(target_index())).IsCode());
|
|
object_pool_.SetObjectAt(target_index(), target);
|
|
// No need to flush the instruction cache, since the code is not modified.
|
|
}
|
|
|
|
protected:
|
|
uword start_;
|
|
const ObjectPool& object_pool_;
|
|
|
|
private:
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(SwitchableCall);
|
|
};
|
|
|
|
|
|
|
|
RawCode* CodePatcher::GetStaticCallTargetAt(uword return_address,
|
|
const Code& code) {
|
|
ASSERT(code.ContainsInstructionAt(return_address));
|
|
PoolPointerCall call(return_address, code);
|
|
return call.Target();
|
|
}
|
|
|
|
|
|
void CodePatcher::PatchStaticCallAt(uword return_address,
|
|
const Code& code,
|
|
const Code& new_target) {
|
|
PatchPoolPointerCallAt(return_address, code, new_target);
|
|
}
|
|
|
|
|
|
void CodePatcher::PatchPoolPointerCallAt(uword return_address,
|
|
const Code& code,
|
|
const Code& new_target) {
|
|
ASSERT(code.ContainsInstructionAt(return_address));
|
|
PoolPointerCall call(return_address, code);
|
|
call.SetTarget(new_target);
|
|
}
|
|
|
|
|
|
RawCode* CodePatcher::GetInstanceCallAt(uword return_address,
|
|
const Code& code,
|
|
ICData* ic_data) {
|
|
ASSERT(code.ContainsInstructionAt(return_address));
|
|
InstanceCall call(return_address, code);
|
|
if (ic_data != NULL) {
|
|
*ic_data ^= call.ic_data();
|
|
}
|
|
return call.target();
|
|
}
|
|
|
|
|
|
intptr_t CodePatcher::InstanceCallSizeInBytes() {
|
|
return InstanceCall::kCallPatternSize;
|
|
}
|
|
|
|
|
|
void CodePatcher::InsertDeoptimizationCallAt(uword start, uword target) {
|
|
// The inserted call should not overlap the lazy deopt jump code.
|
|
ASSERT(start + ShortCallPattern::pattern_length_in_bytes() <= target);
|
|
*reinterpret_cast<uint8_t*>(start) = 0xE8;
|
|
ShortCallPattern call(start);
|
|
call.SetTargetAddress(target);
|
|
CPU::FlushICache(start, ShortCallPattern::pattern_length_in_bytes());
|
|
}
|
|
|
|
|
|
RawFunction* CodePatcher::GetUnoptimizedStaticCallAt(
|
|
uword return_address, const Code& code, ICData* ic_data_result) {
|
|
ASSERT(code.ContainsInstructionAt(return_address));
|
|
UnoptimizedStaticCall static_call(return_address, code);
|
|
ICData& ic_data = ICData::Handle();
|
|
ic_data ^= static_call.ic_data();
|
|
if (ic_data_result != NULL) {
|
|
*ic_data_result = ic_data.raw();
|
|
}
|
|
return ic_data.GetTargetAt(0);
|
|
}
|
|
|
|
|
|
void CodePatcher::PatchSwitchableCallAt(uword return_address,
|
|
const Code& caller_code,
|
|
const Object& data,
|
|
const Code& target) {
|
|
ASSERT(caller_code.ContainsInstructionAt(return_address));
|
|
SwitchableCall call(return_address, caller_code);
|
|
call.SetData(data);
|
|
call.SetTarget(target);
|
|
}
|
|
|
|
|
|
RawCode* CodePatcher::GetSwitchableCallTargetAt(uword return_address,
|
|
const Code& caller_code) {
|
|
ASSERT(caller_code.ContainsInstructionAt(return_address));
|
|
SwitchableCall call(return_address, caller_code);
|
|
return call.target();
|
|
}
|
|
|
|
|
|
RawObject* CodePatcher::GetSwitchableCallDataAt(uword return_address,
|
|
const Code& caller_code) {
|
|
ASSERT(caller_code.ContainsInstructionAt(return_address));
|
|
SwitchableCall call(return_address, caller_code);
|
|
return call.data();
|
|
}
|
|
|
|
|
|
void CodePatcher::PatchNativeCallAt(uword return_address,
|
|
const Code& code,
|
|
NativeFunction target,
|
|
const Code& trampoline) {
|
|
ASSERT(code.ContainsInstructionAt(return_address));
|
|
NativeCall call(return_address, code);
|
|
call.set_target(trampoline);
|
|
call.set_native_function(target);
|
|
}
|
|
|
|
|
|
RawCode* CodePatcher::GetNativeCallAt(uword return_address,
|
|
const Code& code,
|
|
NativeFunction* target) {
|
|
ASSERT(code.ContainsInstructionAt(return_address));
|
|
NativeCall call(return_address, code);
|
|
*target = call.native_function();
|
|
return call.target();
|
|
}
|
|
|
|
} // namespace dart
|
|
|
|
#endif // defined TARGET_ARCH_X64
|