| // Copyright 2019 The SwiftShader Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "Context.hpp" | |
| #include "EventListener.hpp" | |
| #include "File.hpp" | |
| #include "Thread.hpp" | |
| #include "Variable.hpp" | |
| #include "WeakMap.hpp" | |
| #include "System/Debug.hpp" | |
| #include <memory> | |
| #include <mutex> | |
| #include <thread> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) | |
| # define CHECK_REENTRANT_CONTEXT_LOCKS 1 | |
| #else | |
| # define CHECK_REENTRANT_CONTEXT_LOCKS 0 | |
| #endif | |
| namespace { | |
| #if CHECK_REENTRANT_CONTEXT_LOCKS | |
| thread_local std::unordered_set<const void *> contextsWithLock; | |
| #endif | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Broadcaster - template base class for ServerEventBroadcaster and | |
| // ClientEventBroadcaster | |
| //////////////////////////////////////////////////////////////////////////////// | |
| template<typename Listener> | |
| class Broadcaster : public Listener | |
| { | |
| public: | |
| void add(Listener *); | |
| void remove(Listener *); | |
| protected: | |
| template<typename F> | |
| inline void foreach(F &&); | |
| template<typename F> | |
| inline void modify(F &&); | |
| using ListenerSet = std::unordered_set<Listener *>; | |
| std::recursive_mutex mutex; | |
| std::shared_ptr<ListenerSet> listeners = std::make_shared<ListenerSet>(); | |
| int listenersInUse = 0; | |
| }; | |
| template<typename Listener> | |
| void Broadcaster<Listener>::add(Listener *l) | |
| { | |
| modify([&]() { listeners->emplace(l); }); | |
| } | |
| template<typename Listener> | |
| void Broadcaster<Listener>::remove(Listener *l) | |
| { | |
| modify([&]() { listeners->erase(l); }); | |
| } | |
| template<typename Listener> | |
| template<typename F> | |
| void Broadcaster<Listener>::foreach(F &&f) | |
| { | |
| std::unique_lock<std::recursive_mutex> lock(mutex); | |
| ++listenersInUse; | |
| auto copy = listeners; | |
| for(auto l : *copy) { f(l); } | |
| --listenersInUse; | |
| } | |
| template<typename Listener> | |
| template<typename F> | |
| void Broadcaster<Listener>::modify(F &&f) | |
| { | |
| std::unique_lock<std::recursive_mutex> lock(mutex); | |
| if(listenersInUse > 0) | |
| { | |
| // The listeners map is current being iterated over. | |
| // Make a copy before making the edit. | |
| listeners = std::make_shared<ListenerSet>(*listeners); | |
| } | |
| f(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // ServerEventBroadcaster | |
| //////////////////////////////////////////////////////////////////////////////// | |
| class ServerEventBroadcaster : public Broadcaster<vk::dbg::ServerEventListener> | |
| { | |
| public: | |
| using Thread = vk::dbg::Thread; | |
| void onThreadStarted(Thread::ID id) override | |
| { | |
| foreach([&](auto *l) { l->onThreadStarted(id); }); | |
| } | |
| void onThreadStepped(Thread::ID id) override | |
| { | |
| foreach([&](auto *l) { l->onThreadStepped(id); }); | |
| } | |
| void onLineBreakpointHit(Thread::ID id) override | |
| { | |
| foreach([&](auto *l) { l->onLineBreakpointHit(id); }); | |
| } | |
| void onFunctionBreakpointHit(Thread::ID id) override | |
| { | |
| foreach([&](auto *l) { l->onFunctionBreakpointHit(id); }); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // ClientEventBroadcaster | |
| //////////////////////////////////////////////////////////////////////////////// | |
| class ClientEventBroadcaster : public Broadcaster<vk::dbg::ClientEventListener> | |
| { | |
| public: | |
| void onSetBreakpoint(const vk::dbg::Location &location, bool &handled) override | |
| { | |
| foreach([&](auto *l) { l->onSetBreakpoint(location, handled); }); | |
| } | |
| void onSetBreakpoint(const std::string &func, bool &handled) override | |
| { | |
| foreach([&](auto *l) { l->onSetBreakpoint(func, handled); }); | |
| } | |
| void onBreakpointsChanged() override | |
| { | |
| foreach([&](auto *l) { l->onBreakpointsChanged(); }); | |
| } | |
| }; | |
| } // namespace | |
| namespace vk { | |
| namespace dbg { | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Context::Impl | |
| //////////////////////////////////////////////////////////////////////////////// | |
| class Context::Impl : public Context | |
| { | |
| public: | |
| // Context compliance | |
| Lock lock() override; | |
| void addListener(ClientEventListener *) override; | |
| void removeListener(ClientEventListener *) override; | |
| ClientEventListener *clientEventBroadcast() override; | |
| void addListener(ServerEventListener *) override; | |
| void removeListener(ServerEventListener *) override; | |
| ServerEventListener *serverEventBroadcast() override; | |
| void addFile(const std::shared_ptr<File> &file); | |
| ServerEventBroadcaster serverEventBroadcaster; | |
| ClientEventBroadcaster clientEventBroadcaster; | |
| std::mutex mutex; | |
| std::unordered_map<std::thread::id, std::shared_ptr<Thread>> threadsByStdId; | |
| std::unordered_set<std::string> functionBreakpoints; | |
| std::unordered_map<std::string, std::vector<int>> pendingBreakpoints; | |
| WeakMap<Thread::ID, Thread> threads; | |
| WeakMap<File::ID, File> files; | |
| WeakMap<Frame::ID, Frame> frames; | |
| WeakMap<Scope::ID, Scope> scopes; | |
| WeakMap<Variables::ID, Variables> variables; | |
| Thread::ID nextThreadID = 1; | |
| File::ID nextFileID = 1; | |
| Frame::ID nextFrameID = 1; | |
| Scope::ID nextScopeID = 1; | |
| }; | |
| Context::Lock Context::Impl::lock() | |
| { | |
| return Lock(this); | |
| } | |
| void Context::Impl::addListener(ClientEventListener *l) | |
| { | |
| clientEventBroadcaster.add(l); | |
| } | |
| void Context::Impl::removeListener(ClientEventListener *l) | |
| { | |
| clientEventBroadcaster.remove(l); | |
| } | |
| ClientEventListener *Context::Impl::clientEventBroadcast() | |
| { | |
| return &clientEventBroadcaster; | |
| } | |
| void Context::Impl::addListener(ServerEventListener *l) | |
| { | |
| serverEventBroadcaster.add(l); | |
| } | |
| void Context::Impl::removeListener(ServerEventListener *l) | |
| { | |
| serverEventBroadcaster.remove(l); | |
| } | |
| ServerEventListener *Context::Impl::serverEventBroadcast() | |
| { | |
| return &serverEventBroadcaster; | |
| } | |
| void Context::Impl::addFile(const std::shared_ptr<File> &file) | |
| { | |
| files.add(file->id, file); | |
| auto it = pendingBreakpoints.find(file->name); | |
| if(it != pendingBreakpoints.end()) | |
| { | |
| for(auto line : it->second) | |
| { | |
| file->addBreakpoint(line); | |
| } | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Context | |
| //////////////////////////////////////////////////////////////////////////////// | |
| std::shared_ptr<Context> Context::create() | |
| { | |
| return std::shared_ptr<Context>(new Context::Impl()); | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Context::Lock | |
| //////////////////////////////////////////////////////////////////////////////// | |
| Context::Lock::Lock(Impl *ctx) | |
| : ctx(ctx) | |
| { | |
| #if CHECK_REENTRANT_CONTEXT_LOCKS | |
| ASSERT_MSG(contextsWithLock.count(ctx) == 0, "Attempting to acquire Context lock twice on same thread. This will deadlock"); | |
| contextsWithLock.emplace(ctx); | |
| #endif | |
| ctx->mutex.lock(); | |
| } | |
| Context::Lock::Lock(Lock &&o) | |
| : ctx(o.ctx) | |
| { | |
| o.ctx = nullptr; | |
| } | |
| Context::Lock::~Lock() | |
| { | |
| unlock(); | |
| } | |
| Context::Lock &Context::Lock::operator=(Lock &&o) | |
| { | |
| ctx = o.ctx; | |
| o.ctx = nullptr; | |
| return *this; | |
| } | |
| void Context::Lock::unlock() | |
| { | |
| if(ctx) | |
| { | |
| #if CHECK_REENTRANT_CONTEXT_LOCKS | |
| contextsWithLock.erase(ctx); | |
| #endif | |
| ctx->mutex.unlock(); | |
| ctx = nullptr; | |
| } | |
| } | |
| std::shared_ptr<Thread> Context::Lock::currentThread() | |
| { | |
| auto threadIt = ctx->threadsByStdId.find(std::this_thread::get_id()); | |
| if(threadIt != ctx->threadsByStdId.end()) | |
| { | |
| return threadIt->second; | |
| } | |
| auto id = ++ctx->nextThreadID; | |
| char name[256]; | |
| snprintf(name, sizeof(name), "Thread<0x%x>", id.value()); | |
| auto thread = std::make_shared<Thread>(id, ctx); | |
| ctx->threads.add(id, thread); | |
| thread->setName(name); | |
| ctx->threadsByStdId.emplace(std::this_thread::get_id(), thread); | |
| ctx->serverEventBroadcast()->onThreadStarted(id); | |
| return thread; | |
| } | |
| std::shared_ptr<Thread> Context::Lock::get(Thread::ID id) | |
| { | |
| return ctx->threads.get(id); | |
| } | |
| std::vector<std::shared_ptr<Thread>> Context::Lock::threads() | |
| { | |
| std::vector<std::shared_ptr<Thread>> out; | |
| out.reserve(ctx->threads.approx_size()); | |
| for(auto it : ctx->threads) | |
| { | |
| out.push_back(it.second); | |
| } | |
| return out; | |
| } | |
| std::shared_ptr<File> Context::Lock::createVirtualFile(const std::string &name, | |
| const std::string &source) | |
| { | |
| auto file = File::createVirtual(ctx->nextFileID++, name, source); | |
| ctx->addFile(file); | |
| return file; | |
| } | |
| std::shared_ptr<File> Context::Lock::createPhysicalFile(const std::string &path) | |
| { | |
| auto file = File::createPhysical(ctx->nextFileID++, path); | |
| ctx->addFile(file); | |
| return file; | |
| } | |
| std::shared_ptr<File> Context::Lock::get(File::ID id) | |
| { | |
| return ctx->files.get(id); | |
| } | |
| std::shared_ptr<File> Context::Lock::findFile(const std::string &path) | |
| { | |
| for(auto it : ctx->files) | |
| { | |
| auto &file = it.second; | |
| if(file->path() == path) | |
| { | |
| return file; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| std::vector<std::shared_ptr<File>> Context::Lock::files() | |
| { | |
| std::vector<std::shared_ptr<File>> out; | |
| out.reserve(ctx->files.approx_size()); | |
| for(auto it : ctx->files) | |
| { | |
| out.push_back(it.second); | |
| } | |
| return out; | |
| } | |
| std::shared_ptr<Frame> Context::Lock::createFrame( | |
| const std::shared_ptr<File> &file, std::string function) | |
| { | |
| auto frame = std::make_shared<Frame>(ctx->nextFrameID++, std::move(function)); | |
| ctx->frames.add(frame->id, frame); | |
| frame->arguments = createScope(file); | |
| frame->locals = createScope(file); | |
| frame->registers = createScope(file); | |
| frame->hovers = createScope(file); | |
| frame->location.file = file; | |
| return frame; | |
| } | |
| std::shared_ptr<Frame> Context::Lock::get(Frame::ID id) | |
| { | |
| return ctx->frames.get(id); | |
| } | |
| std::shared_ptr<Scope> Context::Lock::createScope( | |
| const std::shared_ptr<File> &file) | |
| { | |
| auto scope = std::make_shared<Scope>(ctx->nextScopeID++, file, std::make_shared<VariableContainer>()); | |
| ctx->scopes.add(scope->id, scope); | |
| return scope; | |
| } | |
| std::shared_ptr<Scope> Context::Lock::get(Scope::ID id) | |
| { | |
| return ctx->scopes.get(id); | |
| } | |
| void Context::Lock::track(const std::shared_ptr<Variables> &vars) | |
| { | |
| ctx->variables.add(vars->id, vars); | |
| } | |
| std::shared_ptr<Variables> Context::Lock::get(Variables::ID id) | |
| { | |
| return ctx->variables.get(id); | |
| } | |
| void Context::Lock::clearFunctionBreakpoints() | |
| { | |
| ctx->functionBreakpoints.clear(); | |
| } | |
| void Context::Lock::addFunctionBreakpoint(const std::string &name) | |
| { | |
| ctx->functionBreakpoints.emplace(name); | |
| } | |
| void Context::Lock::addPendingBreakpoints(const std::string &filename, const std::vector<int> &lines) | |
| { | |
| ctx->pendingBreakpoints.emplace(filename, lines); | |
| } | |
| bool Context::Lock::isFunctionBreakpoint(const std::string &name) | |
| { | |
| return ctx->functionBreakpoints.count(name) > 0; | |
| } | |
| std::unordered_set<std::string> Context::Lock::getFunctionBreakpoints() | |
| { | |
| return ctx->functionBreakpoints; | |
| } | |
| } // namespace dbg | |
| } // namespace vk |