Vulkan/Debug: Various fixes / improvements to Thread

Remove the `Context::Lock` parameter from `enter()`. We don't want to force the lock to be held for the entire duration of the call.

Add `UpdateFrame` type alias.

Add the `UpdateFrame` parameter to `enter()` - you usually want to update the frame as soon as you've pushed the stack.

Switch `pauseAtFrame` from a `shared_ptr` to a `weak_ptr`.
If the frame is released before the thread finds it to pause, then the thread will instead pause at the next frame (following the logic of a null `pauseAtFrame`).

Fixed the assignment of `pauseAtFrame` in `stepOut()` - it was looking at the current frame, not the next one up.

Bug: b/145351270
Change-Id: I1cb85af58c666a7793145480abc95a4ec82e1859
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48695
Tested-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Pipeline/SpirvShaderDebugger.cpp b/src/Pipeline/SpirvShaderDebugger.cpp
index 432b937..76f1fad 100644
--- a/src/Pipeline/SpirvShaderDebugger.cpp
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -856,10 +856,10 @@
 	static State *create(const Debugger *debugger, const char *name);
 	static void destroy(State *);
 
-	State(const Debugger *debugger, const char *stackBase, vk::dbg::Context::Lock &lock);
+	State(const Debugger *debugger, const char *stackBase);
 	~State();
 
-	void enter(vk::dbg::Context::Lock &lock, const char *name);
+	void enter(const char *name);
 	void exit();
 	void updateActiveLaneMask(int lane, bool enabled);
 	void updateLocation(bool isStep, vk::dbg::File::ID file, int line, int column);
@@ -905,8 +905,7 @@
 
 SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger, const char *name)
 {
-	auto lock = debugger->ctx->lock();
-	return new State(debugger, name, lock);
+	return new State(debugger, name);
 }
 
 void SpirvShader::Impl::Debugger::State::destroy(State *state)
@@ -914,13 +913,13 @@
 	delete state;
 }
 
-SpirvShader::Impl::Debugger::State::State(const Debugger *debugger, const char *stackBase, vk::dbg::Context::Lock &lock)
+SpirvShader::Impl::Debugger::State::State(const Debugger *debugger, const char *stackBase)
     : debugger(debugger)
-    , thread(lock.currentThread())
+    , thread(debugger->ctx->lock().currentThread())
     , shadow(new uint8_t[debugger->shadow.size])
     , initialThreadDepth(thread->depth())
 {
-	enter(lock, stackBase);
+	enter(stackBase);
 
 	thread->update(true, [&](vk::dbg::Frame &frame) {
 		globals.locals = frame.locals;
@@ -942,9 +941,9 @@
 	}
 }
 
-void SpirvShader::Impl::Debugger::State::enter(vk::dbg::Context::Lock &lock, const char *name)
+void SpirvShader::Impl::Debugger::State::enter(const char *name)
 {
-	thread->enter(lock, debugger->spirvFile, name);
+	thread->enter(debugger->spirvFile, name);
 }
 
 void SpirvShader::Impl::Debugger::State::exit()
@@ -1058,8 +1057,7 @@
 
 	if(hasDebuggerScope(srcScope->scope))
 	{
-		auto lock = debugger->ctx->lock();
-		auto thread = lock.currentThread();
+		auto thread = debugger->ctx->lock().currentThread();
 
 		debug::Function *oldFunction = oldSrcScope ? debug::find<debug::Function>(oldSrcScope->scope) : nullptr;
 		debug::Function *newFunction = newSrcScope ? debug::find<debug::Function>(newSrcScope->scope) : nullptr;
@@ -1067,7 +1065,7 @@
 		if(oldFunction != newFunction)
 		{
 			if(oldFunction) { thread->exit(); }
-			if(newFunction) { thread->enter(lock, newFunction->source->dbgFile, newFunction->name); }
+			if(newFunction) { thread->enter(newFunction->source->dbgFile, newFunction->name); }
 		}
 
 		auto dbgScope = getScopes(srcScope->scope);
diff --git a/src/Vulkan/Debug/Thread.cpp b/src/Vulkan/Debug/Thread.cpp
index 9e57bc7..2f4dcc0 100644
--- a/src/Vulkan/Debug/Thread.cpp
+++ b/src/Vulkan/Debug/Thread.cpp
@@ -23,7 +23,7 @@
 
 Thread::Thread(ID id, Context *ctx)
     : id(id)
-    , broadcast(ctx->serverEventBroadcast())
+    , ctx(ctx)
 {}
 
 void Thread::setName(const std::string &name)
@@ -46,7 +46,7 @@
 	{
 		if(location.file->hasBreakpoint(location.line))
 		{
-			broadcast->onLineBreakpointHit(id);
+			ctx->serverEventBroadcast()->onLineBreakpointHit(id);
 			state_ = State::Paused;
 		}
 	}
@@ -61,12 +61,23 @@
 
 		case State::Stepping:
 		{
-			if(!pauseAtFrame || pauseAtFrame == frames.back())
+			bool pause = false;
+
 			{
-				broadcast->onThreadStepped(id);
+				auto frame = pauseAtFrame.lock();
+				pause = !frame;             // Pause if there's no pause-at-frame...
+				if(frame == frames.back())  // ... or if we've reached the pause-at-frame
+				{
+					pause = true;
+					pauseAtFrame.reset();
+				}
+			}
+
+			if(pause)
+			{
+				ctx->serverEventBroadcast()->onThreadStepped(id);
 				state_ = State::Paused;
 				lock.wait(stateCV, [this]() REQUIRES(mutex) { return state_ != State::Paused; });
-				pauseAtFrame = 0;
 			}
 			break;
 		}
@@ -76,17 +87,29 @@
 	}
 }
 
-void Thread::enter(Context::Lock &ctxlck, const std::shared_ptr<File> &file, const std::string &function)
+void Thread::enter(const std::shared_ptr<File> &file, const std::string &function, const UpdateFrame &f)
 {
-	auto frame = ctxlck.createFrame(file, function);
-	auto isFunctionBreakpoint = ctxlck.isFunctionBreakpoint(function);
-
-	marl::lock lock(mutex);
-	frames.push_back(frame);
-	if(isFunctionBreakpoint)
+	std::shared_ptr<Frame> frame;
+	bool isFunctionBreakpoint;
 	{
-		broadcast->onFunctionBreakpointHit(id);
-		state_ = State::Paused;
+		auto lock = ctx->lock();
+		frame = lock.createFrame(file, function);
+		isFunctionBreakpoint = lock.isFunctionBreakpoint(function);
+	}
+
+	{
+		marl::lock lock(mutex);
+		frames.push_back(frame);
+
+		if(f) { f(*frame); }
+
+		if(isFunctionBreakpoint)
+		{
+			ctx->serverEventBroadcast()->onFunctionBreakpointHit(id);
+			state_ = State::Paused;
+		}
+
+		onLocationUpdate(lock);
 	}
 }
 
@@ -96,7 +119,7 @@
 	frames.pop_back();
 }
 
-void Thread::update(bool isStep, std::function<void(Frame &)> f)
+void Thread::update(bool isStep, const UpdateFrame &f)
 {
 	marl::lock lock(mutex);
 	auto &frame = *frames.back();
@@ -180,7 +203,7 @@
 {
 	marl::lock lock(mutex);
 	state_ = State::Stepping;
-	pauseAtFrame = (frames.size() > 1) ? frames[frames.size() - 1] : nullptr;
+	pauseAtFrame = (frames.size() > 1) ? frames[frames.size() - 2] : nullptr;
 	stateCV.notify_all();
 }
 
diff --git a/src/Vulkan/Debug/Thread.hpp b/src/Vulkan/Debug/Thread.hpp
index 184d396..d9ba32d 100644
--- a/src/Vulkan/Debug/Thread.hpp
+++ b/src/Vulkan/Debug/Thread.hpp
@@ -107,6 +107,8 @@
 public:
 	using ID = dbg::ID<Thread>;
 
+	using UpdateFrame = std::function<void(Frame &)>;
+
 	// The current execution state.
 	enum class State
 	{
@@ -124,8 +126,8 @@
 	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);
+	// file and function, then calls f to modify the new frame of the stack.
+	void enter(const std::shared_ptr<File> &file, const std::string &function, const UpdateFrame &f = nullptr);
 
 	// exit() pops the thread's stack frame.
 	void exit();
@@ -150,7 +152,7 @@
 	// from full line updates. Note that we cannot simply examine line position
 	// changes as single-line loops such as `while(true) { foo(); }` would not
 	// be correctly steppable.
-	void update(bool isStep, std::function<void(Frame &)> f);
+	void update(bool isStep, const UpdateFrame &f);
 
 	// resume() resumes execution of the thread by unblocking a call to
 	// update() and setting the thread's state to State::Running.
@@ -181,7 +183,7 @@
 	const ID id;
 
 private:
-	ServerEventListener *const broadcast;
+	Context *const ctx;
 
 	void onLocationUpdate(marl::lock &lock) REQUIRES(mutex);
 
@@ -189,7 +191,7 @@
 	std::string name_ GUARDED_BY(mutex);
 	std::vector<std::shared_ptr<Frame>> frames GUARDED_BY(mutex);
 	State state_ GUARDED_BY(mutex) = State::Running;
-	std::shared_ptr<Frame> pauseAtFrame GUARDED_BY(mutex);
+	std::weak_ptr<Frame> pauseAtFrame GUARDED_BY(mutex);
 
 	std::condition_variable stateCV;
 };
diff --git a/src/Vulkan/VkCommandBuffer.cpp b/src/Vulkan/VkCommandBuffer.cpp
index 87ec112..2fde442 100644
--- a/src/Vulkan/VkCommandBuffer.cpp
+++ b/src/Vulkan/VkCommandBuffer.cpp
@@ -1810,11 +1810,9 @@
 	auto debuggerContext = device->getDebuggerContext();
 	if(debuggerContext)
 	{
-		auto lock = debuggerContext->lock();
-		debuggerThread = lock.currentThread();
+		debuggerThread = debuggerContext->lock().currentThread();
 		debuggerThread->setName("vkQueue processor");
-		debuggerThread->enter(lock, debuggerFile, "vkCommandBuffer::submit");
-		lock.unlock();
+		debuggerThread->enter(debuggerFile, "vkCommandBuffer::submit");
 	}
 	defer(if(debuggerThread) { debuggerThread->exit(); });
 	int line = 1;