diff --git a/src/debugger/TimerMap.cxx b/src/debugger/TimerMap.cxx new file mode 100644 index 000000000..d392e227f --- /dev/null +++ b/src/debugger/TimerMap.cxx @@ -0,0 +1,148 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2022 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "TimerMap.hxx" + +/* + TODOs: + x unordered_multimap (not required for just a few timers) + o 13 vs 16 bit, use ADDRESS_MASK & ANY_BANK, when??? + ? timer line display in disassembly? (color, symbol,...?) +*/ + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 TimerMap::add(const uInt16 fromAddr, const uInt16 toAddr, + const uInt8 fromBank, const uInt8 toBank) +{ + const TimerPoint tpFrom(fromAddr, fromBank); + const TimerPoint tpTo(toAddr, toBank); + const Timer complete(tpFrom, tpTo); + + myList.push_back(complete); + myFromMap.insert(TimerPair(tpFrom, &myList.back())); + myToMap.insert(TimerPair(tpTo, &myList.back())); + + return size() - 1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 TimerMap::add(const uInt16 addr, const uInt8 bank) +{ + const uInt32 idx = size() - 1; + const bool isPartialTimer = size() && get(idx).isPartial; + const TimerPoint tp(addr, bank); + + if(!isPartialTimer) + { + const Timer partial(tp); + + myList.push_back(partial); + myFromMap.insert(TimerPair(tp, &myList.back())); + } + else + { + Timer& partial = myList[idx]; + + partial.setTo(tp); + myToMap.insert(TimerPair(tp, &partial)); + } + return size() - 1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool TimerMap::erase(const uInt32 idx) +{ + if(size() > idx) + { + const Timer* timer = &myList[idx]; + const TimerPoint tpFrom(timer->from); + const TimerPoint tpTo(timer->to); + + // Find address in from and to maps, TODO what happens if not found??? + const auto from = myFromMap.equal_range(tpFrom); + for(auto it = from.first; it != from.second; ++it) + if(it->second == timer) + { + myFromMap.erase(it); + break; + } + + const auto to = myToMap.equal_range(tpTo); + for(auto it = to.first; it != to.second; ++it) + if(it->second == timer) + { + myToMap.erase(it); + break; + } + + // Finally remove from list + myList.erase(myList.begin() + idx); + return true; + } + return false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimerMap::clear() +{ + myList.clear(); + myFromMap.clear(); + myToMap.clear(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimerMap::reset() +{ + for(auto it = myList.begin(); it != myList.end(); ++it) + it->reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimerMap::update(const uInt16 addr, const uInt8 bank, + const uInt64 cycles) +{ + // 13 bit timerpoint + if((addr & ADDRESS_MASK) != addr) + { + TimerPoint tp(addr, bank); // -> addr & ADDRESS_MASK + + // Find address in from and to maps + const auto from = myFromMap.equal_range(tp); + for(auto it = from.first; it != from.second; ++it) + if(!it->second->isPartial) + it->second->start(cycles); + + const auto to = myToMap.equal_range(tp); + for(auto it = to.first; it != to.second; ++it) + if(!it->second->isPartial) + it->second->stop(cycles); + } + + // 16 bit timerpoint + TimerPoint tp(addr, bank, false); // -> addr + + // Find address in from and to maps + const auto from = myFromMap.equal_range(tp); + for(auto it = from.first; it != from.second; ++it) + if(!it->second->isPartial) + it->second->start(cycles); + + const auto to = myToMap.equal_range(tp); + for(auto it = to.first; it != to.second; ++it) + if(!it->second->isPartial) + it->second->stop(cycles); +} diff --git a/src/debugger/TimerMap.hxx b/src/debugger/TimerMap.hxx new file mode 100644 index 000000000..8fa3427a2 --- /dev/null +++ b/src/debugger/TimerMap.hxx @@ -0,0 +1,249 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2022 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIMER_MAP_HXX +#define TIMER_MAP_HXX + +#include +#include + +#include "bspf.hxx" + +/** + This class handles debugger timers. Each timer needs a 'from' and a 'to' + address. + + @author Thomas Jentzsch +*/ +class TimerMap +{ + public: + static constexpr uInt8 ANY_BANK = 255; // timer breakpoint valid in any bank + + //private: + static constexpr uInt16 ADDRESS_MASK = 0x1fff; // either 0x1fff or 0xffff (not needed then) + + private: + struct TimerPoint + { + uInt16 addr{0}; + uInt8 bank{ANY_BANK}; + + explicit constexpr TimerPoint(uInt16 c_addr, uInt8 c_bank, bool mask = true) + : addr{c_addr}, bank{c_bank} + { + if(mask && bank != ANY_BANK) + addr = addr & ADDRESS_MASK; + } + TimerPoint() + : addr{0}, bank(ANY_BANK) {} + +#if 0 // unused + bool operator==(const TimerPoint& other) const + { + if(addr == other.addr) + { + if(bank == ANY_BANK || other.bank == ANY_BANK) + return true; + else + return bank == other.bank; + } + return false; + } +#endif + + bool operator<(const TimerPoint& other) const + { + if(bank == ANY_BANK || other.bank == ANY_BANK) + return addr < other.addr; + + return bank < other.bank || (bank == other.bank && addr < other.addr); + } + }; + + public: + struct Timer + { + TimerPoint from{}; + TimerPoint to{}; + bool isPartial{false}; + + uInt64 execs{0}; + uInt64 lastCycles{0}; + uInt64 totalCycles{0}; + uInt64 minCycles{ULONG_MAX}; + uInt64 maxCycles{0}; + bool isStarted{false}; + + explicit constexpr Timer(const TimerPoint& c_from, const TimerPoint& c_to) + : from{c_from}, to{c_to} + { + //if(to.bank == ANY_BANK) // TODO: check if this is required + //{ + // to.bank = from.bank; + // if(to.bank != ANY_BANK) + // to.addr &= ADDRESS_MASK; + //} + } + + Timer(uInt16 fromAddr, uInt16 toAddr, uInt8 fromBank, uInt8 toBank) + { + Timer(TimerPoint(fromAddr, fromBank), TimerPoint(fromAddr, fromBank)); + } + + Timer(const TimerPoint& tp) + { + if(!isPartial) + { + from = tp; + isPartial = true; + } + else + { + to = tp; + isPartial = false; + + //if(to.bank == ANY_BANK) // TODO: check if this is required + //{ + // to.bank = from.bank; + // if(to.bank != ANY_BANK) + // to.addr &= ADDRESS_MASK; + //} + } + } + + Timer(uInt16 addr, uInt8 bank) + { + Timer(TimerPoint(addr, bank)); + } + +#if 0 // unused + bool operator==(const Timer& other) const + { + cerr << from.addr << ", " << to.addr << endl; + if(from.addr == other.from.addr && to.addr == other.to.addr) + { + if((from.bank == ANY_BANK || other.from.bank == ANY_BANK) && + (to.bank == ANY_BANK || other.to.bank == ANY_BANK)) + return true; + else + return from.bank == other.from.bank && to.bank == other.to.bank; + } + return false; + } + + bool operator<(const Timer& other) const + { + if(from.bank < other.from.bank || (from.bank == other.from.bank && from.addr < other.from.addr)) + return true; + + if(from.bank == other.from.bank && from.addr == other.from.addr) + return to.bank < other.to.bank || (to.bank == other.to.bank && to.addr < other.to.addr); + + return false; + } +#endif + + void setTo(const TimerPoint& tp) + { + to = tp; + isPartial = false; + + //if(to.bank == ANY_BANK) // TODO: check if this is required + //{ + // to.bank = from.bank; + // if(to.bank != ANY_BANK) + // to.addr &= ADDRESS_MASK; + //} + } + + void reset() + { + execs = lastCycles = totalCycles = maxCycles = 0; + minCycles = ULONG_MAX; + } + + // Start the timer + void start(uInt64 cycles) + { + lastCycles = cycles; + isStarted = true; + } + + // Stop the timer and update stats + void stop(uInt64 cycles) + { + if(isStarted) + { + const uInt64 diffCycles = cycles - lastCycles; + + ++execs; + totalCycles += diffCycles; + minCycles = std::min(minCycles, diffCycles); + maxCycles = std::max(maxCycles, diffCycles); + isStarted = false; + } + } + + uInt32 averageCycles() const { + return execs ? std::round(totalCycles / execs) : 0; } + }; // Timer + + explicit TimerMap() = default; + + bool isInitialized() const { return myList.size(); } + + /** Add new timer */ + uInt32 add(const uInt16 fromAddr, const uInt16 toAddr, + const uInt8 fromBank, const uInt8 toBank); + uInt32 add(const uInt16 addr, const uInt8 bank); + + /** Erase timer */ + bool erase(const uInt32 idx); + + /** Clear all timers */ + void clear(); + + /** Reset all timers */ + void reset(); + + /** Get timer */ + const Timer& get(const uInt32 idx) const { return myList[idx]; }; + uInt32 size() const { return static_cast(myList.size()); } + + /** Update timer */ + void update(const uInt16 addr, const uInt8 bank, + const uInt64 cycles); + + private: + using TimerList = std::deque; // makes sure that the element pointers do NOT change + using TimerPair = std::pair; + using FromMap = std::multimap; + using ToMap = std::multimap; + + TimerList myList; + FromMap myFromMap; + ToMap myToMap; + + // Following constructors and assignment operators not supported + TimerMap(const TimerMap&) = delete; + TimerMap(TimerMap&&) = delete; + TimerMap& operator=(const TimerMap&) = delete; + TimerMap& operator=(TimerMap&&) = delete; +}; + +#endif