diff --git a/src/xenia/debug/profiler.cc b/src/xenia/debug/profiler.cc new file mode 100644 index 000000000..f3cc2f0c0 --- /dev/null +++ b/src/xenia/debug/profiler.cc @@ -0,0 +1,150 @@ +/** +****************************************************************************** +* Xenia : Xbox 360 Emulator Research Project * +****************************************************************************** +* Copyright 2015 Ben Vanik. All rights reserved. * +* Released under the BSD license - see LICENSE in the root for more details. * +****************************************************************************** +*/ + +#include "xenia/debug/profiler.h" + +#include "xenia/base/mutex.h" +#include "xenia/cpu/backend/code_cache.h" +#include "xenia/cpu/ppc/ppc_opcode_info.h" +#include "xenia/cpu/stack_walker.h" +#include "xenia/debug/debugger.h" +#include "xenia/kernel/xthread.h" +#include "xenia/emulator.h" + +// Credits go to Very Sleepy for the general design +// https://github.com/VerySleepy/verysleepy + +namespace xe { +namespace debug { + +Profiler::Profiler(Debugger* debugger) : debugger_(debugger) {} + +void Profiler::Start() { + assert_false(is_profiling_); + + total_samples_collected_.exchange(0); + is_profiling_ = true; + timer_.Start(); + + auto params = threading::Thread::CreationParameters(); + worker_thread_ = xe::threading::Thread::Create( + params, std::bind(&Profiler::ProfilerEntry, this)); +} + +void Profiler::Stop() { + assert_true(is_profiling_); + + is_profiling_ = false; + timer_.Stop(); + profiling_stop_fence_.Wait(); + worker_thread_.reset(); + + CalculateSamplesPerInstruction(); +} + +void Profiler::OnThreadsChanged() { + auto lock = global_critical_region::AcquireDirect(); + should_refresh_threads_ = true; +} + +void Profiler::CalculateSamplesPerInstruction() { + auto cache = debugger_->emulator()->processor()->backend()->code_cache(); + auto memory = debugger_->emulator()->memory(); + + for (auto& sample : samples_) { + // Translate x86 address to ppc address + auto host_addr = sample.first.second.host_frames[0]; + auto func = cache->LookupFunction(host_addr); + if (!func) { + continue; + } + + uint32_t ppc_addr = func->MapMachineCodeToGuestAddress(host_addr); + auto code = xe::load_and_swap(memory->TranslateVirtual(ppc_addr)); + auto& disasm = cpu::ppc::GetOpcodeDisasmInfo(cpu::ppc::LookupOpcode(code)); + samples_per_instruction_[disasm.name] += sample.second; + } +} + +void Profiler::CalculateSamplesPerFunction() { + auto cache = debugger_->emulator()->processor()->backend()->code_cache(); + auto memory = debugger_->emulator()->memory(); + + for (auto& sample : samples_) { + // Translate x86 address to ppc address + auto host_addr = sample.first.second.host_frames[0]; + auto func = cache->LookupFunction(host_addr); + if (!func) { + continue; + } + + samples_per_function_[func] += sample.second; + } +} + +void Profiler::ProfilerEntry() { + threading::Thread::GetCurrentThread()->set_name("Profiler Thread"); + std::vector thread_list; + + // Fetch a list of threads from the debugger. + should_refresh_threads_ = true; + + // Called when we should profile. + while (is_profiling_) { + if (should_refresh_threads_) { + auto lock = global_critical_region::AcquireDirect(); + auto exec_infos = debugger_->QueryThreadExecutionInfos(); + for (auto info : exec_infos) { + if (info->thread && !info->thread->is_host_object()) { + thread_list.push_back(info->thread->thread()); + } + } + + should_refresh_threads_ = false; + } + + // Reshuffle the thread list so we don't starve threads. Probably don't need + // to do this every run. + std::random_shuffle(thread_list.begin(), thread_list.end()); + for (size_t i = 0; i < thread_list.size(); i++) { + // FIXME: This is totally a race condition. We can't take the global lock + // though, because that would screw with profiling. + if (should_refresh_threads_) { + break; + } + + SampleThread(thread_list[i]); + } + } + + profiling_stop_fence_.Signal(); +} + +void Profiler::SampleThread(threading::Thread* thread) { + auto stack_walker = debugger_->emulator()->processor()->stack_walker(); + thread->Suspend(); + + CallStack stack; + stack.depth = stack_walker->CaptureStackTrace( + thread->native_handle(), stack.host_frames, 0, + xe::countof(stack.host_frames), nullptr, nullptr); + assert_true(stack.depth > 0); + + thread->Resume(); + + // Discard the sample if a stack trace could not be captured. + if (stack.depth > 0) { + samples_[{thread->system_id(), stack}]++; + total_samples_[thread->system_id()]++; + total_samples_collected_.fetch_add(1); + } +} + +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/profiler.h b/src/xenia/debug/profiler.h new file mode 100644 index 000000000..df826bc62 --- /dev/null +++ b/src/xenia/debug/profiler.h @@ -0,0 +1,112 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_PROFILER_H_ +#define XENIA_DEBUG_PROFILER_H_ + +#include +#include + +#include "xenia/base/assert.h" +#include "xenia/base/clock.h" +#include "xenia/base/threading.h" + +namespace xe { +namespace kernel { +class XThread; +} + +namespace cpu { +class Function; +} + +namespace debug { +class Debugger; + +class Profiler { + public: + Profiler(Debugger* debugger); + + // Time elapsed between profiling start and stop (or now) + uint64_t GetTicksElapsed() { return timer_.GetTicksElapsed(); } + double GetSecondsElapsed() { return timer_.GetSecondsElapsed(); } + + void Start(); + void Stop(); + + // Internal - do not use. + void OnThreadsChanged(); + + struct CallStack { + uint64_t host_frames[128]; + size_t depth; + + bool operator<(const CallStack& other) const { + if (depth != other.depth) { + return depth < other.depth; + } + + for (size_t i = 0; i < depth; i++) { + if (host_frames[i] != other.host_frames[i]) { + return host_frames[i] < other.host_frames[i]; + } + } + + return false; + } + }; + + bool is_profiling() const { return is_profiling_; } + std::map, uint64_t>& GetSamplesPerThread() { + assert_false(is_profiling_); + return samples_; + } + std::map& GetTotalSamplesPerThread() { + assert_false(is_profiling_); + return total_samples_; + } + std::map& GetSamplesPerInstruction() { + assert_false(is_profiling_); + return samples_per_instruction_; + } + uint64_t total_samples_collected() { return total_samples_collected_.load(); } + + private: + void CalculateSamplesPerInstruction(); + void CalculateSamplesPerFunction(); + + void ProfilerEntry(); + + void SampleThread(threading::Thread* thread); + + Debugger* debugger_ = nullptr; + + // TODO: Callstack map + // Map of {thread_id, stack} => # hits + std::map, uint64_t> samples_; + std::map total_samples_; // thread_id => total samples + std::atomic total_samples_collected_; + + std::map samples_per_instruction_; + std::map samples_per_function_; + + bool is_profiling_ = false; + bool worker_should_exit_ = false; + bool should_refresh_threads_ = false; + threading::Fence worker_fence_; + threading::Fence profiling_stop_fence_; + std::unique_ptr worker_thread_; + + Timer timer_; +}; + +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_PROFILER_H_ \ No newline at end of file diff --git a/src/xenia/debug/protocol.h b/src/xenia/debug/protocol.h new file mode 100644 index 000000000..aa20c8f4c --- /dev/null +++ b/src/xenia/debug/protocol.h @@ -0,0 +1,29 @@ +/** +****************************************************************************** +* Xenia : Xbox 360 Emulator Research Project * +****************************************************************************** +* Copyright 2015 Ben Vanik. All rights reserved. * +* Released under the BSD license - see LICENSE in the root for more details. * +****************************************************************************** +*/ + +#ifndef XENIA_DEBUG_PROTOCOL_H_ +#define XENIA_DEBUG_PROTOCOL_H_ + +namespace xe { +namespace debug { +namespace protocol { + +template +class Packet { + public: + + protected: + int type_; +}; + +} // namespace protocol +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_PROTOCOL_H_ diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index 131601eac..46d11acad 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -1251,6 +1251,28 @@ SHIM_CALL KeRemoveQueueDpc_shim(PPCContext* ppc_context, SHIM_SET_RETURN_32(result ? 1 : 0); } +// https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/blob/51e4dfcaacfdbd1a9692272931a436371492f72d/import/OpenXDK/include/xboxkrnl/xboxkrnl.h#L1372 +struct X_ERWLOCK { + be lock_count; // 0x0 + be writers_waiting_count; // 0x4 + be readers_waiting_count; // 0x8 + be readers_entry_count; // 0xC + X_KEVENT writer_event; // 0x10 + X_KSEMAPHORE reader_semaphore; // 0x20 + be spin_lock; // 0x34 +}; + +void ExInitializeReadWriteLock(pointer_t lock_ptr) { + lock_ptr->lock_count = -1; + lock_ptr->writers_waiting_count = 0; + lock_ptr->readers_waiting_count = 0; + lock_ptr->readers_entry_count = 0; + KeInitializeEvent(&lock_ptr->writer_event, 1, 0); + KeInitializeSemaphore(&lock_ptr->reader_semaphore, 0, 0x7FFFFFFF); +} +DECLARE_XBOXKRNL_EXPORT(ExInitializeReadWriteLock, + ExportTag::kThreading | ExportTag::kImplemented); + // NOTE: This function is very commonly inlined, and probably won't be called! pointer_result_t InterlockedPushEntrySList( pointer_t plist_ptr, pointer_t entry) {