| // 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/code_patcher.h" | |
| #include "vm/cpu.h" | |
| #include "vm/dart_entry.h" | |
| #include "vm/instructions.h" | |
| #include "vm/object.h" | |
| #include "vm/object_store.h" | |
| #include "vm/raw_object.h" | |
| #include "vm/reverse_pc_lookup_cache.h" | |
| namespace dart { | |
| // callq [CODE_REG + entry_point_offset (disp8)] | |
| static const int16_t kCallPatternJIT[] = { | |
| 0x41, 0xff, 0x54, 0x24, -1, | |
| }; | |
| // callq [TMP + entry_point_offset (disp8)] | |
| static const int16_t kCallPatternAOT[] = { | |
| 0x41, | |
| 0xff, | |
| 0x53, | |
| -1, | |
| }; | |
| static const intptr_t kLoadCodeFromPoolInstructionLength = 3; | |
| static const intptr_t kLoadCodeFromPoolDisp8PatternLength = | |
| kLoadCodeFromPoolInstructionLength + 1; | |
| static const intptr_t kLoadCodeFromPoolDisp32PatternLength = | |
| kLoadCodeFromPoolInstructionLength + 4; | |
| // movq CODE_REG, [PP + disp8] | |
| static const int16_t | |
| kLoadCodeFromPoolDisp8JIT[kLoadCodeFromPoolDisp8PatternLength] = { | |
| 0x4d, | |
| 0x8b, | |
| 0x67, | |
| -1, | |
| }; | |
| // movq CODE_REG, [PP + disp32] | |
| static const int16_t | |
| kLoadCodeFromPoolDisp32JIT[kLoadCodeFromPoolDisp32PatternLength] = { | |
| 0x4d, 0x8b, 0xa7, -1, -1, -1, -1, | |
| }; | |
| // movq TMP, [PP + disp8] | |
| static const int16_t | |
| kLoadCodeFromPoolDisp8AOT[kLoadCodeFromPoolDisp8PatternLength] = { | |
| 0x4d, | |
| 0x8b, | |
| 0x5f, | |
| -1, | |
| }; | |
| // movq TMP, [PP + disp32] | |
| static const int16_t | |
| kLoadCodeFromPoolDisp32AOT[kLoadCodeFromPoolDisp32PatternLength] = { | |
| 0x4d, 0x8b, 0x9f, -1, -1, -1, -1, | |
| }; | |
| static void MatchCallPattern(uword* pc) { | |
| const int16_t* call_pattern = | |
| FLAG_precompiled_mode ? kCallPatternAOT : kCallPatternJIT; | |
| const intptr_t call_pattern_length = FLAG_precompiled_mode | |
| ? ARRAY_SIZE(kCallPatternAOT) | |
| : ARRAY_SIZE(kCallPatternJIT); | |
| // callq [reg + entry_point_offset] | |
| if (MatchesPattern(*pc, call_pattern, call_pattern_length)) { | |
| *pc -= call_pattern_length; | |
| } else { | |
| FATAL("Expected `call [%s + offs]` at %" Px, | |
| FLAG_precompiled_mode ? "TMP" : "CODE_REG", *pc); | |
| } | |
| } | |
| static void MatchDataLoadFromPool(uword* pc, intptr_t* data_index) { | |
| // movq RBX, [PP + offset] | |
| static int16_t load_data_disp8[] = { | |
| 0x49, | |
| 0x8b, | |
| 0x5f, | |
| -1, // | |
| }; | |
| static int16_t load_data_disp32[] = { | |
| 0x49, 0x8b, 0x9f, -1, -1, -1, -1, | |
| }; | |
| if (MatchesPattern(*pc, load_data_disp8, ARRAY_SIZE(load_data_disp8))) { | |
| *pc -= ARRAY_SIZE(load_data_disp8); | |
| *data_index = IndexFromPPLoadDisp8(*pc + 3); | |
| } else if (MatchesPattern(*pc, load_data_disp32, | |
| ARRAY_SIZE(load_data_disp32))) { | |
| *pc -= ARRAY_SIZE(load_data_disp32); | |
| *data_index = IndexFromPPLoadDisp32(*pc + 3); | |
| } else { | |
| FATAL("Expected `movq RBX, [PP + imm8|imm32]` at %" Px, *pc); | |
| } | |
| } | |
| static void MatchCodeLoadFromPool(uword* pc, intptr_t* code_index) { | |
| const int16_t* load_code_disp8_pattern = FLAG_precompiled_mode | |
| ? kLoadCodeFromPoolDisp8AOT | |
| : kLoadCodeFromPoolDisp8JIT; | |
| const int16_t* load_code_disp32_pattern = FLAG_precompiled_mode | |
| ? kLoadCodeFromPoolDisp32AOT | |
| : kLoadCodeFromPoolDisp32JIT; | |
| if (MatchesPattern(*pc, load_code_disp8_pattern, | |
| kLoadCodeFromPoolDisp8PatternLength)) { | |
| *pc -= kLoadCodeFromPoolDisp8PatternLength; | |
| *code_index = | |
| IndexFromPPLoadDisp8(*pc + kLoadCodeFromPoolInstructionLength); | |
| } else if (MatchesPattern(*pc, load_code_disp32_pattern, | |
| kLoadCodeFromPoolDisp32PatternLength)) { | |
| *pc -= kLoadCodeFromPoolDisp32PatternLength; | |
| *code_index = | |
| IndexFromPPLoadDisp32(*pc + kLoadCodeFromPoolInstructionLength); | |
| } else { | |
| FATAL("Expected `movq %s, [PP + imm8|imm32]` at %" Px, | |
| FLAG_precompiled_mode ? "TMP" : "CODE_REG", *pc); | |
| } | |
| } | |
| class UnoptimizedCall : public ValueObject { | |
| public: | |
| UnoptimizedCall(uword return_address, const Code& code) | |
| : object_pool_(ObjectPool::Handle(code.GetObjectPool())), | |
| code_index_(-1), | |
| argument_index_(-1) { | |
| uword pc = return_address; | |
| MatchCallPattern(&pc); | |
| MatchDataLoadFromPool(&pc, &argument_index_); | |
| MatchCodeLoadFromPool(&pc, &code_index_); | |
| ASSERT(Object::Handle( | |
| object_pool_.ObjectAt<std::memory_order_acquire>(code_index_)) | |
| .IsCode()); | |
| } | |
| intptr_t argument_index() const { return argument_index_; } | |
| CodePtr target() const { | |
| Code& code = Code::Handle(); | |
| code ^= object_pool_.ObjectAt<std::memory_order_acquire>(code_index_); | |
| return code.ptr(); | |
| } | |
| void set_target(const Code& target) const { | |
| object_pool_.SetObjectAt<std::memory_order_release>(code_index_, target); | |
| // No need to flush the instruction cache, since the code is not modified. | |
| } | |
| protected: | |
| const ObjectPool& object_pool_; | |
| intptr_t code_index_; | |
| intptr_t argument_index_; | |
| private: | |
| DISALLOW_IMPLICIT_CONSTRUCTORS(UnoptimizedCall); | |
| }; | |
| class NativeCall : public ValueObject { | |
| public: | |
| NativeCall(uword return_address, const Code& code) | |
| : object_pool_(ObjectPool::Handle(code.GetObjectPool())), | |
| code_index_(-1), | |
| argument_index_(-1) { | |
| uword pc = return_address; | |
| MatchCallPattern(&pc); | |
| MatchCodeLoadFromPool(&pc, &code_index_); | |
| MatchDataLoadFromPool(&pc, &argument_index_); | |
| ASSERT(Object::Handle( | |
| object_pool_.ObjectAt<std::memory_order_acquire>(code_index_)) | |
| .IsCode()); | |
| } | |
| intptr_t argument_index() const { return argument_index_; } | |
| NativeFunction native_function() const { | |
| return reinterpret_cast<NativeFunction>( | |
| object_pool_.RawValueAt(argument_index())); | |
| } | |
| void set_native_function(NativeFunction func) const { | |
| object_pool_.SetRawValueAt<std::memory_order_relaxed>( | |
| argument_index(), reinterpret_cast<uword>(func)); | |
| } | |
| CodePtr target() const { | |
| Code& code = Code::Handle(); | |
| code ^= object_pool_.ObjectAt<std::memory_order_acquire>(code_index_); | |
| return code.ptr(); | |
| } | |
| void set_target(const Code& target) const { | |
| object_pool_.SetObjectAt<std::memory_order_release>(code_index_, target); | |
| // No need to flush the instruction cache, since the code is not modified. | |
| } | |
| private: | |
| const ObjectPool& object_pool_; | |
| intptr_t code_index_; | |
| intptr_t argument_index_; | |
| DISALLOW_IMPLICIT_CONSTRUCTORS(NativeCall); | |
| }; | |
| class InstanceCall : public UnoptimizedCall { | |
| public: | |
| InstanceCall(uword return_address, const Code& code) | |
| : UnoptimizedCall(return_address, code) { | |
| #if defined(DEBUG) | |
| Object& test_data = Object::Handle(data()); | |
| ASSERT(test_data.IsArray() || test_data.IsICData() || | |
| test_data.IsMegamorphicCache()); | |
| if (test_data.IsICData()) { | |
| ASSERT(ICData::Cast(test_data).NumArgsTested() > 0); | |
| } | |
| #endif // DEBUG | |
| } | |
| ObjectPtr data() const { | |
| return object_pool_.ObjectAt<std::memory_order_acquire>(argument_index()); | |
| } | |
| void set_data(const Object& data) const { | |
| ASSERT(data.IsArray() || data.IsICData() || data.IsMegamorphicCache()); | |
| object_pool_.SetObjectAt<std::memory_order_release>(argument_index(), data); | |
| } | |
| private: | |
| DISALLOW_IMPLICIT_CONSTRUCTORS(InstanceCall); | |
| }; | |
| class UnoptimizedStaticCall : public UnoptimizedCall { | |
| public: | |
| UnoptimizedStaticCall(uword return_address, const Code& caller_code) | |
| : UnoptimizedCall(return_address, caller_code) { | |
| #if defined(DEBUG) | |
| ICData& test_ic_data = ICData::Handle(); | |
| test_ic_data ^= ic_data(); | |
| ASSERT(test_ic_data.NumArgsTested() >= 0); | |
| #endif // DEBUG | |
| } | |
| ObjectPtr ic_data() const { return object_pool_.ObjectAt(argument_index()); } | |
| 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& caller_code) | |
| : object_pool_(ObjectPool::Handle(caller_code.GetObjectPool())), | |
| code_index_(-1) { | |
| uword pc = return_address; | |
| MatchCallPattern(&pc); | |
| MatchCodeLoadFromPool(&pc, &code_index_); | |
| ASSERT(Object::Handle( | |
| object_pool_.ObjectAt<std::memory_order_acquire>(code_index_)) | |
| .IsCode()); | |
| } | |
| CodePtr Target() const { | |
| Code& code = Code::Handle(); | |
| code ^= object_pool_.ObjectAt<std::memory_order_acquire>(code_index_); | |
| return code.ptr(); | |
| } | |
| void SetTarget(const Code& target) const { | |
| object_pool_.SetObjectAt<std::memory_order_release>(code_index_, target); | |
| // No need to flush the instruction cache, since the code is not modified. | |
| } | |
| protected: | |
| const ObjectPool& object_pool_; | |
| intptr_t code_index_; | |
| 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 SwitchableCallBase : public ValueObject { | |
| public: | |
| explicit SwitchableCallBase(const ObjectPool& object_pool) | |
| : object_pool_(object_pool), target_index_(-1), data_index_(-1) {} | |
| intptr_t data_index() const { return data_index_; } | |
| intptr_t target_index() const { return target_index_; } | |
| ObjectPtr data() const { | |
| return object_pool_.ObjectAt<std::memory_order_acquire>(data_index()); | |
| } | |
| void SetDataRelease(const Object& data) const { | |
| ASSERT(!Object::Handle( | |
| object_pool_.ObjectAt<std::memory_order_relaxed>(data_index())) | |
| .IsCode()); | |
| object_pool_.SetObjectAt<std::memory_order_release>(data_index(), data); | |
| // No need to flush the instruction cache, since the code is not modified. | |
| } | |
| protected: | |
| const ObjectPool& object_pool_; | |
| intptr_t target_index_; | |
| intptr_t data_index_; | |
| private: | |
| DISALLOW_IMPLICIT_CONSTRUCTORS(SwitchableCallBase); | |
| }; | |
| // See [SwitchableCallBase] for a switchable calls in general. | |
| // | |
| // The target slot is always a [Code] object: Either the code of the | |
| // monomorphic function or a stub code. | |
| class SwitchableCall : public SwitchableCallBase { | |
| public: | |
| SwitchableCall(uword return_address, const Code& caller_code) | |
| : SwitchableCallBase(ObjectPool::Handle(caller_code.GetObjectPool())) { | |
| ASSERT(caller_code.ContainsInstructionAt(return_address)); | |
| uword pc = return_address; | |
| // callq [CODE_REG + entrypoint_offset] | |
| static int16_t call_pattern[] = { | |
| 0x41, 0xff, 0x54, 0x24, -1, // | |
| }; | |
| if (MatchesPattern(pc, call_pattern, ARRAY_SIZE(call_pattern))) { | |
| pc -= ARRAY_SIZE(call_pattern); | |
| } else { | |
| FATAL("Failed to decode at %" Px, pc); | |
| } | |
| // movq RBX, [PP + offset] | |
| MatchDataLoadFromPool(&pc, &data_index_); | |
| // movq CODE_REG, [PP + offset] | |
| static int16_t load_code_disp8[] = { | |
| 0x4d, | |
| 0x8b, | |
| 0x67, | |
| -1, // | |
| }; | |
| static int16_t load_code_disp32[] = { | |
| 0x4d, 0x8b, 0xa7, -1, -1, -1, -1, | |
| }; | |
| if (MatchesPattern(pc, load_code_disp8, ARRAY_SIZE(load_code_disp8))) { | |
| pc -= ARRAY_SIZE(load_code_disp8); | |
| target_index_ = IndexFromPPLoadDisp8(pc + 3); | |
| } else if (MatchesPattern(pc, load_code_disp32, | |
| ARRAY_SIZE(load_code_disp32))) { | |
| pc -= ARRAY_SIZE(load_code_disp32); | |
| target_index_ = IndexFromPPLoadDisp32(pc + 3); | |
| } else { | |
| FATAL("Failed to decode at %" Px, pc); | |
| } | |
| ASSERT(Object::Handle( | |
| object_pool_.ObjectAt<std::memory_order_relaxed>(target_index_)) | |
| .IsCode()); | |
| } | |
| void SetTargetRelease(const Code& target) const { | |
| ASSERT(Object::Handle( | |
| object_pool_.ObjectAt<std::memory_order_relaxed>(target_index())) | |
| .IsCode()); | |
| object_pool_.SetObjectAt<std::memory_order_release>(target_index(), target); | |
| // No need to flush the instruction cache, since the code is not modified. | |
| } | |
| ObjectPtr target() const { | |
| return object_pool_.ObjectAt<std::memory_order_acquire>(target_index()); | |
| } | |
| }; | |
| // See [SwitchableCallBase] for a switchable calls in general. | |
| // | |
| // The target slot is always a direct entrypoint address: Either the entry point | |
| // of the monomorphic function or a stub entry point. | |
| class BareSwitchableCall : public SwitchableCallBase { | |
| public: | |
| explicit BareSwitchableCall(uword return_address) | |
| : SwitchableCallBase(ObjectPool::Handle( | |
| IsolateGroup::Current()->object_store()->global_object_pool())) { | |
| uword pc = return_address; | |
| // callq RCX | |
| static int16_t call_pattern[] = { | |
| 0xff, | |
| 0xd1, // | |
| }; | |
| if (MatchesPattern(pc, call_pattern, ARRAY_SIZE(call_pattern))) { | |
| pc -= ARRAY_SIZE(call_pattern); | |
| } else { | |
| FATAL("Failed to decode at %" Px, pc); | |
| } | |
| // movq RBX, [PP + offset] | |
| static int16_t load_data_disp8[] = { | |
| 0x49, | |
| 0x8b, | |
| 0x5f, | |
| -1, // | |
| }; | |
| static int16_t load_data_disp32[] = { | |
| 0x49, 0x8b, 0x9f, -1, -1, -1, -1, | |
| }; | |
| if (MatchesPattern(pc, load_data_disp8, ARRAY_SIZE(load_data_disp8))) { | |
| pc -= ARRAY_SIZE(load_data_disp8); | |
| data_index_ = IndexFromPPLoadDisp8(pc + 3); | |
| } else if (MatchesPattern(pc, load_data_disp32, | |
| ARRAY_SIZE(load_data_disp32))) { | |
| pc -= ARRAY_SIZE(load_data_disp32); | |
| data_index_ = IndexFromPPLoadDisp32(pc + 3); | |
| } else { | |
| FATAL("Failed to decode at %" Px, pc); | |
| } | |
| ASSERT(!Object::Handle( | |
| object_pool_.ObjectAt<std::memory_order_relaxed>(data_index_)) | |
| .IsCode()); | |
| // movq RCX, [PP + offset] | |
| static int16_t load_code_disp8[] = { | |
| 0x49, | |
| 0x8b, | |
| 0x4f, | |
| -1, // | |
| }; | |
| static int16_t load_code_disp32[] = { | |
| 0x49, 0x8b, 0x8f, -1, -1, -1, -1, | |
| }; | |
| if (MatchesPattern(pc, load_code_disp8, ARRAY_SIZE(load_code_disp8))) { | |
| pc -= ARRAY_SIZE(load_code_disp8); | |
| target_index_ = IndexFromPPLoadDisp8(pc + 3); | |
| } else if (MatchesPattern(pc, load_code_disp32, | |
| ARRAY_SIZE(load_code_disp32))) { | |
| pc -= ARRAY_SIZE(load_code_disp32); | |
| target_index_ = IndexFromPPLoadDisp32(pc + 3); | |
| } else { | |
| FATAL("Failed to decode at %" Px, pc); | |
| } | |
| ASSERT(object_pool_.TypeAt(target_index_) == | |
| ObjectPool::EntryType::kImmediate); | |
| } | |
| void SetTargetRelease(const Code& target) const { | |
| ASSERT(object_pool_.TypeAt(target_index()) == | |
| ObjectPool::EntryType::kImmediate); | |
| object_pool_.SetRawValueAt<std::memory_order_release>( | |
| target_index(), target.MonomorphicEntryPoint()); | |
| } | |
| uword target_entry() const { | |
| return object_pool_.RawValueAt<std::memory_order_relaxed>(target_index()); | |
| } | |
| }; | |
| CodePtr 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) { | |
| PoolPointerCall call(return_address, code); | |
| call.SetTarget(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); | |
| } | |
| CodePtr CodePatcher::GetInstanceCallAt(uword return_address, | |
| const Code& caller_code, | |
| Object* data) { | |
| ASSERT(caller_code.ContainsInstructionAt(return_address)); | |
| InstanceCall call(return_address, caller_code); | |
| if (data != nullptr) { | |
| *data = call.data(); | |
| } | |
| return call.target(); | |
| } | |
| void CodePatcher::PatchInstanceCallAt(uword return_address, | |
| const Code& caller_code, | |
| const Object& data, | |
| const Code& target) { | |
| auto thread = Thread::Current(); | |
| thread->isolate_group()->RunWithStoppedMutators([&]() { | |
| PatchInstanceCallAtWithMutatorsStopped(thread, return_address, caller_code, | |
| data, target); | |
| }); | |
| } | |
| void CodePatcher::PatchInstanceCallAtWithMutatorsStopped( | |
| Thread* thread, | |
| uword return_address, | |
| const Code& caller_code, | |
| const Object& data, | |
| const Code& target) { | |
| ASSERT(caller_code.ContainsInstructionAt(return_address)); | |
| InstanceCall call(return_address, caller_code); | |
| call.set_data(data); | |
| call.set_target(target); | |
| } | |
| FunctionPtr CodePatcher::GetUnoptimizedStaticCallAt(uword return_address, | |
| const Code& caller_code, | |
| ICData* ic_data_result) { | |
| ASSERT(caller_code.ContainsInstructionAt(return_address)); | |
| UnoptimizedStaticCall static_call(return_address, caller_code); | |
| ICData& ic_data = ICData::Handle(); | |
| ic_data ^= static_call.ic_data(); | |
| if (ic_data_result != nullptr) { | |
| *ic_data_result = ic_data.ptr(); | |
| } | |
| return ic_data.GetTargetAt(0); | |
| } | |
| void CodePatcher::PatchSwitchableCallAt(uword return_address, | |
| const Code& caller_code, | |
| const Object& data, | |
| const Code& target) { | |
| // We lock to block other writers but don't start a safepoint to block readers | |
| // (i.e., Dart execution). | |
| // First update target to a stub that does not read 'data' so that concurrent | |
| // Dart execution cannot observe the new stub with the old data or the old | |
| // stub with the new data. | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| if (FLAG_precompiled_mode) { | |
| BareSwitchableCall call(return_address); | |
| call.SetTargetRelease(StubCode::SwitchableCallMiss()); | |
| call.SetDataRelease(data); | |
| call.SetTargetRelease(target); | |
| } else { | |
| SwitchableCall call(return_address, caller_code); | |
| call.SetTargetRelease(StubCode::SwitchableCallMiss()); | |
| call.SetDataRelease(data); | |
| call.SetTargetRelease(target); | |
| } | |
| } | |
| uword CodePatcher::GetSwitchableCallTargetEntryAt(uword return_address, | |
| const Code& caller_code) { | |
| if (FLAG_precompiled_mode) { | |
| BareSwitchableCall call(return_address); | |
| return call.target_entry(); | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| } | |
| ObjectPtr CodePatcher::GetSwitchableCallDataAt(uword return_address, | |
| const Code& caller_code) { | |
| if (FLAG_precompiled_mode) { | |
| BareSwitchableCall call(return_address); | |
| return call.data(); | |
| } else { | |
| SwitchableCall call(return_address, caller_code); | |
| return call.data(); | |
| } | |
| } | |
| void CodePatcher::PatchNativeCallAt(uword return_address, | |
| const Code& caller_code, | |
| NativeFunction target, | |
| const Code& trampoline) { | |
| Thread::Current()->isolate_group()->RunWithStoppedMutators([&]() { | |
| ASSERT(caller_code.ContainsInstructionAt(return_address)); | |
| NativeCall call(return_address, caller_code); | |
| call.set_target(trampoline); | |
| call.set_native_function(target); | |
| }); | |
| } | |
| CodePtr CodePatcher::GetNativeCallAt(uword return_address, | |
| const Code& caller_code, | |
| NativeFunction* target) { | |
| ASSERT(caller_code.ContainsInstructionAt(return_address)); | |
| NativeCall call(return_address, caller_code); | |
| *target = call.native_function(); | |
| return call.target(); | |
| } | |
| } // namespace dart | |
| #endif // defined TARGET_ARCH_X64 |