// 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 #include #include #include #include #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 contextsWithLock; #endif //////////////////////////////////////////////////////////////////////////////// // Broadcaster - template base class for ServerEventBroadcaster and // ClientEventBroadcaster //////////////////////////////////////////////////////////////////////////////// template class Broadcaster : public Listener { public: void add(Listener *); void remove(Listener *); protected: template inline void foreach(F &&); template inline void modify(F &&); using ListenerSet = std::unordered_set; std::recursive_mutex mutex; std::shared_ptr listeners = std::make_shared(); int listenersInUse = 0; }; template void Broadcaster::add(Listener *l) { modify([&]() { listeners->emplace(l); }); } template void Broadcaster::remove(Listener *l) { modify([&]() { listeners->erase(l); }); } template template void Broadcaster::foreach(F &&f) { std::unique_lock lock(mutex); ++listenersInUse; auto copy = listeners; for(auto l : *copy) { f(l); } --listenersInUse; } template template void Broadcaster::modify(F &&f) { std::unique_lock lock(mutex); if(listenersInUse > 0) { // The listeners map is current being iterated over. // Make a copy before making the edit. listeners = std::make_shared(*listeners); } f(); } //////////////////////////////////////////////////////////////////////////////// // ServerEventBroadcaster //////////////////////////////////////////////////////////////////////////////// class ServerEventBroadcaster : public Broadcaster { 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 { 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); ServerEventBroadcaster serverEventBroadcaster; ClientEventBroadcaster clientEventBroadcaster; std::mutex mutex; std::unordered_map> threadsByStdId; std::unordered_set functionBreakpoints; std::unordered_map> pendingBreakpoints; WeakMap threads; WeakMap files; WeakMap frames; WeakMap scopes; WeakMap 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) { 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::create() { return std::shared_ptr(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 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(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 Context::Lock::get(Thread::ID id) { return ctx->threads.get(id); } std::vector> Context::Lock::threads() { std::vector> out; out.reserve(ctx->threads.approx_size()); for(auto it : ctx->threads) { out.push_back(it.second); } return out; } std::shared_ptr 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 Context::Lock::createPhysicalFile(const std::string &path) { auto file = File::createPhysical(ctx->nextFileID++, path); ctx->addFile(file); return file; } std::shared_ptr Context::Lock::get(File::ID id) { return ctx->files.get(id); } std::shared_ptr 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> Context::Lock::files() { std::vector> out; out.reserve(ctx->files.approx_size()); for(auto it : ctx->files) { out.push_back(it.second); } return out; } std::shared_ptr Context::Lock::createFrame( const std::shared_ptr &file, std::string function) { auto frame = std::make_shared(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 Context::Lock::get(Frame::ID id) { return ctx->frames.get(id); } std::shared_ptr Context::Lock::createScope( const std::shared_ptr &file) { auto scope = std::make_shared(ctx->nextScopeID++, file, std::make_shared()); ctx->scopes.add(scope->id, scope); return scope; } std::shared_ptr Context::Lock::get(Scope::ID id) { return ctx->scopes.get(id); } void Context::Lock::track(const std::shared_ptr &vars) { ctx->variables.add(vars->id, vars); } std::shared_ptr 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 &lines) { ctx->pendingBreakpoints.emplace(filename, lines); } bool Context::Lock::isFunctionBreakpoint(const std::string &name) { return ctx->functionBreakpoints.count(name) > 0; } std::unordered_set Context::Lock::getFunctionBreakpoints() { return ctx->functionBreakpoints; } } // namespace dbg } // namespace vk