Commit a5859c15 by Ben Clayton

Vulkan/Debug: Add Context

Holds the global debugger state. Bug: b/145351270 Change-Id: I4e483c145af068755716f23c09adcc68d138ceaa Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38898Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com> Tested-by: 's avatarBen Clayton <bclayton@google.com> Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
parent 71af5936
// 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 <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <unordered_set>
namespace
{
class Broadcaster : public vk::dbg::EventListener
{
public:
using Thread = vk::dbg::Thread;
// EventListener
void onThreadStarted(Thread::ID) override;
void onThreadStepped(Thread::ID) override;
void onLineBreakpointHit(Thread::ID) override;
void onFunctionBreakpointHit(Thread::ID) override;
void add(EventListener*);
void remove(EventListener*);
private:
template <typename F>
inline void foreach(F&&);
template <typename F>
inline void modify(F&&);
using ListenerSet = std::unordered_set<EventListener*>;
std::recursive_mutex mutex;
std::shared_ptr<ListenerSet> listeners = std::make_shared<ListenerSet>();
int listenersInUse = 0;
};
void Broadcaster::onThreadStarted(Thread::ID id)
{
foreach([&](EventListener* l) { l->onThreadStarted(id); });
}
void Broadcaster::onThreadStepped(Thread::ID id)
{
foreach([&](EventListener* l) { l->onThreadStepped(id); });
}
void Broadcaster::onLineBreakpointHit(Thread::ID id)
{
foreach([&](EventListener* l) { l->onLineBreakpointHit(id); });
}
void Broadcaster::onFunctionBreakpointHit(Thread::ID id)
{
foreach([&](EventListener* l) { l->onFunctionBreakpointHit(id); });
}
void Broadcaster::add(EventListener* l)
{
modify([&]() { listeners->emplace(l); });
}
void Broadcaster::remove(EventListener* l)
{
modify([&]() { listeners->erase(l); });
}
template <typename F>
void Broadcaster::foreach(F&& f)
{
std::unique_lock<std::recursive_mutex> lock(mutex);
++listenersInUse;
auto copy = listeners;
for(auto l : *copy) { f(l); }
--listenersInUse;
}
template <typename F>
void Broadcaster::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();
}
} // namespace
namespace vk
{
namespace dbg
{
////////////////////////////////////////////////////////////////////////////////
// Context::Impl
////////////////////////////////////////////////////////////////////////////////
class Context::Impl : public Context
{
public:
// Context compliance
Lock lock() override;
void addListener(EventListener*) override;
void removeListener(EventListener*) override;
EventListener* broadcast() override;
void addFile(const std::shared_ptr<File>& file);
Broadcaster broadcaster;
std::mutex mutex;
std::vector<EventListener*> eventListeners;
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<VariableContainer::ID, VariableContainer> variableContainers;
Thread::ID nextThreadID = 1;
File::ID nextFileID = 1;
Frame::ID nextFrameID = 1;
Scope::ID nextScopeID = 1;
VariableContainer::ID nextVariableContainerID = 1;
};
Context::Lock Context::Impl::lock()
{
return Lock(this);
}
void Context::Impl::addListener(EventListener* l)
{
broadcaster.add(l);
}
void Context::Impl::removeListener(EventListener* l)
{
broadcaster.remove(l);
}
EventListener* Context::Impl::broadcast()
{
return &broadcaster;
}
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)
{
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)
{
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->broadcast()->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::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)
{
auto frame = std::make_shared<Frame>(ctx->nextFrameID++);
ctx->frames.add(frame->id, frame);
frame->arguments = createScope(file);
frame->locals = createScope(file);
frame->registers = createScope(file);
frame->hovers = createScope(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, createVariableContainer());
ctx->scopes.add(scope->id, scope);
return scope;
}
std::shared_ptr<Scope> Context::Lock::get(Scope::ID id)
{
return ctx->scopes.get(id);
}
std::shared_ptr<VariableContainer> Context::Lock::createVariableContainer()
{
auto container = std::make_shared<VariableContainer>(ctx->nextVariableContainerID++);
ctx->variableContainers.add(container->id, container);
return container;
}
std::shared_ptr<VariableContainer> Context::Lock::get(VariableContainer::ID id)
{
return ctx->variableContainers.get(id);
}
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;
}
} // namespace dbg
} // namespace vk
\ No newline at end of file
// 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.
#ifndef VK_DEBUG_CONTEXT_HPP_
#define VK_DEBUG_CONTEXT_HPP_
#include "ID.hpp"
#include <memory>
#include <string>
#include <vector>
namespace vk
{
namespace dbg
{
// Forward declarations.
class Thread;
class File;
class Frame;
class Scope;
class VariableContainer;
class EventListener;
// Context holds the full state of the debugger, including all current files,
// threads, frames and variables. It also holds a list of EventListeners that
// can be broadcast to using the Context::broadcast() interface.
// Context requires locking before accessing any state. The lock is
// non-reentrant and careful use is required to prevent accidentical
// double-locking by the same thread.
class Context
{
class Impl;
public:
// Lock is the interface to the Context's state.
// The lock is automatically released when the Lock is destructed.
class Lock
{
public:
Lock(Impl*);
Lock(Lock&&);
~Lock();
// move-assignment operator.
Lock& operator=(Lock&&);
// unlock() explicitly unlocks before the Lock destructor is called.
// It is illegal to call any other methods after calling unlock().
void unlock();
// currentThread() creates (or returns an existing) a Thread that
// represents the currently executing thread.
std::shared_ptr<Thread> currentThread();
// get() returns the thread with the given ID, or null if the thread
// does not exist or no longer has any external shared_ptr references.
std::shared_ptr<Thread> get(ID<Thread>);
// threads() returns the full list of threads that still have an
// external shared_ptr reference.
std::vector<std::shared_ptr<Thread>> threads();
// createVirtualFile() returns a new file that is not backed by the
// filesystem.
// name is the unique name of the file.
// source is the content of the file.
std::shared_ptr<File> createVirtualFile(const std::string& name,
const std::string& source);
// createPhysicalFile() returns a new file that is backed by the file
// at path.
std::shared_ptr<File> createPhysicalFile(const std::string& path);
// get() returns the file with the given ID, or null if the file
// does not exist or no longer has any external shared_ptr references.
std::shared_ptr<File> get(ID<File>);
// files() returns the full list of files.
std::vector<std::shared_ptr<File>> files();
// createFrame() returns a new frame for the given file.
std::shared_ptr<Frame> createFrame(
const std::shared_ptr<File>& file);
// get() returns the frame with the given ID, or null if the frame
// does not exist or no longer has any external shared_ptr references.
std::shared_ptr<Frame> get(ID<Frame>);
// createScope() returns a new scope for the given file.
std::shared_ptr<Scope> createScope(
const std::shared_ptr<File>& file);
// get() returns the scope with the given ID, or null if the scope
// does not exist.
std::shared_ptr<Scope> get(ID<Scope>);
// createVariableContainer() returns a new variable container.
std::shared_ptr<VariableContainer> createVariableContainer();
// get() returns the variable container with the given ID, or null if
// the variable container does not exist or no longer has any external
// shared_ptr references.
std::shared_ptr<VariableContainer> get(ID<VariableContainer>);
// addFunctionBreakpoint() adds a breakpoint to the start of the
// function with the given name.
void addFunctionBreakpoint(const std::string& name);
// addPendingBreakpoints() adds a number of breakpoints to the file with
// the given name which has not yet been created with a call to
// createVirtualFile() or createPhysicalFile().
void addPendingBreakpoints(const std::string& name, const std::vector<int>& lines);
// isFunctionBreakpoint() returns true if the function with the given
// name has a function breakpoint set.
bool isFunctionBreakpoint(const std::string& name);
private:
Lock(const Lock&) = delete;
Lock& operator=(const Lock&) = delete;
Impl* ctx;
};
// create() creates and returns a new Context.
static std::shared_ptr<Context> create();
virtual ~Context() = default;
// lock() returns a Lock which exclusively locks the context for state
// access.
virtual Lock lock() = 0;
// addListener() registers an EventListener for event notifications.
virtual void addListener(EventListener*) = 0;
// removeListener() unregisters an EventListener that was previously
// registered by a call to addListener().
virtual void removeListener(EventListener*) = 0;
// broadcast() returns an EventListener that will broadcast all methods on
// to all registered EventListeners.
virtual EventListener* broadcast() = 0;
};
} // namespace dbg
} // namespace vk
#endif // VK_DEBUG_CONTEXT_HPP_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment