Vulkan/Debug: Add Thread and EventListener
EventListener is an interface that is used to listen for thread events.
Thread holds the state for a single thread of execution.
Frame holds a number of variable scopes for one of a thread's stack frame.
Scope is a container for frame variables.
Bug: b/145351270
Change-Id: Ic61e17f32cfd6929dbd7b0fce1ffb716301fc73e
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38897
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Vulkan/Debug/EventListener.hpp b/src/Vulkan/Debug/EventListener.hpp
new file mode 100644
index 0000000..6d96066
--- /dev/null
+++ b/src/Vulkan/Debug/EventListener.hpp
@@ -0,0 +1,50 @@
+// 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_EVENT_LISTENER_HPP_
+#define VK_DEBUG_EVENT_LISTENER_HPP_
+
+namespace vk
+{
+namespace dbg
+{
+
+class Thread;
+
+// EventListener is an interface that is used to listen for thread events.
+class EventListener
+{
+public:
+ virtual ~EventListener() = default;
+
+ // onThreadStarted() is called when a new thread begins execution.
+ virtual void onThreadStarted(ID<Thread>) {}
+
+ // onThreadStepped() is called when a thread performs a single line /
+ // instruction step.
+ virtual void onThreadStepped(ID<Thread>) {}
+
+ // onLineBreakpointHit() is called when a thread hits a line breakpoint and
+ // pauses execution.
+ virtual void onLineBreakpointHit(ID<Thread>) {}
+
+ // onFunctionBreakpointHit() is called when a thread hits a function
+ // breakpoint and pauses execution.
+ virtual void onFunctionBreakpointHit(ID<Thread>) {}
+};
+
+} // namespace dbg
+} // namespace vk
+
+#endif // VK_DEBUG_EVENT_LISTENER_HPP_
diff --git a/src/Vulkan/Debug/Thread.cpp b/src/Vulkan/Debug/Thread.cpp
new file mode 100644
index 0000000..b61c5d9
--- /dev/null
+++ b/src/Vulkan/Debug/Thread.cpp
@@ -0,0 +1,183 @@
+// 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 "Thread.hpp"
+
+#include "Context.hpp"
+#include "EventListener.hpp"
+#include "File.hpp"
+
+namespace vk
+{
+namespace dbg
+{
+
+Thread::Thread(ID id, Context* ctx) :
+ id(id),
+ broadcast(ctx->broadcast()) {}
+
+void Thread::setName(const std::string& name)
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ name_ = name;
+}
+
+std::string Thread::name() const
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ return name_;
+}
+
+void Thread::update(const Location& location)
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ frames.back()->location = location;
+
+ if(state_ == State::Running)
+ {
+ if(location.file->hasBreakpoint(location.line))
+ {
+ broadcast->onLineBreakpointHit(id);
+ state_ = State::Paused;
+ }
+ }
+
+ switch(state_)
+ {
+ case State::Paused:
+ {
+ stateCV.wait(lock, [this] { return state_ != State::Paused; });
+ break;
+ }
+
+ case State::Stepping:
+ {
+ if(!pauseAtFrame || pauseAtFrame == frames.back())
+ {
+ broadcast->onThreadStepped(id);
+ state_ = State::Paused;
+ stateCV.wait(lock, [this] { return state_ != State::Paused; });
+ pauseAtFrame = 0;
+ }
+ break;
+ }
+
+ case State::Running:
+ break;
+ }
+}
+
+void Thread::enter(Context::Lock& ctxlck, const std::shared_ptr<File>& file, const std::string& function)
+{
+ auto frame = ctxlck.createFrame(file);
+ auto isFunctionBreakpoint = ctxlck.isFunctionBreakpoint(function);
+
+ std::unique_lock<std::mutex> lock(mutex);
+ frame->function = function;
+ frames.push_back(frame);
+ if(isFunctionBreakpoint)
+ {
+ broadcast->onFunctionBreakpointHit(id);
+ state_ = State::Paused;
+ }
+}
+
+void Thread::exit()
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ frames.pop_back();
+}
+
+std::shared_ptr<VariableContainer> Thread::registers() const
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ return frames.back()->registers->variables;
+}
+
+std::shared_ptr<VariableContainer> Thread::locals() const
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ return frames.back()->locals->variables;
+}
+
+std::shared_ptr<VariableContainer> Thread::arguments() const
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ return frames.back()->arguments->variables;
+}
+
+std::shared_ptr<VariableContainer> Thread::hovers() const
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ return frames.back()->hovers->variables;
+}
+
+std::vector<Frame> Thread::stack() const
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ std::vector<Frame> out;
+ out.reserve(frames.size());
+ for(auto frame : frames)
+ {
+ out.push_back(*frame);
+ }
+ return out;
+}
+
+Thread::State Thread::state() const
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ return state_;
+}
+
+void Thread::resume()
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ state_ = State::Running;
+ lock.unlock();
+ stateCV.notify_all();
+}
+
+void Thread::pause()
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ state_ = State::Paused;
+}
+
+void Thread::stepIn()
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ state_ = State::Stepping;
+ pauseAtFrame.reset();
+ stateCV.notify_all();
+}
+
+void Thread::stepOver()
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ state_ = State::Stepping;
+ pauseAtFrame = frames.back();
+ stateCV.notify_all();
+}
+
+void Thread::stepOut()
+{
+ std::unique_lock<std::mutex> lock(mutex);
+ state_ = State::Stepping;
+ pauseAtFrame = (frames.size() > 1) ? frames[frames.size() - 1] : nullptr;
+ stateCV.notify_all();
+}
+
+} // namespace dbg
+} // namespace vk
\ No newline at end of file
diff --git a/src/Vulkan/Debug/Thread.hpp b/src/Vulkan/Debug/Thread.hpp
new file mode 100644
index 0000000..8199d67
--- /dev/null
+++ b/src/Vulkan/Debug/Thread.hpp
@@ -0,0 +1,194 @@
+// 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_THREAD_HPP_
+#define VK_DEBUG_THREAD_HPP_
+
+#include "Context.hpp"
+#include "ID.hpp"
+#include "Location.hpp"
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+namespace vk
+{
+namespace dbg
+{
+
+class File;
+class VariableContainer;
+class EventListener;
+
+// Scope is a container for variables and is used to provide source data for the
+// DAP 'Scope' type:
+// https://microsoft.github.io/debug-adapter-protocol/specification#Types_Scope
+class Scope
+{
+public:
+ using ID = dbg::ID<Scope>;
+
+ inline Scope(ID id,
+ const std::shared_ptr<File>& file,
+ const std::shared_ptr<VariableContainer>& variables);
+
+ // The unique identifier of the scope.
+ const ID id;
+
+ // The file this scope is associated with.
+ const std::shared_ptr<File> file;
+
+ // The scope's variables.
+ const std::shared_ptr<VariableContainer> variables;
+};
+
+Scope::Scope(ID id,
+ const std::shared_ptr<File>& file,
+ const std::shared_ptr<VariableContainer>& variables) :
+ id(id),
+ file(file),
+ variables(variables) {}
+
+// Frame holds a number of variable scopes for one of a thread's stack frame,
+// and is used to provide source data for the DAP 'StackFrame' type:
+// https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame
+class Frame
+{
+public:
+ using ID = dbg::ID<Frame>;
+
+ inline Frame(ID id);
+
+ // The unique identifier of the stack frame.
+ const ID id;
+
+ // The name of function for this stack frame.
+ std::string function;
+
+ // The current execution location within the stack frame.
+ Location location;
+
+ // The scope for the frame's arguments.
+ std::shared_ptr<Scope> arguments;
+
+ // The scope for the frame's locals.
+ std::shared_ptr<Scope> locals;
+
+ // The scope for the frame's registers.
+ std::shared_ptr<Scope> registers;
+
+ // The scope for variables that should only appear in hover tooltips.
+ std::shared_ptr<Scope> hovers;
+};
+
+Frame::Frame(ID id) :
+ id(id) {}
+
+// Thread holds the state for a single thread of execution.
+class Thread
+{
+public:
+ using ID = dbg::ID<Thread>;
+
+ // The current execution state.
+ enum class State
+ {
+ Running, // Thread is running.
+ Stepping, // Thread is currently single line stepping.
+ Paused // Thread is currently paused.
+ };
+
+ Thread(ID id, Context* ctx);
+
+ // setName() sets the name of the thread.
+ void setName(const std::string&);
+
+ // name() returns the name of the thread.
+ std::string name() const;
+
+ // enter() pushes the thread's stack with a new frame created with the given
+ // file and function.
+ void enter(Context::Lock& lock, const std::shared_ptr<File>& file, const std::string& function);
+
+ // exit() pops the thread's stack frame.
+ void exit();
+
+ // registers() returns the thread's current stack frame's register variables.
+ std::shared_ptr<VariableContainer> registers() const;
+
+ // locals() returns the thread's current stack frame's local variables.
+ std::shared_ptr<VariableContainer> locals() const;
+
+ // arguments() returns the thread's current stack frame's argument variables.
+ std::shared_ptr<VariableContainer> arguments() const;
+
+ // hovers() returns the thread's current stack frame's hover variables.
+ std::shared_ptr<VariableContainer> hovers() const;
+
+ // stack() returns a copy of the thread's current stack frames.
+ std::vector<Frame> stack() const;
+
+ // state() returns the current thread's state.
+ State state() const;
+
+ // resume() resumes execution of the thread by unblocking a call to
+ // update() and setting the thread's state to State::Running.
+ void resume();
+
+ // pause() suspends execution of the thread by blocking the next call to
+ // update() and setting the thread's state to State::Paused.
+ void pause();
+
+ // stepIn() temporarily resumes execution of the thread by unblocking a
+ // call to update(), and setting the thread's state to State::Stepping.
+ // The next call to update() will suspend execution again.
+ void stepIn();
+
+ // stepOver() temporarily resumes execution of the thread by unblocking a
+ // call to update(), and setting the thread's state to State::Stepping.
+ // The next call to update() within the same stack frame will suspend
+ // execution again.
+ void stepOver();
+
+ // stepOut() temporarily resumes execution of the thread by unblocking a
+ // call to update(), and setting the thread's state to State::Stepping.
+ // The next call to update() at the stack frame above the current frame will
+ // suspend execution again.
+ void stepOut();
+
+ // update() updates the current stack frame's location, and potentially
+ // blocks until the thread is resumed with one of the methods above.
+ void update(const Location& location);
+
+ // The unique identifier of the thread.
+ const ID id;
+
+private:
+ EventListener* const broadcast;
+
+ mutable std::mutex mutex;
+ std::string name_;
+ std::vector<std::shared_ptr<Frame>> frames;
+ std::condition_variable stateCV;
+ State state_ = State::Running;
+ std::shared_ptr<Frame> pauseAtFrame;
+};
+
+} // namespace dbg
+} // namespace vk
+
+#endif // VK_DEBUG_THREAD_HPP_