Vulkan/Debug: Add Server

Server implements a Debug Adapter Protocol server that listens on a specific port, that operates on the vk::dbg::Context passed to the constructor.

Bug: b/145351270
Change-Id: If09d92c6d298cfbd71a7b2366b6b8407a728ec43
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38899
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/Server.cpp b/src/Vulkan/Debug/Server.cpp
new file mode 100644
index 0000000..05d7714
--- /dev/null
+++ b/src/Vulkan/Debug/Server.cpp
@@ -0,0 +1,596 @@
+// 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 "Server.hpp"
+
+#include "Context.hpp"
+#include "EventListener.hpp"
+#include "File.hpp"
+#include "Thread.hpp"
+#include "Variable.hpp"
+
+#include "dap/network.h"
+#include "dap/protocol.h"
+#include "dap/session.h"
+#include "marl/waitgroup.h"
+
+#include <thread>
+#include <unordered_set>
+
+// Switch for controlling DAP debug logging
+#define ENABLE_DAP_LOGGING 0
+
+#if ENABLE_DAP_LOGGING
+#	define DAP_LOG(msg, ...) printf(msg "\n", __VA_ARGS__)
+#else
+#	define DAP_LOG(...) \
+		do               \
+		{                \
+		} while(false)
+#endif
+
+namespace vk
+{
+namespace dbg
+{
+
+class Server::Impl : public Server, public EventListener
+{
+public:
+	Impl(const std::shared_ptr<Context>& ctx, int port);
+	~Impl();
+
+	// EventListener
+	void onThreadStarted(ID<Thread>) override;
+	void onThreadStepped(ID<Thread>) override;
+	void onLineBreakpointHit(ID<Thread>) override;
+	void onFunctionBreakpointHit(ID<Thread>) override;
+
+	dap::Scope scope(const char* type, Scope*);
+	dap::Source source(File*);
+	std::shared_ptr<File> file(const dap::Source& source);
+
+	const std::shared_ptr<Context> ctx;
+	const std::unique_ptr<dap::net::Server> server;
+	const std::unique_ptr<dap::Session> session;
+	std::atomic<bool> clientIsVisualStudio = { false };
+};
+
+Server::Impl::Impl(const std::shared_ptr<Context>& context, int port) :
+    ctx(context),
+    server(dap::net::Server::create()),
+    session(dap::Session::create())
+{
+	session->registerHandler([](const dap::DisconnectRequest& req) {
+		DAP_LOG("DisconnectRequest receieved");
+		return dap::DisconnectResponse();
+	});
+
+	session->registerHandler([&](const dap::InitializeRequest& req) {
+		DAP_LOG("InitializeRequest receieved");
+		dap::InitializeResponse response;
+		response.supportsFunctionBreakpoints = true;
+		response.supportsConfigurationDoneRequest = true;
+		response.supportsEvaluateForHovers = true;
+		clientIsVisualStudio = (req.clientID.value("") == "visualstudio");
+		return response;
+	});
+
+	session->registerSentHandler(
+	    [&](const dap::ResponseOrError<dap::InitializeResponse>& response) {
+		    DAP_LOG("InitializeResponse sent");
+		    session->send(dap::InitializedEvent());
+	    });
+
+	session->registerHandler([](const dap::SetExceptionBreakpointsRequest& req) {
+		DAP_LOG("SetExceptionBreakpointsRequest receieved");
+		dap::SetExceptionBreakpointsResponse response;
+		return response;
+	});
+
+	session->registerHandler(
+	    [this](const dap::SetFunctionBreakpointsRequest& req) {
+		    DAP_LOG("SetFunctionBreakpointsRequest receieved");
+		    auto lock = ctx->lock();
+		    dap::SetFunctionBreakpointsResponse response;
+		    for(auto const& bp : req.breakpoints)
+		    {
+			    lock.addFunctionBreakpoint(bp.name.c_str());
+			    response.breakpoints.push_back({});
+		    }
+		    return response;
+	    });
+
+	session->registerHandler(
+	    [this](const dap::SetBreakpointsRequest& req)
+	        -> dap::ResponseOrError<dap::SetBreakpointsResponse> {
+		    DAP_LOG("SetBreakpointsRequest receieved");
+		    bool verified = false;
+
+		    size_t numBreakpoints = 0;
+		    if(req.breakpoints.has_value())
+		    {
+			    auto const& breakpoints = req.breakpoints.value();
+			    numBreakpoints = breakpoints.size();
+			    if(auto file = this->file(req.source))
+			    {
+				    file->clearBreakpoints();
+				    for(auto const& bp : breakpoints)
+				    {
+					    file->addBreakpoint(bp.line);
+				    }
+				    verified = true;
+			    }
+			    else if(req.source.name.has_value())
+			    {
+				    std::vector<int> lines;
+				    lines.reserve(breakpoints.size());
+				    for(auto const& bp : breakpoints)
+				    {
+					    lines.push_back(bp.line);
+				    }
+				    ctx->lock().addPendingBreakpoints(req.source.name.value(),
+				                                      lines);
+			    }
+		    }
+
+		    dap::SetBreakpointsResponse response;
+		    for(size_t i = 0; i < numBreakpoints; i++)
+		    {
+			    dap::Breakpoint bp;
+			    bp.verified = verified;
+			    bp.source = req.source;
+			    response.breakpoints.push_back(bp);
+		    }
+		    return response;
+	    });
+
+	session->registerHandler([this](const dap::ThreadsRequest& req) {
+		DAP_LOG("ThreadsRequest receieved");
+		auto lock = ctx->lock();
+		dap::ThreadsResponse response;
+		for(auto thread : lock.threads())
+		{
+			std::string name = thread->name();
+			if(clientIsVisualStudio)
+			{
+				// WORKAROUND: https://github.com/microsoft/VSDebugAdapterHost/issues/15
+				for(size_t i = 0; i < name.size(); i++)
+				{
+					if(name[i] == '.')
+					{
+						name[i] = '_';
+					}
+				}
+			}
+
+			dap::Thread out;
+			out.id = thread->id.value();
+			out.name = name;
+			response.threads.push_back(out);
+		};
+		return response;
+	});
+
+	session->registerHandler(
+	    [this](const dap::StackTraceRequest& req)
+	        -> dap::ResponseOrError<dap::StackTraceResponse> {
+		    DAP_LOG("StackTraceRequest receieved");
+
+		    auto lock = ctx->lock();
+		    auto thread = lock.get(Thread::ID(req.threadId));
+		    if(!thread)
+		    {
+			    return dap::Error("Thread %d not found", req.threadId);
+		    }
+
+		    auto stack = thread->stack();
+
+		    dap::StackTraceResponse response;
+		    response.totalFrames = stack.size();
+		    response.stackFrames.reserve(stack.size());
+		    for(auto const& frame : stack)
+		    {
+			    auto const& loc = frame.location;
+			    dap::StackFrame sf;
+			    sf.column = 0;
+			    sf.id = frame.id.value();
+			    sf.name = frame.function;
+			    sf.line = loc.line;
+			    if(loc.file)
+			    {
+				    sf.source = source(loc.file.get());
+			    }
+			    response.stackFrames.emplace_back(std::move(sf));
+		    }
+		    return response;
+	    });
+
+	session->registerHandler([this](const dap::ScopesRequest& req)
+	                             -> dap::ResponseOrError<dap::ScopesResponse> {
+		DAP_LOG("ScopesRequest receieved");
+
+		auto lock = ctx->lock();
+		auto frame = lock.get(Frame::ID(req.frameId));
+		if(!frame)
+		{
+			return dap::Error("Frame %d not found", req.frameId);
+		}
+
+		dap::ScopesResponse response;
+		response.scopes = {
+			scope("locals", frame->locals.get()),
+			scope("arguments", frame->arguments.get()),
+			scope("registers", frame->registers.get()),
+		};
+		return response;
+	});
+
+	session->registerHandler([this](const dap::VariablesRequest& req)
+	                             -> dap::ResponseOrError<dap::VariablesResponse> {
+		DAP_LOG("VariablesRequest receieved");
+
+		auto lock = ctx->lock();
+		auto vars = lock.get(VariableContainer::ID(req.variablesReference));
+		if(!vars)
+		{
+			return dap::Error("VariablesReference %d not found",
+			                  int(req.variablesReference));
+		}
+
+		dap::VariablesResponse response;
+		vars->foreach(req.start.value(0), [&](const Variable& v) {
+			if(!req.count.has_value() ||
+			   req.count.value() < int(response.variables.size()))
+			{
+				dap::Variable out;
+				out.evaluateName = v.name;
+				out.name = v.name;
+				out.type = v.value->type()->string();
+				out.value = v.value->string();
+				if(v.value->type()->kind == Kind::VariableContainer)
+				{
+					auto const vc = static_cast<const VariableContainer*>(v.value.get());
+					out.variablesReference = vc->id.value();
+				}
+				response.variables.push_back(out);
+			}
+		});
+		return response;
+	});
+
+	session->registerHandler([this](const dap::SourceRequest& req)
+	                             -> dap::ResponseOrError<dap::SourceResponse> {
+		DAP_LOG("SourceRequest receieved");
+
+		dap::SourceResponse response;
+		uint64_t id = req.sourceReference;
+
+		auto lock = ctx->lock();
+		auto file = lock.get(File::ID(id));
+		if(!file)
+		{
+			return dap::Error("Source %d not found", id);
+		}
+		response.content = file->source;
+		return response;
+	});
+
+	session->registerHandler([this](const dap::PauseRequest& req)
+	                             -> dap::ResponseOrError<dap::PauseResponse> {
+		DAP_LOG("PauseRequest receieved");
+
+		dap::StoppedEvent event;
+		event.reason = "pause";
+
+		auto lock = ctx->lock();
+		if(auto thread = lock.get(Thread::ID(req.threadId)))
+		{
+			thread->pause();
+			event.threadId = req.threadId;
+		}
+		else
+		{
+			auto threads = lock.threads();
+			for(auto thread : threads)
+			{
+				thread->pause();
+			}
+			event.allThreadsStopped = true;
+
+			// Workaround for
+			// https://github.com/microsoft/VSDebugAdapterHost/issues/11
+			if(clientIsVisualStudio)
+			{
+				for(auto thread : threads)
+				{
+					event.threadId = thread->id.value();
+					break;
+				}
+			}
+		}
+
+		session->send(event);
+
+		dap::PauseResponse response;
+		return response;
+	});
+
+	session->registerHandler([this](const dap::ContinueRequest& req)
+	                             -> dap::ResponseOrError<dap::ContinueResponse> {
+		DAP_LOG("ContinueRequest receieved");
+
+		dap::ContinueResponse response;
+
+		auto lock = ctx->lock();
+		if(auto thread = lock.get(Thread::ID(req.threadId)))
+		{
+			thread->resume();
+			response.allThreadsContinued = false;
+		}
+		else
+		{
+			for(auto it : lock.threads())
+			{
+				thread->resume();
+			}
+			response.allThreadsContinued = true;
+		}
+
+		return response;
+	});
+
+	session->registerHandler([this](const dap::NextRequest& req)
+	                             -> dap::ResponseOrError<dap::NextResponse> {
+		DAP_LOG("NextRequest receieved");
+
+		auto lock = ctx->lock();
+		auto thread = lock.get(Thread::ID(req.threadId));
+		if(!thread)
+		{
+			return dap::Error("Unknown thread %d", int(req.threadId));
+		}
+
+		thread->stepOver();
+		return dap::NextResponse();
+	});
+
+	session->registerHandler([this](const dap::StepInRequest& req)
+	                             -> dap::ResponseOrError<dap::StepInResponse> {
+		DAP_LOG("StepInRequest receieved");
+
+		auto lock = ctx->lock();
+		auto thread = lock.get(Thread::ID(req.threadId));
+		if(!thread)
+		{
+			return dap::Error("Unknown thread %d", int(req.threadId));
+		}
+
+		thread->stepIn();
+		return dap::StepInResponse();
+	});
+
+	session->registerHandler([this](const dap::StepOutRequest& req)
+	                             -> dap::ResponseOrError<dap::StepOutResponse> {
+		DAP_LOG("StepOutRequest receieved");
+
+		auto lock = ctx->lock();
+		auto thread = lock.get(Thread::ID(req.threadId));
+		if(!thread)
+		{
+			return dap::Error("Unknown thread %d", int(req.threadId));
+		}
+
+		thread->stepOut();
+		return dap::StepOutResponse();
+	});
+
+	session->registerHandler([this](const dap::EvaluateRequest& req)
+	                             -> dap::ResponseOrError<dap::EvaluateResponse> {
+		DAP_LOG("EvaluateRequest receieved");
+
+		auto lock = ctx->lock();
+		if(req.frameId.has_value())
+		{
+			auto frame = lock.get(Frame::ID(req.frameId.value(0)));
+			if(!frame)
+			{
+				return dap::Error("Unknown frame %d", int(req.frameId.value()));
+			}
+
+			auto fmt = FormatFlags::Default;
+			auto subfmt = FormatFlags::Default;
+
+			if(req.context.value("") == "hover")
+			{
+				subfmt.listPrefix = "\n";
+				subfmt.listSuffix = "";
+				subfmt.listDelimiter = "\n";
+				subfmt.listIndent = "  ";
+				fmt.listPrefix = "";
+				fmt.listSuffix = "";
+				fmt.listDelimiter = "\n";
+				fmt.listIndent = "";
+				fmt.subListFmt = &subfmt;
+			}
+
+			dap::EvaluateResponse response;
+			auto findHandler = [&](const Variable& var) {
+				response.result = var.value->string(fmt);
+				response.type = var.value->type()->string();
+			};
+
+			std::vector<std::shared_ptr<vk::dbg::VariableContainer>> variables = {
+				frame->locals->variables,
+				frame->arguments->variables,
+				frame->registers->variables,
+				frame->hovers->variables,
+			};
+
+			for(auto const& vars : variables)
+			{
+				if(vars->find(req.expression, findHandler)) { return response; }
+			}
+
+			// HACK: VSCode does not appear to include the % in %123 tokens
+			// TODO: This might be a configuration problem of the SPIRV-Tools
+			// spirv-ls plugin. Investigate.
+			auto withPercent = "%" + req.expression;
+			for(auto const& vars : variables)
+			{
+				if(vars->find(withPercent, findHandler)) { return response; }
+			}
+		}
+
+		return dap::Error("Could not evaluate expression");
+	});
+
+	session->registerHandler([](const dap::LaunchRequest& req) {
+		DAP_LOG("LaunchRequest receieved");
+		return dap::LaunchResponse();
+	});
+
+	marl::WaitGroup configurationDone(1);
+	session->registerHandler([=](const dap::ConfigurationDoneRequest& req) {
+		DAP_LOG("ConfigurationDoneRequest receieved");
+		configurationDone.done();
+		return dap::ConfigurationDoneResponse();
+	});
+
+	DAP_LOG("Waiting for debugger connection...");
+	server->start(port, [&](const std::shared_ptr<dap::ReaderWriter>& rw) {
+		session->bind(rw);
+		ctx->addListener(this);
+	});
+	configurationDone.wait();
+}
+
+Server::Impl::~Impl()
+{
+	ctx->removeListener(this);
+	server->stop();
+}
+
+void Server::Impl::onThreadStarted(ID<Thread> id)
+{
+	dap::ThreadEvent event;
+	event.reason = "started";
+	event.threadId = id.value();
+	session->send(event);
+}
+
+void Server::Impl::onThreadStepped(ID<Thread> id)
+{
+	dap::StoppedEvent event;
+	event.threadId = id.value();
+	event.reason = "step";
+	session->send(event);
+}
+
+void Server::Impl::onLineBreakpointHit(ID<Thread> id)
+{
+	dap::StoppedEvent event;
+	event.threadId = id.value();
+	event.reason = "breakpoint";
+	session->send(event);
+}
+
+void Server::Impl::onFunctionBreakpointHit(ID<Thread> id)
+{
+	dap::StoppedEvent event;
+	event.threadId = id.value();
+	event.reason = "function breakpoint";
+	session->send(event);
+}
+
+dap::Scope Server::Impl::scope(const char* type, Scope* s)
+{
+	dap::Scope out;
+	// out.line = s->startLine;
+	// out.endLine = s->endLine;
+	out.source = source(s->file.get());
+	out.name = type;
+	out.presentationHint = type;
+	out.variablesReference = s->variables->id.value();
+	return out;
+}
+
+dap::Source Server::Impl::source(File* file)
+{
+	dap::Source out;
+	out.name = file->name;
+	if(file->isVirtual())
+	{
+		out.sourceReference = file->id.value();
+	}
+	else
+	{
+		out.path = file->path();
+	}
+	return out;
+}
+
+std::shared_ptr<File> Server::Impl::file(const dap::Source& source)
+{
+	auto lock = ctx->lock();
+	if(source.sourceReference.has_value())
+	{
+		auto id = source.sourceReference.value();
+		if(auto file = lock.get(File::ID(id)))
+		{
+			return file;
+		}
+	}
+
+	auto files = lock.files();
+	if(source.path.has_value())
+	{
+		auto path = source.path.value();
+		std::shared_ptr<File> out;
+		for(auto file : files)
+		{
+			if(file->path() == path)
+			{
+				out = file;
+				break;
+			}
+		}
+		return out;
+	}
+
+	if(source.name.has_value())
+	{
+		auto name = source.name.value();
+		std::shared_ptr<File> out;
+		for(auto file : files)
+		{
+			if(file->name == name)
+			{
+				out = file;
+				break;
+			}
+		}
+		return out;
+	}
+
+	return nullptr;
+}
+
+std::shared_ptr<Server> Server::create(const std::shared_ptr<Context>& ctx, int port)
+{
+	return std::make_shared<Server::Impl>(ctx, port);
+}
+
+}  // namespace dbg
+}  // namespace vk
\ No newline at end of file
diff --git a/src/Vulkan/Debug/Server.hpp b/src/Vulkan/Debug/Server.hpp
new file mode 100644
index 0000000..dead78e
--- /dev/null
+++ b/src/Vulkan/Debug/Server.hpp
@@ -0,0 +1,43 @@
+// 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_SERVER_HPP_
+#define VK_DEBUG_SERVER_HPP_
+
+#include <memory>
+
+namespace vk
+{
+namespace dbg
+{
+
+class Context;
+
+// Server implements a Debug Adapter Protocol server that listens on a specific
+// port, that operates on the vk::dbg::Context passed to the constructor.
+class Server
+{
+public:
+	static std::shared_ptr<Server> create(const std::shared_ptr<Context>&, int port);
+
+	virtual ~Server() = default;
+
+private:
+	class Impl;
+};
+
+}  // namespace dbg
+}  // namespace vk
+
+#endif  // VK_DEBUG_SERVER_HPP_