SpirvShaderDebugger: Mark II
This is a major reworking of the way the debugger is implemented.
Instead of generating shader code that is continually driving `vk::dbg` state, the debugger now:
* Maintains a full shadow copy of all Intermediate values (SSA)
* Only calls out to C++ whenever a trap is set
* Only constructs and updates the `vk::dbg` state when a trap is hit
The main goal of this reworking is to properly support OpenCL.Debug.100's DebugValue instructions that may use deref expressions
(See https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#_debug_operations_a_id_operation_a)
These are remarkably hard to implement without having everything backed with real memory.
The resulting reimplementation is:
* Much, much faster (>1000x faster for sample apps I've tried). Only ~2x slower than non-debug enabled SwiftShader now.
* Uses much less runtime memory.
* Much cleaner, removing a load of template magic for setting variable state that was extremely hard to follow
* More thoroughly commented, explaining each component and method.
Bug: b/145351270
Change-Id: I1b48e9defae095109ebfaf90dfb7c7e22d335816
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48729
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Pipeline/SpirvShaderDebugger.cpp b/src/Pipeline/SpirvShaderDebugger.cpp
index 76f1fad..4aa1059 100644
--- a/src/Pipeline/SpirvShaderDebugger.cpp
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -14,6 +14,8 @@
#include "SpirvShader.hpp"
+#include "System/Types.hpp"
+
// If enabled, each instruction will be printed before defining.
#define PRINT_EACH_DEFINED_DBG_INSTRUCTION 0
// If enabled, each instruction will be printed before emitting.
@@ -30,6 +32,7 @@
# include "Vulkan/Debug/File.hpp"
# include "Vulkan/Debug/Thread.hpp"
# include "Vulkan/Debug/Variable.hpp"
+# include "Vulkan/Debug/EventListener.hpp"
# include "spirv/unified1/OpenCLDebugInfo100.h"
# include "spirv-tools/libspirv.h"
@@ -37,8 +40,137 @@
# include <algorithm>
# include <queue>
+////////////////////////////////////////////////////////////////////////////////
+// namespace sw::SIMD
+// Adds sw::SIMD::PerLane<> and typedefs for C++ versions of the Reactor SIMD
+// types (sw::SIMD::Int, etc)
+////////////////////////////////////////////////////////////////////////////////
+namespace sw {
+namespace SIMD {
+
+// PerLane is a SIMD vector that holds N vectors of width SIMD::Width.
+// PerLane operator[] returns the elements of a single lane (a transpose of the
+// storage arrays).
+template<typename T, int N = 1>
+struct PerLane
+{
+ sw::vec<T, N> operator[](int lane) const
+ {
+ sw::vec<T, N> out;
+ for(int i = 0; i < N; i++)
+ {
+ out[i] = elements[i][lane];
+ }
+ return out;
+ }
+ std::array<sw::vec<T, Width>, N> elements;
+};
+
+template<typename T>
+struct PerLane<T, 1>
+{
+ const T &operator[](int lane) const { return data[lane]; }
+ std::array<T, Width> data;
+};
+
+using uint_t = PerLane<unsigned int>;
+using uint2 = PerLane<unsigned int, 2>;
+using uint3 = PerLane<unsigned int, 3>;
+using uint4 = PerLane<unsigned int, 4>;
+
+using int_t = PerLane<int>;
+using int2 = PerLane<int, 2>;
+using int3 = PerLane<int, 3>;
+using int4 = PerLane<int, 4>;
+
+using float_t = PerLane<float>;
+using vec2 = PerLane<float, 2>;
+using vec3 = PerLane<float, 3>;
+using vec4 = PerLane<float, 4>;
+
+} // namespace SIMD
+} // namespace sw
+
+////////////////////////////////////////////////////////////////////////////////
+// namespace ::(anonymous)
+// Utility functions
+////////////////////////////////////////////////////////////////////////////////
namespace {
+// vecElementName() returns the element name for the i'th vector element of
+// size n.
+// Vectors of size 4 or less use a [x,y,z,w] element naming scheme.
+// Larger vectors use a number index naming scheme.
+std::string vecElementName(int i, int n)
+{
+ return (n > 4) ? std::to_string(i) : &"x\0y\0z\0w\0"[i * 2];
+}
+
+// laneName() returns a string describing values for the lane i.
+std::string laneName(int i)
+{
+ return "Lane " + std::to_string(i);
+}
+
+// isEntryBreakpointForShaderType() returns true if name is equal to the
+// special entry breakpoint name for the given shader type.
+// This allows the IDE to request all shaders of the given type to break on
+// entry.
+bool isEntryBreakpointForShaderType(spv::ExecutionModel type, const std::string &name)
+{
+ switch(type)
+ {
+ case spv::ExecutionModelGLCompute: return name == "ComputeShader";
+ case spv::ExecutionModelFragment: return name == "FragmentShader";
+ case spv::ExecutionModelVertex: return name == "VertexShader";
+ default: return false;
+ }
+}
+
+// makeDbgValue() returns a vk::dbg::Value that contains a copy of val.
+template<typename T>
+std::shared_ptr<vk::dbg::Value> makeDbgValue(const T &val)
+{
+ return vk::dbg::make_constant(val);
+}
+
+// makeDbgValue() returns a vk::dbg::Value that contains a copy of vec.
+template<typename T, int N>
+std::shared_ptr<vk::dbg::Value> makeDbgValue(const sw::vec<T, N> &vec)
+{
+ return vk::dbg::Struct::create("vec" + std::to_string(N), [&](auto &vc) {
+ for(int i = 0; i < N; i++)
+ {
+ vc->put(vecElementName(i, N), makeDbgValue<T>(vec[i]));
+ }
+ });
+}
+
+// store() emits a store instruction to write sizeof(T) bytes from val into ptr.
+template<typename T>
+void store(const rr::RValue<rr::Pointer<rr::Byte>> &ptr, const rr::RValue<T> &val)
+{
+ *rr::Pointer<T>(ptr) = val;
+}
+
+// store() emits a store instruction to write sizeof(T) bytes from val into ptr.
+template<typename T>
+void store(const rr::RValue<rr::Pointer<rr::Byte>> &ptr, const T &val)
+{
+ *rr::Pointer<T>(ptr) = val;
+}
+
+// store() emits a store instruction to write sizeof(T) * N bytes from val into
+// ptr.
+template<typename T, std::size_t N>
+void store(const rr::RValue<rr::Pointer<rr::Byte>> &ptr, const std::array<T, N> &val)
+{
+ for(std::size_t i = 0; i < N; i++)
+ {
+ store<T>(ptr + i * sizeof(T), val[i]);
+ }
+}
+
// ArgTy<F>::type resolves to the single argument type of the function F.
template<typename F>
struct ArgTy
@@ -46,23 +178,106 @@
using type = typename ArgTy<decltype(&F::operator())>::type;
};
+// ArgTy<F>::type resolves to the single argument type of the template method.
template<typename R, typename C, typename Arg>
struct ArgTy<R (C::*)(Arg) const>
{
using type = typename std::decay<Arg>::type;
};
-template<typename T>
-using ArgTyT = typename ArgTy<T>::type;
+// ArgTyT resolves to the single argument type of the template function or
+// method F.
+template<typename F>
+using ArgTyT = typename ArgTy<F>::type;
-template<typename T>
-T take(std::queue<T> &queue)
+// getOrCreate() searchs the map for the given key. If the map contains an entry
+// with the given key, then the value is returned. Otherwise, create() is called
+// and the returned value is placed into the map with the given key, and this
+// value is returned.
+// create is a function with the signature:
+// V()
+template<typename K, typename V, typename CREATE, typename HASH>
+V getOrCreate(std::unordered_map<K, V, HASH> &map, const K &key, CREATE &&create)
{
- auto v = queue.front();
- queue.pop();
- return v;
+ auto it = map.find(key);
+ if(it != map.end())
+ {
+ return it->second;
+ }
+ auto val = create();
+ map.emplace(key, val);
+ return val;
}
+// HoversFromLocals is an implementation of vk::dbg::Variables that is used to
+// provide a scope's 'hover' variables - those that appear when you place the
+// cursor over a variable in an IDE.
+// Unlike the top-level SIMD lane grouping of variables in Frame::locals,
+// Frame::hovers displays each variable as a value per SIMD lane.
+// Instead maintaining another collection of variables per scope,
+// HoversFromLocals dynamically builds the hover information from the locals.
+class HoversFromLocals : public vk::dbg::Variables
+{
+public:
+ HoversFromLocals(const std::shared_ptr<vk::dbg::Variables> &locals)
+ : locals(locals)
+ {}
+
+ void foreach(size_t startIndex, size_t count, const ForeachCallback &cb) override
+ {
+ // No op - hovers are only searched, never iterated.
+ }
+
+ std::shared_ptr<vk::dbg::Value> get(const std::string &name) override
+ {
+ // Is the hover variable a SIMD-common variable? If so, just return
+ // that.
+ if(auto val = locals->get(name))
+ {
+ return val;
+ }
+
+ // Search each of the lanes for the named variable.
+ // Collect them all up, and return that in a new Struct value.
+ bool found = false;
+ auto str = vk::dbg::Struct::create("", [&](auto &vc) {
+ for(int lane = 0; lane < sw::SIMD::Width; lane++)
+ {
+ auto laneN = laneName(lane);
+ if(auto laneV = locals->get(laneN))
+ {
+ if(auto children = laneV->children())
+ {
+ if(auto val = children->get(name))
+ {
+ vc->put(laneN, val);
+ found = true;
+ }
+ }
+ }
+ }
+ });
+
+ if(found)
+ {
+ // The value returned will be returned to the debug client by
+ // identifier. As the value is a Struct, the server will include
+ // a handle to the Variables, which needs to be kept alive so the
+ // client can send a request for its members.
+ // lastFind keeps any nested Variables alive long enough for them to
+ // be requested.
+ lastFind = str;
+ return str;
+ }
+
+ return nullptr;
+ }
+
+private:
+ std::shared_ptr<vk::dbg::Variables> locals;
+ std::shared_ptr<vk::dbg::Struct> lastFind;
+};
+
} // anonymous namespace
namespace spvtools {
@@ -80,44 +295,19 @@
} // namespace spvtools
-namespace {
-
-const char *laneNames[] = { "Lane 0", "Lane 1", "Lane 2", "Lane 3" };
-static_assert(sizeof(laneNames) / sizeof(laneNames[0]) == sw::SIMD::Width,
- "laneNames must have SIMD::Width entries");
-
-template<typename T>
-std::string tostring(const T &s)
-{
- return std::to_string(s);
-}
-std::string tostring(char *s)
-{
- return s;
-}
-std::string tostring(const char *s)
-{
- return s;
-}
-template<typename T>
-std::string tostring(T *s)
-{
- char buf[32];
- snprintf(buf, sizeof(buf), "%p", s);
- return buf;
-}
-std::string tostring(sw::SpirvShader::Object::ID id)
-{
- return "%" + tostring(id.value());
-}
-
////////////////////////////////////////////////////////////////////////////////
+// namespace ::(anonymous)::debug
// OpenCL.Debug.100 data structures
////////////////////////////////////////////////////////////////////////////////
+namespace {
namespace debug {
+struct Declare;
+struct LocalVariable;
struct Member;
+struct Value;
+// Object is the common base class for all the OpenCL.Debug.100 data structures.
struct Object
{
enum class Kind
@@ -195,22 +385,27 @@
return "<unknown>";
}
-template<typename TYPE_, typename BASE, Object::Kind KIND_>
+// ObjectImpl is a helper template struct which simplifies deriving from Object.
+// ObjectImpl passes down the KIND to the Object constructor, and implements
+// kindof().
+template<typename TYPE, typename BASE, Object::Kind KIND>
struct ObjectImpl : public BASE
{
- using ID = sw::SpirvID<TYPE_>;
- static constexpr auto KIND = KIND_;
+ using ID = sw::SpirvID<TYPE>;
+ static constexpr auto Kind = KIND;
ObjectImpl()
- : BASE(KIND)
+ : BASE(Kind)
{}
static_assert(BASE::kindof(KIND), "BASE::kindof() returned false");
// kindof() returns true iff kind is of this type, or any type deriving from
// this type.
- static constexpr bool kindof(Object::Kind kind) { return kind == KIND; }
+ static constexpr bool kindof(Object::Kind kind) { return kind == Kind; }
};
+// cast() casts the debug type pointer obj to TO.
+// If obj is null or not of the type TO, then nullptr is returned.
template<typename TO, typename FROM>
TO *cast(FROM *obj)
{
@@ -218,6 +413,8 @@
return (TO::kindof(obj->kind)) ? static_cast<TO *>(obj) : nullptr;
}
+// cast() casts the debug type pointer obj to TO.
+// If obj is null or not of the type TO, then nullptr is returned.
template<typename TO, typename FROM>
const TO *cast(const FROM *obj)
{
@@ -225,11 +422,9 @@
return (TO::kindof(obj->kind)) ? static_cast<const TO *>(obj) : nullptr;
}
+// Scope is the base class for all OpenCL.DebugInfo.100 scope objects.
struct Scope : public Object
{
- // Global represents the global scope.
- static const Scope Global;
-
using ID = sw::SpirvID<Scope>;
inline Scope(Kind kind)
: Object(kind)
@@ -248,9 +443,17 @@
Scope *parent = nullptr;
};
+// Type is the base class for all OpenCL.DebugInfo.100 type objects.
struct Type : public Object
{
using ID = sw::SpirvID<Type>;
+
+ struct Member
+ {
+ Type *type;
+ std::string name;
+ };
+
inline Type(Kind kind)
: Object(kind)
{}
@@ -267,14 +470,8 @@
kind == Kind::TemplateType;
}
- std::pair<const Type *, uint32_t> index(std::queue<uint32_t> &&indices) const
- {
- if(indices.size() == 0)
- {
- return { this, 0 };
- }
- return indexMember(std::move(indices));
- }
+ // name() returns the type name.
+ virtual std::string name() const = 0;
// sizeInBytes() returns the number of bytes of the given debug type.
virtual uint32_t sizeInBytes() const = 0;
@@ -283,16 +480,38 @@
// at ptr of this type.
virtual std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const = 0;
-protected:
- // indexMember() returns the nested inner element debug type and byte offset
- // from the base of this type, using the list of indices.
- virtual std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&) const = 0;
+ // numMembers() returns the number of members for the given type.
+ virtual size_t numMembers() const = 0;
+
+ // getMember() returns the member by index.
+ virtual Member getMember(size_t) const = 0;
+
+ // undefined() returns a shared pointer to a vk::dbg::Value that represents
+ // an undefined value of this type.
+ std::shared_ptr<vk::dbg::Value> undefined() const
+ {
+ struct Undef : public vk::dbg::Value
+ {
+ Undef(const std::string &ty)
+ : ty(ty)
+ {}
+ const std::string ty;
+ std::string type() override { return ty; }
+ std::string get(const vk::dbg::FormatFlags &) override { return "<undefined>"; }
+ };
+ return std::make_shared<Undef>(name());
+ }
};
+// CompilationUnit represents the OpenCL.DebugInfo.100 DebugCompilationUnit
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugCompilationUnit
struct CompilationUnit : ObjectImpl<CompilationUnit, Scope, Object::Kind::CompilationUnit>
{
};
+// Source represents the OpenCL.DebugInfo.100 DebugSource instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugSource
struct Source : ObjectImpl<Source, Object, Object::Kind::Source>
{
spv::SourceLanguage language;
@@ -303,19 +522,18 @@
std::shared_ptr<vk::dbg::File> dbgFile;
};
+// BasicType represents the OpenCL.DebugInfo.100 DebugBasicType instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugBasicType
struct BasicType : ObjectImpl<BasicType, Type, Object::Kind::BasicType>
{
- std::string name;
+ std::string name_;
uint32_t size = 0; // in bits.
OpenCLDebugInfo100DebugBaseTypeAttributeEncoding encoding = OpenCLDebugInfo100Unspecified;
+ std::string name() const override { return name_; }
uint32_t sizeInBytes() const override { return size / 8; }
-
- std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&) const override
- {
- DABORT("indexMember() called on BasicType %s", name.c_str());
- return {};
- }
+ size_t numMembers() const override { return 0; }
+ Member getMember(size_t) const override { return {}; }
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
@@ -344,183 +562,96 @@
}
};
+// ArrayType represents the OpenCL.DebugInfo.100 DebugTypeArray instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeArray
+//
+// Unlike OpenCL.DebugInfo.100's DebugTypeArray, ArrayType is always
+// single-dimensional. Multi-dimensional arrays are transformed into multiple
+// nested ArrayTypes. This is done to simplify logic.
struct ArrayType : ObjectImpl<ArrayType, Type, Object::Kind::ArrayType>
{
Type *base = nullptr;
- std::vector<uint32_t> dimensions;
+ bool ownsBase = false; // If true, base is owned by this ArrayType.
+ uint32_t size; // In elements
- // build() loops over each element of the multi-dimensional array, calling
- // enter() for building each new dimension group, and element() for each
- // inner-most dimension element.
- //
- // enter must be a function of the signature:
- // std::shared_ptr<vk::dbg::VariableContainer>
- // (std::shared_ptr<vk::dbg::VariableContainer>& parent, uint32_t idx)
- // where:
- // parent is the outer dimension group
- // idx is the index of the next deepest dimension.
- //
- // element must be a function of the signature:
- // void(std::shared_ptr<vk::dbg::VariableContainer> &parent,
- // uint32_t idx, uint32_t offset)
- // where:
- // parent is the penultimate deepest dimension group
- // idx is the index of the element in parent group
- // offset is the 'flattened array' index for the element.
- template<typename GROUP, typename ENTER_FUNC, typename ELEMENT_FUNC>
- void build(const GROUP &group, ENTER_FUNC &&enter, ELEMENT_FUNC &&element) const
+ ~ArrayType()
{
- if(dimensions.size() == 0) { return; }
-
- struct Dimension
- {
- uint32_t idx = 0;
- GROUP group;
- bool built = false;
- };
-
- std::vector<Dimension> dims;
- dims.resize(dimensions.size());
-
- uint32_t offset = 0;
-
- int dimIdx = 0;
- const int n = static_cast<int>(dimensions.size()) - 1;
- while(dimIdx >= 0)
- {
- // (Re)build groups to inner dimensions.
- for(; dimIdx <= n; dimIdx++)
- {
- if(!dims[dimIdx].built)
- {
- dims[dimIdx].group = (dimIdx == 0)
- ? group
- : enter(dims[dimIdx - 1].group, dims[dimIdx - 1].idx);
- dims[dimIdx].built = true;
- }
- }
-
- // Emit each of the inner-most dimension elements.
- for(dims[n].idx = 0; dims[n].idx < dimensions[n]; dims[n].idx++)
- {
- ASSERT(dims[n].built);
- element(dims[n].group, dims[n].idx, offset++);
- }
-
- dimIdx = n;
- while(dims[dimIdx].idx == dimensions[dimIdx])
- {
- dims[dimIdx] = {}; // Clear the the current dimension
- dimIdx--; // Step up a dimension
- if(dimIdx < 0) { break; }
- dims[dimIdx].idx++; // Increment the next dimension index
- }
- }
+ if(ownsBase) { delete base; }
}
- uint32_t sizeInBytes() const override
- {
- auto numBytes = base->sizeInBytes();
- for(auto dim : dimensions)
- {
- numBytes *= dim;
- }
- return numBytes;
- }
-
- std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
- {
- std::vector<uint32_t> arrIndices(dimensions.size());
- for(size_t i = 0; i < dimensions.size(); i++)
- {
- arrIndices[i] = take(indices);
- }
-
- auto out = base->index(std::move(indices));
- auto stride = base->sizeInBytes();
- for(int i = static_cast<int>(dimensions.size()) - 1; i >= 0; i--)
- {
- out.second += arrIndices[i] * stride;
- stride *= dimensions[i];
- }
- return out;
- }
+ std::string name() const override { return base->name() + "[]"; }
+ uint32_t sizeInBytes() const override { return base->sizeInBytes() * size; }
+ size_t numMembers() const override { return size; }
+ Member getMember(size_t i) const override { return { base, std::to_string(i) }; }
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
- auto vc = std::make_shared<vk::dbg::VariableContainer>();
- build(
- vc,
- [&](std::shared_ptr<vk::dbg::VariableContainer> &parent, uint32_t idx) {
- auto child = std::make_shared<vk::dbg::VariableContainer>();
- parent->put(tostring(idx), std::make_shared<vk::dbg::Struct>("array", child));
- return child;
- },
- [&](std::shared_ptr<vk::dbg::VariableContainer> &parent, uint32_t idx, uint32_t offset) {
- offset = offset * base->sizeInBytes() * (interleaved ? sw::SIMD::Width : 1);
- auto addr = static_cast<uint8_t *>(ptr) + offset;
- auto child = base->value(addr, interleaved);
- auto key = tostring(idx);
+ auto members = std::make_shared<vk::dbg::VariableContainer>();
+
+ auto addr = static_cast<uint8_t *>(ptr);
+ for(size_t i = 0; i < size; i++)
+ {
+ auto member = getMember(i);
+
# if DEBUG_ANNOTATE_VARIABLE_KEYS
- key += " (" + tostring(addr) + " +" + tostring(offset) + ", idx: " + tostring(idx) + ")" + (interleaved ? "I" : "F");
+ key += " (" + std::to_string(addr) + " +" + std::to_string(offset) + ", i: " + std::to_string(i) + ")" + (interleaved ? "I" : "F");
# endif
- parent->put(key, child);
- });
- return std::make_shared<vk::dbg::Struct>("array", vc);
+ members->put(member.name, base->value(addr, interleaved));
+
+ addr += base->sizeInBytes() * (interleaved ? sw::SIMD::Width : 1);
+ }
+ return std::make_shared<vk::dbg::Struct>(name(), members);
}
};
+// VectorType represents the OpenCL.DebugInfo.100 DebugTypeVector instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeVector
struct VectorType : ObjectImpl<VectorType, Type, Object::Kind::VectorType>
{
Type *base = nullptr;
uint32_t components = 0;
- uint32_t sizeInBytes() const override
- {
- return base->sizeInBytes() * components;
- }
-
- std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
- {
- auto idx = take(indices);
- auto out = base->index(std::move(indices));
- out.second += base->sizeInBytes() * idx;
- return out;
- }
+ std::string name() const override { return "vec" + std::to_string(components) + "<" + base->name() + ">"; }
+ uint32_t sizeInBytes() const override { return base->sizeInBytes() * components; }
+ size_t numMembers() const override { return components; }
+ Member getMember(size_t i) const override { return { base, vecElementName(i, components) }; }
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
const auto elSize = base->sizeInBytes();
- auto vc = std::make_shared<vk::dbg::VariableContainer>();
+ auto members = std::make_shared<vk::dbg::VariableContainer>();
for(uint32_t i = 0; i < components; i++)
{
auto offset = elSize * i * (interleaved ? sw::SIMD::Width : 1);
auto elPtr = static_cast<uint8_t *>(ptr) + offset;
- auto elKey = (components > 4) ? tostring(i) : &"x\0y\0z\0w\0"[i * 2];
# if DEBUG_ANNOTATE_VARIABLE_KEYS
- elKey += " (" + tostring(elPtr) + " +" + tostring(offset) + ")" + (interleaved ? "I" : "F");
+ elKey += " (" + std::to_string(elPtr) + " +" + std::to_string(offset) + ")" + (interleaved ? "I" : "F");
# endif
- vc->put(elKey, base->value(elPtr, interleaved));
+ members->put(getMember(i).name, base->value(elPtr, interleaved));
}
- return std::make_shared<vk::dbg::Struct>("vector", vc);
+ return std::make_shared<vk::dbg::Struct>(name(), members);
}
};
+// FunctionType represents the OpenCL.DebugInfo.100 DebugTypeFunction
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeFunction
struct FunctionType : ObjectImpl<FunctionType, Type, Object::Kind::FunctionType>
{
uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
Type *returnTy = nullptr;
std::vector<Type *> paramTys;
+ std::string name() const override { return "function"; }
uint32_t sizeInBytes() const override { return 0; }
- std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
- {
- DABORT("indexMember() called on FunctionType");
- return {};
- }
+ size_t numMembers() const override { return 0; }
+ Member getMember(size_t i) const override { return {}; }
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override { return nullptr; }
};
+// Member represents the OpenCL.DebugInfo.100 DebugTypeMember instruction.
+// Despite the instruction name, this is not a type - rather a member of a type.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeMember
struct Member : ObjectImpl<Member, Object, Object::Kind::Member>
{
std::string name;
@@ -534,9 +665,12 @@
uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
};
+// CompositeType represents the OpenCL.DebugInfo.100 DebugTypeComposite
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeComposite
struct CompositeType : ObjectImpl<CompositeType, Type, Object::Kind::CompositeType>
{
- std::string name;
+ std::string name_;
OpenCLDebugInfo100DebugCompositeType tag = OpenCLDebugInfo100Class;
Source *source = nullptr;
uint32_t line = 0;
@@ -545,36 +679,33 @@
std::string linkage;
uint32_t size = 0; // in bits.
uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
- std::vector<Member *> members;
+ std::vector<debug::Member *> members_;
+ std::string name() const override { return name_; }
uint32_t sizeInBytes() const override { return size / 8; }
-
- std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
- {
- auto idx = take(indices);
- auto member = members[idx];
- auto out = member->type->index(std::move(indices));
- out.second += member->offset / 8;
- return out;
- }
+ size_t numMembers() const override { return members_.size(); }
+ Member getMember(size_t i) const override { return { members_[i]->type, members_[i]->name }; }
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
- auto vc = std::make_shared<vk::dbg::VariableContainer>();
- for(auto &member : members)
+ auto fields = std::make_shared<vk::dbg::VariableContainer>();
+ for(auto &member : members_)
{
auto offset = (member->offset / 8) * (interleaved ? sw::SIMD::Width : 1);
auto elPtr = static_cast<uint8_t *>(ptr) + offset;
auto elKey = member->name;
# if DEBUG_ANNOTATE_VARIABLE_KEYS
- // elKey += " (" + tostring(elPtr) + " +" + tostring(offset) + ")" + (interleaved ? "I" : "F");
+ // elKey += " (" + std::to_string(elPtr) + " +" + std::to_string(offset) + ")" + (interleaved ? "I" : "F");
# endif
- vc->put(elKey, member->type->value(elPtr, interleaved));
+ fields->put(elKey, member->type->value(elPtr, interleaved));
}
- return std::make_shared<vk::dbg::Struct>(name, vc);
+ return std::make_shared<vk::dbg::Struct>(name_, fields);
}
};
+// TemplateParameter represents the OpenCL.DebugInfo.100
+// DebugTypeTemplateParameter instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeTemplateParameter
struct TemplateParameter : ObjectImpl<TemplateParameter, Object, Object::Kind::TemplateParameter>
{
std::string name;
@@ -585,22 +716,26 @@
uint32_t column = 0;
};
+// TemplateType represents the OpenCL.DebugInfo.100 DebugTypeTemplate
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeTemplate
struct TemplateType : ObjectImpl<TemplateType, Type, Object::Kind::TemplateType>
{
Type *target = nullptr; // Class, struct or function.
std::vector<TemplateParameter *> parameters;
+ std::string name() const override { return "template<>"; }
uint32_t sizeInBytes() const override { return target->sizeInBytes(); }
- std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
- {
- return target->index(std::move(indices));
- }
+ size_t numMembers() const override { return 0; }
+ Member getMember(size_t i) const override { return {}; }
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
return target->value(ptr, interleaved);
}
};
+// Function represents the OpenCL.DebugInfo.100 DebugFunction instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugFunction
struct Function : ObjectImpl<Function, Scope, Object::Kind::Function>
{
std::string name;
@@ -613,13 +748,19 @@
sw::SpirvShader::Function::ID function;
};
+// LexicalBlock represents the OpenCL.DebugInfo.100 DebugLexicalBlock instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugLexicalBlock
struct LexicalBlock : ObjectImpl<LexicalBlock, Scope, Object::Kind::LexicalBlock>
{
uint32_t line = 0;
uint32_t column = 0;
std::string name;
+
+ std::vector<LocalVariable *> variables;
};
+// InlinedAt represents the OpenCL.DebugInfo.100 DebugInlinedAt instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugInlinedAt
struct InlinedAt : ObjectImpl<InlinedAt, Object, Object::Kind::InlinedAt>
{
uint32_t line = 0;
@@ -627,12 +768,16 @@
InlinedAt *inlined = nullptr;
};
+// SourceScope represents the OpenCL.DebugInfo.100 DebugScope instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugScope
struct SourceScope : ObjectImpl<SourceScope, Object, Object::Kind::SourceScope>
{
Scope *scope = nullptr;
InlinedAt *inlinedAt = nullptr;
};
+// GlobalVariable represents the OpenCL.DebugInfo.100 DebugGlobalVariable instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugGlobalVariable
struct GlobalVariable : ObjectImpl<GlobalVariable, Object, Object::Kind::GlobalVariable>
{
std::string name;
@@ -646,10 +791,24 @@
uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
};
+// LocalVariable represents the OpenCL.DebugInfo.100 DebugLocalVariable
+// instruction.
+// Local variables are essentially just a scoped variable name.
+// Their value comes from either a DebugDeclare (which has an immutable pointer
+// to the actual data), or from a number of DebugValues (which can change
+// any nested members of the variable over time).
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugLocalVariable
struct LocalVariable : ObjectImpl<LocalVariable, Object, Object::Kind::LocalVariable>
{
static constexpr uint32_t NoArg = ~uint32_t(0);
+ enum class Definition
+ {
+ Undefined, // Variable has no defined value
+ Declaration, // Variable value comes from definition
+ Values // Variable value comes from values
+ };
+
std::string name;
Type *type = nullptr;
Source *source = nullptr;
@@ -657,19 +816,40 @@
uint32_t column = 0;
Scope *parent = nullptr;
uint32_t arg = NoArg;
+
+ Definition definition = Definition::Undefined;
+ Declare *declaration = nullptr; // Used if definition == Definition::Declaration
+
+ // ValueNode is a tree node of debug::Value definitions.
+ // Each node in the tree represents an element in the type tree.
+ struct ValueNode
+ {
+ // NoDebugValueIndex indicates that this node is never assigned a value.
+ static constexpr const uint32_t NoDebugValueIndex = ~0u;
+
+ uint32_t debugValueIndex = NoDebugValueIndex; // Index into State::lastReachedDebugValues
+ std::unordered_map<uint32_t, std::unique_ptr<ValueNode>> children;
+ };
+ ValueNode values; // Used if definition == Definition::Values
};
+// Operation represents the OpenCL.DebugInfo.100 DebugOperation instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugOperation
struct Operation : ObjectImpl<Operation, Object, Object::Kind::Operation>
{
uint32_t opcode = 0;
std::vector<uint32_t> operands;
};
+// Expression represents the OpenCL.DebugInfo.100 DebugExpression instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugExpression
struct Expression : ObjectImpl<Expression, Object, Object::Kind::Expression>
{
std::vector<Operation *> operations;
};
+// Declare represents the OpenCL.DebugInfo.100 DebugDeclare instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugDeclare
struct Declare : ObjectImpl<Declare, Object, Object::Kind::Declare>
{
LocalVariable *local = nullptr;
@@ -677,6 +857,8 @@
Expression *expression = nullptr;
};
+// Value represents the OpenCL.DebugInfo.100 DebugValue instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugValue
struct Value : ObjectImpl<Value, Object, Object::Kind::Value>
{
LocalVariable *local = nullptr;
@@ -685,8 +867,6 @@
std::vector<uint32_t> indexes;
};
-const Scope Scope::Global = CompilationUnit{};
-
// find<T>() searches the nested scopes, returning for the first scope that is
// castable to type T. If no scope can be found of type T, then nullptr is
// returned.
@@ -697,108 +877,172 @@
return scope->parent ? find<T>(scope->parent) : nullptr;
}
-bool hasDebuggerScope(debug::Scope *spirvScope)
-{
- return debug::cast<debug::Function>(spirvScope) != nullptr ||
- debug::cast<debug::LexicalBlock>(spirvScope) != nullptr;
-}
-
} // namespace debug
} // anonymous namespace
-namespace rr {
-
////////////////////////////////////////////////////////////////////////////////
-// rr::CToReactor<T> specializations.
+// namespace ::sw
+//
+// Implementations for:
+// sw::SpirvShader::Impl::Debugger
+// sw::SpirvShader::Impl::Debugger::LocalVariableValue
+// sw::SpirvShader::Impl::Debugger::State
+// sw::SpirvShader::Impl::Debugger::State::Data
////////////////////////////////////////////////////////////////////////////////
-template<typename T>
-struct CToReactor<sw::SpirvID<T>>
-{
- using type = rr::Int;
- static rr::Int cast(sw::SpirvID<T> id) { return rr::Int(id.value()); }
-};
-
-template<typename T>
-struct CToReactor<vk::dbg::ID<T>>
-{
- using type = rr::Int;
- static rr::Int cast(vk::dbg::ID<T> id) { return rr::Int(id.value()); }
-};
-
-} // namespace rr
-
namespace sw {
////////////////////////////////////////////////////////////////////////////////
// sw::SpirvShader::Impl::Debugger
//
-// Private struct holding debugger information for the SpirvShader.
+// SpirvShader-private struct holding compile-time-mutable and
+// execution-time-immutable debugger information.
+//
// There is an instance of this class per shader program.
////////////////////////////////////////////////////////////////////////////////
-struct SpirvShader::Impl::Debugger
+struct SpirvShader::Impl::Debugger : public vk::dbg::ClientEventListener
{
- class Group;
class State;
+ class LocalVariableValue;
+
+ Debugger(const SpirvShader *shader, const std::shared_ptr<vk::dbg::Context> &ctx);
+ ~Debugger();
enum class Pass
{
- Define,
- Emit
+ Define, // Pre-pass (called from SpirvShader constructor)
+ Emit // Code generation pass (called from SpirvShader::emit()).
};
- void process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass);
+ // process() is called for each debugger instruction in two compiler passes.
+ // For the Define pass, process() constructs ::debug objects and
+ // registers them in the objects map.
+ // For the Emit pass, process() populates the fields of ::debug objects and
+ // potentially emits instructions for the shader program.
+ void process(const InsnIterator &insn, EmitState *state, Pass pass);
- void setNextSetLocationIsStep();
- void setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &, int line, int column);
- void setLocation(EmitState *state, const std::string &path, int line, int column);
+ // finalize() must be called after all shader instruction have been emitted.
+ // finalize() allocates the trap memory and registers the Debugger for
+ // client debugger events so that it can monitor for changes in breakpoints.
+ void finalize();
- // foreachLane() calls f for each debugger group representing the SIMD
- // lanes of execution.
- // FUNC is a function with the signature:
- // (int lane, const Group &group, auto &key)
- template<typename Key, typename Func>
- void foreachLane(const Key &key, const debug::Scope *scope, EmitState *state, Func &&f) const;
+ // setNextSetLocationIsSteppable() indicates that the next call to
+ // setLocation() must be a debugger steppable line.
+ void setNextSetLocationIsSteppable();
- // exposeVariable exposes the variable with the given ID to the debugger
- // using the specified key.
- template<typename Key>
- void exposeVariable(
- const SpirvShader *shader,
- const Key &key,
- const debug::Scope *scope,
- const debug::Type *type,
- Object::ID id,
- EmitState *state) const;
+ // setScope() sets the current debug source scope. Used by setLocation()
+ // when the next location is debugger steppable.
+ void setScope(debug::SourceScope *);
- // exposeVariable exposes the variable with the given ID to the
- // debugger under the specified group, for the specified SIMD lane.
- template<typename Key>
- void exposeVariable(
- const SpirvShader *shader,
- const Group &group,
- int lane,
- const Key &key,
- const debug::Type *type,
- Object::ID id,
- EmitState *state,
- int wordOffset = 0) const;
+ // setLocation() sets the current codegen source location to the given file
+ // and line.
+ void setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &, int line);
+ void setLocation(EmitState *state, const char *file, int line);
- std::shared_ptr<vk::dbg::Context> ctx;
- std::shared_ptr<vk::dbg::File> spirvFile;
- std::unordered_map<const void *, int> spirvLineMappings; // instruction pointer to line
- std::unordered_map<const void *, Object::ID> results; // instruction pointer to result ID
+ using SpirvInstruction = const void *;
- // Shadow memory is used to construct a contiguous memory block for local
- // variables that may be formed from multiple SSA values.
+ const SpirvShader *const shader; // The shader program being debugged
+ std::shared_ptr<vk::dbg::Context> const ctx; // The debugger context
+ bool shaderHasDebugInfo; // True if the shader has high-level debug info (OpenCL.Debug100 instructions)
+ std::shared_ptr<vk::dbg::File> spirvFile; // Virtual file containing SPIR-V disassembly instructions
+ std::unordered_map<SpirvInstruction, int> spirvLineMappings; // Instruction pointer to line
+ std::unordered_map<SpirvInstruction, Object::ID> results; // Instruction pointer to result ID
+
+ // LocationAndScope holds a source location and scope pair.
+ struct LocationAndScope
+ {
+ vk::dbg::Location location;
+ debug::SourceScope *scope;
+
+ inline bool operator==(const LocationAndScope &other) const
+ {
+ return location == other.location && scope == other.scope;
+ }
+ struct Hash
+ {
+ uint64_t operator()(const LocationAndScope &l) const
+ {
+ return std::hash<decltype(l.location)>()(l.location) ^ std::hash<decltype(l.scope)>()(l.scope);
+ }
+ };
+ };
+
+ // Traps holds information about debugger traps - points in the shader
+ // program where execution may pause for the debugger, either due to hitting
+ // a breakpoint or following a single line step.
+ // The Traps::memory is continually read during execution of a shader,
+ // triggering a trap when the byte is non-zero. Traps can also be enabled
+ // via the State::alwaysTrap field.
+ struct Traps
+ {
+ // Source location + scope -> line trap index
+ std::unordered_map<LocationAndScope, size_t, LocationAndScope::Hash> byLocationAndScope;
+
+ // Function name -> entry trap index
+ std::unordered_map<std::string, size_t> byFunctionName;
+
+ // Trap index -> source location + scope
+ std::vector<LocationAndScope> byIndex;
+
+ // Trap memory - shared for all running instances of the shader.
+ // Each byte represents a single trap enabled (1) / disabled (0) state.
+ std::unique_ptr<uint8_t[]> memory;
+ } traps;
+
+ // Shadow memory is used to construct a contiguous memory block
+ // (State::shadow) that contains an up-to-date copy of each
+ // SpirvShader::Object's value(s) in the currently executing shader.
+ // Shadow memory either contains SIMD-interleaved values for all components
+ // in the object, or a SIMD-pointer (Shadow::Pointer).
struct Shadow
{
- // Offset in the shadow memory allocation for the given local variable.
- std::unordered_map<debug::LocalVariable *, uint32_t> offsets;
+ // Entry describes the byte offset and kind of the shadow memory for
+ // a single SpirvShader::Object.
+ struct Entry
+ {
+ enum class Kind
+ {
+ Value,
+ Pointer,
+ };
+ Kind kind;
+ uint32_t offset;
+ };
- // Total size of the shadow memory in bytes.
- uint32_t size;
+ // Pointer is the structure stored in shadow memory for pointer types.
+ // The address for a given SIMD lane is the base + offsets[lane].
+ struct Pointer
+ {
+ uint8_t *base; // Common base address for all SIMD lanes.
+ uint32_t offsets[sw::SIMD::Width]; // Per lane offsets.
+ };
+
+ // Memory is returned by get().
+ // Memory holds a pointer (addr) to the entry in the shadow memory, and
+ // provides the dref() method for dereferencing a pointer for the given
+ // SIMD lane.
+ struct Memory
+ {
+ inline operator void *();
+ inline Memory dref(int lane) const;
+ uint8_t *addr;
+ };
+
+ // create() adds a new entry for the object with the given id.
+ void create(const SpirvShader *, const EmitState *, Object::ID);
+
+ // get() returns a Memory pointing to the shadow memory for the object
+ // with the given id for the given SIMD lane.
+ Memory get(const State *, Object::ID, int lane) const;
+
+ std::unordered_map<Object::ID, Entry> entries;
+ uint32_t size = 0; // Total size of the shadow memory in bytes.
} shadow;
+ // vk::dbg::ClientEventListener
+ void onSetBreakpoint(const vk::dbg::Location &location, bool &handled) override;
+ void onSetBreakpoint(const std::string &func, bool &handled) override;
+ void onBreakpointsChanged() override;
+
private:
// add() registers the debug object with the given id.
template<typename ID>
@@ -840,405 +1084,431 @@
void defineOrEmit(InsnIterator insn, Pass pass, F &&emit);
std::unordered_map<std::string, std::shared_ptr<vk::dbg::File>> files;
- bool nextSetLocationIsStep = true;
- int lastSetLocationLine = 0;
+ uint32_t numDebugValueSlots = 0; // Number of independent debug::Values which need to be tracked
+ bool nextSetLocationIsSteppable = true;
+ debug::SourceScope *lastSetScope = nullptr;
+ vk::dbg::Location lastSetLocation;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::LocalVariableValue
+//
+// Implementation of vk::dbg::Value that displays a debug::LocalVariable that
+// has its value(s) defined by debug::Value(s).
+//
+// TODO(b/145351270) Note: The OpenCL.DebugInfo.100 spec does not state how
+// DebugValues should be applied to the DebugLocalVariable.
+//
+// This implementation keeps track of the order of DebugValues as they are
+// 'executed', and uses the most recent values for each specific index.
+// OpenCL.DebugInfo.100 is significantly derived from the LLVM debug
+// instructions, and so it can be assumed that DebugValue is intended to behave
+// like llvm.dbg.value.
+//
+// https://llvm.org/docs/SourceLevelDebugging.html#object-lifetime-in-optimized-code
+// describes the expected behavior of llvm.dbg.value, which instead of runtime
+// tracking, uses static analysis of the LLVM IR to determine which debug
+// values should be used.
+//
+// If DebugValue is to behave the same way as llvm.dbg.value, then this
+// implementation should be changed to examine the order of DebugValue
+// instructions in the SPIR-V. This can only be done once the SPIR-V generating
+// compiler and SPIR-V optimization passes generate and preserve the DebugValue
+// ordering as described in the LLVM SourceLevelDebugging document.
+////////////////////////////////////////////////////////////////////////////////
+class sw::SpirvShader::Impl::Debugger::LocalVariableValue : public vk::dbg::Value
+{
+public:
+ // Data shared across all nodes in the LocalVariableValue.
+ struct Shared
+ {
+ Shared(debug::LocalVariable const *const variable, State const *const state, int const lane)
+ : variable(variable)
+ , state(state)
+ , lane(lane)
+ {
+ ASSERT(variable->definition == debug::LocalVariable::Definition::Values);
+ }
+
+ debug::LocalVariable const *const variable;
+ State const *const state;
+ int const lane;
+ };
+
+ LocalVariableValue(debug::LocalVariable *variable, State const *const state, int lane);
+
+ LocalVariableValue(
+ std::shared_ptr<const Shared> const &shared,
+ debug::Type const *ty,
+ debug::LocalVariable::ValueNode const *node);
+
+private:
+ // vk::dbg::Value
+ std::string type() override;
+ std::string get(const vk::dbg::FormatFlags &) override;
+ std::shared_ptr<vk::dbg::Variables> children() override;
+
+ void updateValue();
+ std::shared_ptr<const Shared> const shared;
+ debug::Type const *const ty;
+ debug::LocalVariable::ValueNode const *const node;
+ debug::Value *activeValue = nullptr;
+ std::shared_ptr<vk::dbg::Value> value;
};
////////////////////////////////////////////////////////////////////////////////
// sw::SpirvShader::Impl::Debugger::State
//
// State holds the runtime data structures for the shader debug session.
+//
+// When debugging is enabled, the shader program will construct a State with a
+// call to create(), and during execution write shader information into fields
+// of this class, including:
+// * Shadow memory for keeping track of register-held values.
+// * Global variables.
+// * Last reached ::debug::Values (see LocalVariableValue)
+//
+// Bulky data that is only needed once the shader has hit a trap is held by
+// State::Data. This is lazily constructed by the first call to trap().
+//
// There is an instance of this class per shader invocation.
////////////////////////////////////////////////////////////////////////////////
class SpirvShader::Impl::Debugger::State
{
public:
- static State *create(const Debugger *debugger, const char *name);
- static void destroy(State *);
-
- State(const Debugger *debugger, const char *stackBase);
- ~State();
-
- 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);
- void createScope(const debug::Scope *);
- void setScope(debug::SourceScope *newScope);
-
- vk::dbg::VariableContainer *hovers(const debug::Scope *);
- vk::dbg::VariableContainer *localsLane(const debug::Scope *, int lane);
-
- template<typename K>
- vk::dbg::VariableContainer *group(vk::dbg::VariableContainer *vc, K key);
-
- template<typename K, typename V>
- void putVal(vk::dbg::VariableContainer *vc, K key, V value);
-
- template<typename K>
- void putPtr(vk::dbg::VariableContainer *vc, K key, void *ptr, bool interleaved, const debug::Type *type);
-
- template<typename K, typename V>
- void putRef(vk::dbg::VariableContainer *vc, K key, V *ptr);
-
- // Scopes holds pointers to the vk::dbg::Scopes for local variables, hover
- // variables and the locals indexed by SIMD lane.
- struct Scopes
+ // Globals holds a copy of the shader's builtin global variables.
+ struct Globals
{
- std::shared_ptr<vk::dbg::Scope> locals;
- std::shared_ptr<vk::dbg::Scope> hovers;
- std::array<std::shared_ptr<vk::dbg::VariableContainer>, sw::SIMD::Width> localsByLane;
+ struct Compute
+ {
+ sw::uint3 numWorkgroups;
+ sw::uint3 workgroupID;
+ sw::uint3 workgroupSize;
+ uint32_t numSubgroups;
+ uint32_t subgroupIndex;
+ sw::SIMD::uint3 globalInvocationId;
+ sw::SIMD::uint3 localInvocationId;
+ sw::SIMD::uint3 localInvocationIndex;
+ };
+ struct Fragment
+ {
+ uint32_t viewIndex;
+ sw::SIMD::vec4 fragCoord;
+ sw::SIMD::vec4 pointCoord;
+ sw::SIMD::int2 windowSpacePosition;
+ sw::SIMD::uint_t helperInvocation;
+ };
+ struct Vertex
+ {
+ uint32_t viewIndex;
+ uint32_t instanceIndex;
+ sw::SIMD::uint_t vertexIndex;
+ };
+
+ // Common for all shader types
+ uint32_t subgroupSize;
+ sw::SIMD::uint_t activeLaneMask;
+
+ // Shader type specific globals
+ union
+ {
+ Compute compute;
+ Fragment fragment;
+ Vertex vertex;
+ };
};
- // getScopes() returns the Scopes object for the given debug::Scope.
- const Scopes &getScopes(const debug::Scope *scope);
+ // create() allocates, constructs and returns a State.
+ // Called at the start of the debugger-enabled shader program.
+ static State *create(const Debugger *debugger);
- const Debugger *debugger;
- const std::shared_ptr<vk::dbg::Thread> thread;
+ // destroy() destructs and frees a state.
+ // Called at the end of the debugger-enabled shader program.
+ static void destroy(State *);
+
+ // trap() is called by the debugger-enabled shader program to suspend
+ // execution of the shader. This will appear in the attached debugger as if
+ // a breakpoint has been hit.
+ // trap() will be called if the Debugger::Traps::memory[index] is non-zero,
+ // or if alwaysTrap is non-zero.
+ // index is the index of the trap (see Debugger::Traps).
+ void trap(int index);
+
+ const Debugger *const debugger;
+
+ // traps is a simple copy of Debugger::Traps::memory.
+ // Copied here to reduce pointer chasing during shader execution.
+ uint8_t *traps = nullptr;
+
+ // alwaysTrap (if non-zero) forces a call trap() even if
+ // Debugger::Traps::memory[index] is zero. Used to perform single line
+ // stepping (pause at next line / instruction).
+ uint8_t alwaysTrap = 0;
+
+ // Global variable values. Written to at shader start.
+ Globals globals;
+
+ // Shadow memory for all SpirvShader::Objects in the executing shader
+ // program.
+ // See Debugger::Shadow for more information.
std::unique_ptr<uint8_t[]> const shadow;
- std::unordered_map<const debug::Scope *, Scopes> scopes;
- Scopes globals; // Scope for globals.
- debug::SourceScope *srcScope = nullptr; // Current source scope.
- const size_t initialThreadDepth = 0;
-};
-
-SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger, const char *name)
-{
- return new State(debugger, name);
-}
-
-void SpirvShader::Impl::Debugger::State::destroy(State *state)
-{
- delete state;
-}
-
-SpirvShader::Impl::Debugger::State::State(const Debugger *debugger, const char *stackBase)
- : debugger(debugger)
- , thread(debugger->ctx->lock().currentThread())
- , shadow(new uint8_t[debugger->shadow.size])
- , initialThreadDepth(thread->depth())
-{
- enter(stackBase);
-
- thread->update(true, [&](vk::dbg::Frame &frame) {
- globals.locals = frame.locals;
- globals.hovers = frame.hovers;
- for(int i = 0; i < sw::SIMD::Width; i++)
- {
- auto locals = std::make_shared<vk::dbg::VariableContainer>();
- frame.locals->variables->put(laneNames[i], std::make_shared<vk::dbg::Struct>("", locals));
- globals.localsByLane[i] = locals;
- }
- });
-}
-
-SpirvShader::Impl::Debugger::State::~State()
-{
- for(auto depth = thread->depth(); depth > initialThreadDepth; depth--)
- {
- exit();
- }
-}
-
-void SpirvShader::Impl::Debugger::State::enter(const char *name)
-{
- thread->enter(debugger->spirvFile, name);
-}
-
-void SpirvShader::Impl::Debugger::State::exit()
-{
- thread->exit();
-}
-
-void SpirvShader::Impl::Debugger::State::updateActiveLaneMask(int lane, bool enabled)
-{
- globals.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
-}
-
-void SpirvShader::Impl::Debugger::State::updateLocation(bool isStep, vk::dbg::File::ID fileID, int line, int column)
-{
- auto file = debugger->ctx->lock().get(fileID);
- thread->update(isStep, [&](vk::dbg::Frame &frame) {
- frame.location = { file, line, column };
- });
-}
-
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers(const debug::Scope *scope)
-{
- return getScopes(scope).hovers->variables.get();
-}
-
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(const debug::Scope *scope, int i)
-{
- return getScopes(scope).localsByLane[i].get();
-}
-
-template<typename K>
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::group(vk::dbg::VariableContainer *vc, K key)
-{
- auto out = std::make_shared<vk::dbg::VariableContainer>();
- vc->put(tostring(key), std::make_shared<vk::dbg::Struct>("", out));
- return out.get();
-}
-
-template<typename K, typename V>
-void SpirvShader::Impl::Debugger::State::putVal(vk::dbg::VariableContainer *vc, K key, V value)
-{
- vc->put(tostring(key), vk::dbg::make_constant(value));
-}
-
-template<typename K>
-void SpirvShader::Impl::Debugger::State::putPtr(vk::dbg::VariableContainer *vc, K key, void *ptr, bool interleaved, const debug::Type *type)
-{
- vc->put(tostring(key), type->value(ptr, interleaved));
-}
-
-template<typename K, typename V>
-void SpirvShader::Impl::Debugger::State::putRef(vk::dbg::VariableContainer *vc, K key, V *ptr)
-{
- vc->put(tostring(key), vk::dbg::make_reference(*ptr));
-}
-
-void SpirvShader::Impl::Debugger::State::createScope(const debug::Scope *spirvScope)
-{
- // TODO(b/151338669): We're creating scopes per-shader invocation.
- // This is all really static information, and should only be created
- // once *per program*.
-
- ASSERT(spirvScope != nullptr);
-
- auto lock = debugger->ctx->lock();
- Scopes s = {};
- s.locals = lock.createScope(spirvScope->source->dbgFile);
- s.hovers = lock.createScope(spirvScope->source->dbgFile);
-
- for(int i = 0; i < sw::SIMD::Width; i++)
- {
- auto locals = std::make_shared<vk::dbg::VariableContainer>();
- s.localsByLane[i] = locals;
- s.locals->variables->put(laneNames[i], std::make_shared<vk::dbg::Struct>("", locals));
- }
-
- if(hasDebuggerScope(spirvScope->parent))
- {
- auto parent = getScopes(spirvScope->parent);
- for(int i = 0; i < sw::SIMD::Width; i++)
- {
- s.localsByLane[i]->extend(parent.localsByLane[i]);
- }
- s.hovers->variables->extend(parent.hovers->variables);
- }
- else
- {
- // Scope has no parent. Ensure the globals are inherited for this stack
- // frame.
- //
- // Note: We're combining globals with locals as DAP doesn't have a
- // 'globals' enum value for Scope::presentationHint.
- // TODO(bclayton): We should probably keep globals separate from locals
- // and combine them at the server interface. That way we can easily
- // provide globals if DAP later supports it as a Scope::presentationHint
- // type.
- for(int i = 0; i < sw::SIMD::Width; i++)
- {
- s.localsByLane[i]->extend(globals.localsByLane[i]);
- }
- }
-
- scopes.emplace(spirvScope, std::move(s));
-}
-
-void SpirvShader::Impl::Debugger::State::setScope(debug::SourceScope *newSrcScope)
-{
- auto oldSrcScope = srcScope;
- if(oldSrcScope == newSrcScope) { return; }
- srcScope = newSrcScope;
-
- if(hasDebuggerScope(srcScope->scope))
- {
- 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;
-
- if(oldFunction != newFunction)
- {
- if(oldFunction) { thread->exit(); }
- if(newFunction) { thread->enter(newFunction->source->dbgFile, newFunction->name); }
- }
-
- auto dbgScope = getScopes(srcScope->scope);
- thread->update(true, [&](vk::dbg::Frame &frame) {
- frame.locals = dbgScope.locals;
- frame.hovers = dbgScope.hovers;
- });
- }
-}
-
-const SpirvShader::Impl::Debugger::State::Scopes &SpirvShader::Impl::Debugger::State::getScopes(const debug::Scope *scope)
-{
- if(scope == &debug::Scope::Global)
- {
- return globals;
- }
-
- auto dbgScopeIt = scopes.find(scope);
- ASSERT_MSG(dbgScopeIt != scopes.end(),
- "createScope() not called for debug::Scope %s %p",
- cstr(scope->kind), scope);
- return dbgScopeIt->second;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// sw::SpirvShader::Impl::Debugger::Group
-//
-// This provides a convenient C++ interface for adding debugger values to
-// VariableContainers.
-////////////////////////////////////////////////////////////////////////////////
-class SpirvShader::Impl::Debugger::Group
-{
-public:
- using Ptr = rr::Pointer<rr::Byte>;
-
- static Group hovers(Ptr state, const debug::Scope *scope);
- static Group locals(Ptr state, const debug::Scope *scope);
- static Group localsLane(Ptr state, const debug::Scope *scope, int lane);
- static Group globals(Ptr state, int lane);
-
- inline Group();
- inline Group(Ptr state, Ptr group);
-
- inline bool isValid() const;
-
- template<typename K, typename RK>
- Group group(RK key) const;
-
- template<typename K, typename V, typename RK, typename RV>
- void put(RK key, RV value) const;
-
- template<typename K, typename RK>
- void putPtr(RK key, RValue<Pointer<Byte>> ptr, bool interleaved, const debug::Type *type) const;
-
- template<typename K, typename V, typename RK>
- void putRef(RK key, RValue<Pointer<Byte>> ref) const;
-
- template<typename K, typename V, typename RK, typename RV>
- void put(RK key, RV x, RV y) const;
-
- template<typename K, typename V, typename RK, typename RV>
- void put(RK key, RV x, RV y, RV z) const;
-
- template<typename K, typename V, typename RK, typename RV>
- void put(RK key, RV x, RV y, RV z, RV w) const;
-
- template<typename K, typename V, typename VEC>
- void putVec3(K key, const VEC &v) const;
+ // Array of last reached debug::Value.
+ // Indexed by ::debug::LocalVariable::ValueNode::debugValueIndex.
+ std::unique_ptr<debug::Value *[]> const lastReachedDebugValues;
private:
- Ptr state;
- Ptr ptr;
- bool valid = false;
+ // Data holds the debugger-interface state (vk::dbg::*).
+ // This is only constructed on the first call to Debugger::State::trap() as
+ // it contains data that is only needed when the debugger is actively
+ // inspecting execution of the shader program.
+ struct Data
+ {
+ Data(State *state);
+
+ // terminate() is called at the end of execution of the shader program.
+ // terminate() ensures that the debugger thread stack is at the same
+ // level as when the program entered.
+ void terminate(State *state);
+
+ // trap() updates the debugger thread with the stack frames and
+ // variables at the trap's scoped location.
+ // trap() will notify the debugger that the thread has paused, and will
+ // block until instructed to resume (either continue or step) by the
+ // user.
+ void trap(int index, State *state);
+
+ private:
+ using PerLaneVariables = std::array<std::shared_ptr<vk::dbg::VariableContainer>, sw::SIMD::Width>;
+
+ struct StackEntry
+ {
+ debug::LexicalBlock *block;
+ uint32_t line;
+
+ bool operator!=(const StackEntry &other) const { return block != other.block || line != other.line; }
+ };
+
+ struct GlobalVariables
+ {
+ std::shared_ptr<vk::dbg::VariableContainer> common;
+ PerLaneVariables lanes;
+ };
+
+ // updateFrameLocals() updates the local variables in the frame with
+ // those in the lexical block.
+ void updateFrameLocals(State *state, vk::dbg::Frame &frame, debug::LexicalBlock *block);
+
+ // getOrCreateLocals() creates and returns the per-lane local variables
+ // from those in the lexical block.
+ PerLaneVariables getOrCreateLocals(State *state, debug::LexicalBlock const *block);
+
+ // buildGlobal() creates and adds to globals global variable with the
+ // given name and value. The value is copied instead of holding a
+ // pointer to val.
+ template<typename T>
+ void buildGlobal(const char *name, const T &val);
+ template<typename T, int N>
+ void buildGlobal(const char *name, const sw::SIMD::PerLane<T, N> &vec);
+
+ // buildGlobals() builds all the global variable values, populating
+ // globals.
+ void buildGlobals(State *state);
+
+ // buildSpirvVariables() builds a Struct holding all the SPIR-V named
+ // values for the given lane.
+ std::shared_ptr<vk::dbg::Struct> buildSpirvVariables(State *state, int lane) const;
+
+ // buildSpirvValue() returns a debugger value for the SPIR-V shadow
+ // value at memory of the given type and for the given lane.
+ std::shared_ptr<vk::dbg::Value> buildSpirvValue(State *state, Shadow::Memory memory, const SpirvShader::Type &type, int lane) const;
+
+ GlobalVariables globals;
+ std::shared_ptr<vk::dbg::Thread> thread;
+ std::vector<StackEntry> stack;
+ std::unordered_map<debug::LexicalBlock const *, PerLaneVariables> locals;
+ };
+
+ State(const Debugger *debugger);
+ ~State();
+ std::unique_ptr<Data> data;
};
-SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::hovers(Ptr state, const debug::Scope *scope)
-{
- return Group(state, rr::Call(&State::hovers, state, scope));
-}
-
-SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, const debug::Scope *scope, int lane)
-{
- return Group(state, rr::Call(&State::localsLane, state, scope, lane));
-}
-
-SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::globals(Ptr state, int lane)
-{
- return localsLane(state, &debug::Scope::Global, lane);
-}
-
-SpirvShader::Impl::Debugger::Group::Group() {}
-
-SpirvShader::Impl::Debugger::Group::Group(Ptr state, Ptr group)
- : state(state)
- , ptr(group)
- , valid(true)
-{}
-
-bool SpirvShader::Impl::Debugger::Group::isValid() const
-{
- return valid;
-}
-
-template<typename K, typename RK>
-SpirvShader::Impl::Debugger::Group SpirvShader::Impl::Debugger::Group::group(RK key) const
-{
- ASSERT(isValid());
- return Group(state, rr::Call(&State::group<K>, state, ptr, key));
-}
-
-template<typename K, typename V, typename RK, typename RV>
-void SpirvShader::Impl::Debugger::Group::put(RK key, RV value) const
-{
- ASSERT(isValid());
- rr::Call(&State::putVal<K, V>, state, ptr, key, value);
-}
-
-template<typename K, typename RK>
-void SpirvShader::Impl::Debugger::Group::putPtr(RK key, RValue<Pointer<Byte>> addr, bool interleaved, const debug::Type *type) const
-{
- ASSERT(isValid());
- rr::Call(&State::putPtr<K>, state, ptr, key, addr, interleaved, type);
-}
-
-template<typename K, typename V, typename RK>
-void SpirvShader::Impl::Debugger::Group::putRef(RK key, RValue<Pointer<Byte>> ref) const
-{
- ASSERT(isValid());
- rr::Call(&State::putRef<K, V>, state, ptr, key, ref);
-}
-
-template<typename K, typename V, typename RK, typename RV>
-void SpirvShader::Impl::Debugger::Group::put(RK key, RV x, RV y) const
-{
- auto vec = group<K>(key);
- vec.template put<const char *, V>("x", x);
- vec.template put<const char *, V>("y", y);
-}
-
-template<typename K, typename V, typename RK, typename RV>
-void SpirvShader::Impl::Debugger::Group::put(RK key, RV x, RV y, RV z) const
-{
- auto vec = group<K>(key);
- vec.template put<const char *, V>("x", x);
- vec.template put<const char *, V>("y", y);
- vec.template put<const char *, V>("z", z);
-}
-
-template<typename K, typename V, typename RK, typename RV>
-void SpirvShader::Impl::Debugger::Group::put(RK key, RV x, RV y, RV z, RV w) const
-{
- auto vec = group<K>(key);
- vec.template put<const char *, V>("x", x);
- vec.template put<const char *, V>("y", y);
- vec.template put<const char *, V>("z", z);
- vec.template put<const char *, V>("w", w);
-}
-
-template<typename K, typename V, typename VEC>
-void SpirvShader::Impl::Debugger::Group::putVec3(K key, const VEC &v) const
-{
- auto vec = group<K>(key);
- vec.template put<const char *, V>("x", Extract(v, 0));
- vec.template put<const char *, V>("y", Extract(v, 1));
- vec.template put<const char *, V>("z", Extract(v, 2));
-}
-
////////////////////////////////////////////////////////////////////////////////
// sw::SpirvShader::Impl::Debugger methods
////////////////////////////////////////////////////////////////////////////////
+SpirvShader::Impl::Debugger::Debugger(const SpirvShader *shader, const std::shared_ptr<vk::dbg::Context> &ctx)
+ : shader(shader)
+ , ctx(ctx)
+{
+}
+
+SpirvShader::Impl::Debugger::~Debugger()
+{
+ ctx->removeListener(this);
+}
+
+void SpirvShader::Impl::Debugger::finalize()
+{
+ ASSERT(traps.byIndex.size() == traps.byLocationAndScope.size());
+ traps.memory = std::make_unique<uint8_t[]>(traps.byIndex.size());
+
+ ctx->addListener(this);
+
+ // Register existing breakpoints.
+ onBreakpointsChanged();
+}
+
+void sw::SpirvShader::Impl::Debugger::setNextSetLocationIsSteppable()
+{
+ nextSetLocationIsSteppable = true;
+}
+
+void SpirvShader::Impl::Debugger::setScope(debug::SourceScope *scope)
+{
+ lastSetScope = scope;
+}
+
+void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &file, int line)
+{
+ vk::dbg::Location location{ file, line };
+
+ if(location != lastSetLocation)
+ {
+ // If the location has changed, then this is always a step.
+ nextSetLocationIsSteppable = true;
+ lastSetLocation = location;
+ }
+
+ if(nextSetLocationIsSteppable)
+ {
+ // Get or create the trap for the given location and scope.
+ LocationAndScope locationAndScope{ location, lastSetScope };
+ int index = getOrCreate(traps.byLocationAndScope, locationAndScope, [&] {
+ traps.byIndex.emplace_back(locationAndScope);
+ return traps.byIndex.size() - 1;
+ });
+
+ // Also create a map index for the given scope's function so we can
+ // break on function entry.
+ if(lastSetScope)
+ {
+ if(auto func = debug::find<debug::Function>(lastSetScope->scope))
+ {
+ getOrCreate(traps.byFunctionName, func->name, [&] { return index; });
+ }
+ }
+
+ // Emit the shader logic to test the trap value (either through via
+ // Debugger::State::traps[] or Debugger::State::alwaysTrap), and call
+ // Debugger::State::trap() if either are true.
+ auto dbgState = state->routine->dbgState;
+ auto alwaysTrap = *Pointer<Byte>(dbgState + OFFSET(Impl::Debugger::State, alwaysTrap));
+ auto traps = *Pointer<Pointer<Byte>>(dbgState + OFFSET(Impl::Debugger::State, traps));
+ auto trap = Pointer<Byte>(traps)[index];
+ If(alwaysTrap != Byte(0) || trap != Byte(0))
+ {
+ rr::Call(&State::trap, state->routine->dbgState, index);
+ }
+ nextSetLocationIsSteppable = false;
+ }
+}
+
+void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const char *path, int line)
+{
+ auto lock = ctx->lock();
+ auto file = lock.findFile(path);
+ if(!file)
+ {
+ file = lock.createPhysicalFile(path);
+ }
+ setLocation(state, file, line);
+}
+
+void SpirvShader::Impl::Debugger::onSetBreakpoint(const vk::dbg::Location &location, bool &handled)
+{
+ // Notify the debugger if the breakpoint location is handled.
+ // We don't actually set the trap here as this is performed by
+ // onBreakpointsChanged(), which is only called once, even for multiple
+ // breakpoint changes.
+ for(auto it : traps.byLocationAndScope)
+ {
+ if(location == it.first.location)
+ {
+ handled = true;
+ return;
+ }
+ }
+}
+
+void SpirvShader::Impl::Debugger::onSetBreakpoint(const std::string &func, bool &handled)
+{
+ // Notify the debugger if the function-entry breakpoint is handled.
+ // We don't actually set the trap here as this is performed by
+ // onBreakpointsChanged(), which is only called once, even for multiple
+ // breakpoint changes.
+ auto it = traps.byFunctionName.find(func);
+ if(it != traps.byFunctionName.end())
+ {
+ handled = true;
+ }
+
+ if(isEntryBreakpointForShaderType(shader->executionModel, func))
+ {
+ handled = true;
+ }
+}
+
+void SpirvShader::Impl::Debugger::onBreakpointsChanged()
+{
+ // TODO(b/145351270): TSAN will probably moan that traps.memory is being
+ // modified while being read on othe threads. We can solve this by adding
+ // a shared mutex (RWMutex) for the traps, read-locking for execution, and
+ // write locking here. This will prevent setting breakpoints while a shader
+ // is executing (maybe problematic if you want to debug a slow or
+ // never-completing shader).
+ // For now, just be racy. It's unlikely that this will cause any noticable
+ // problems.
+
+ // Start by disabling all traps.
+ memset(traps.memory.get(), 0, traps.byIndex.size() * sizeof(traps.memory[0]));
+
+ // Add traps for all breakpoints by location.
+ for(auto it : files)
+ {
+ auto &file = it.second;
+ for(auto line : file->getBreakpoints())
+ {
+ for(auto it : traps.byLocationAndScope)
+ {
+ if(it.first.location == vk::dbg::Location{ file, line })
+ {
+ traps.memory[it.second] = 1;
+ }
+ }
+ }
+ }
+
+ // Add traps for all breakpoints by function name.
+ auto lock = ctx->lock();
+ for(auto it : traps.byFunctionName)
+ {
+ if(lock.isFunctionBreakpoint(it.first))
+ {
+ traps.memory[it.second] = 1;
+ }
+ }
+
+ // Add traps for breakpoints by shader type.
+ for(auto bp : lock.getFunctionBreakpoints())
+ {
+ if(isEntryBreakpointForShaderType(shader->executionModel, bp))
+ {
+ traps.memory[0] = 1;
+ }
+ }
+}
+
template<typename F, typename T>
void SpirvShader::Impl::Debugger::defineOrEmit(InsnIterator insn, Pass pass, F &&emit)
{
@@ -1254,9 +1524,8 @@
}
}
-void SpirvShader::Impl::Debugger::process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass)
+void SpirvShader::Impl::Debugger::process(const InsnIterator &insn, EmitState *state, Pass pass)
{
- auto dbg = shader->impl.debugger;
auto extInstIndex = insn.word(4);
switch(extInstIndex)
{
@@ -1273,7 +1542,7 @@
break;
case OpenCLDebugInfo100DebugTypeBasic:
defineOrEmit(insn, pass, [&](debug::BasicType *type) {
- type->name = shader->getString(insn.word(5));
+ type->name_ = shader->getString(insn.word(5));
type->size = shader->GetConstScalarInt(insn.word(6));
type->encoding = static_cast<OpenCLDebugInfo100DebugBaseTypeAttributeEncoding>(insn.word(7));
});
@@ -1281,9 +1550,17 @@
case OpenCLDebugInfo100DebugTypeArray:
defineOrEmit(insn, pass, [&](debug::ArrayType *type) {
type->base = get(debug::Type::ID(insn.word(5)));
- for(uint32_t i = 6; i < insn.wordCount(); i++)
+ type->size = shader->GetConstScalarInt(insn.word(6));
+ for(uint32_t i = 7; i < insn.wordCount(); i++)
{
- type->dimensions.emplace_back(shader->GetConstScalarInt(insn.word(i)));
+ // Decompose multi-dimentional into nested single
+ // dimensional arrays. Greatly simplifies logic.
+ auto inner = new debug::ArrayType();
+ inner->base = type->base;
+ type->size = shader->GetConstScalarInt(insn.word(i));
+ type->base = inner;
+ type->ownsBase = true;
+ type = inner;
}
});
break;
@@ -1310,7 +1587,7 @@
break;
case OpenCLDebugInfo100DebugTypeComposite:
defineOrEmit(insn, pass, [&](debug::CompositeType *type) {
- type->name = shader->getString(insn.word(5));
+ type->name_ = shader->getString(insn.word(5));
type->tag = static_cast<OpenCLDebugInfo100DebugCompositeType>(insn.word(6));
type->source = get(debug::Source::ID(insn.word(7)));
type->line = insn.word(8);
@@ -1324,7 +1601,7 @@
auto obj = get(debug::Object::ID(insn.word(i)));
if(auto member = debug::cast<debug::Member>(obj)) // Can also be Function or TypeInheritance, which we don't care about.
{
- type->members.push_back(member);
+ type->members_.push_back(member);
}
}
});
@@ -1373,12 +1650,6 @@
var->variable = isNone(insn.word(12)) ? 0 : insn.word(12);
var->flags = insn.word(13);
// static member declaration: word(14)
-
- // TODO(b/148401179): Instead of simply hiding variables that have been stripped by optimizations, show them in the debugger as `<optimized-away>`
- if(var->variable != 0)
- {
- exposeVariable(shader, var->name.c_str(), &debug::Scope::Global, var->type, var->variable, state);
- }
});
break;
case OpenCLDebugInfo100DebugFunction:
@@ -1394,8 +1665,6 @@
func->scopeLine = insn.word(13);
func->function = Function::ID(insn.word(14));
// declaration: word(13)
-
- rr::Call(&State::createScope, state->routine->dbgState, func);
});
break;
case OpenCLDebugInfo100DebugLexicalBlock:
@@ -1408,8 +1677,6 @@
{
scope->name = shader->getString(insn.word(9));
}
-
- rr::Call(&State::createScope, state->routine->dbgState, scope);
});
break;
case OpenCLDebugInfo100DebugScope:
@@ -1419,8 +1686,7 @@
{
ss->inlinedAt = get(debug::InlinedAt::ID(insn.word(6)));
}
-
- rr::Call(&State::setScope, state->routine->dbgState, ss);
+ setScope(ss);
});
break;
case OpenCLDebugInfo100DebugNoScope:
@@ -1447,6 +1713,10 @@
{
var->arg = insn.word(11);
}
+ if(auto block = debug::find<debug::LexicalBlock>(var->parent))
+ {
+ block->variables.emplace_back(var);
+ }
});
break;
case OpenCLDebugInfo100DebugDeclare:
@@ -1454,105 +1724,54 @@
decl->local = get(debug::LocalVariable::ID(insn.word(5)));
decl->variable = Object::ID(insn.word(6));
decl->expression = get(debug::Expression::ID(insn.word(7)));
- exposeVariable(
- shader,
- decl->local->name.c_str(),
- decl->local->parent,
- decl->local->type,
- decl->variable,
- state);
+
+ decl->local->declaration = decl;
+
+ ASSERT(decl->local->definition == debug::LocalVariable::Definition::Undefined);
+ decl->local->definition = debug::LocalVariable::Definition::Declaration;
});
break;
case OpenCLDebugInfo100DebugValue:
defineOrEmit(insn, pass, [&](debug::Value *value) {
value->local = get(debug::LocalVariable::ID(insn.word(5)));
- value->value = Object::ID(insn.word(6));
+ value->value = insn.word(6);
value->expression = get(debug::Expression::ID(insn.word(7)));
+
+ if(value->local->definition == debug::LocalVariable::Definition::Undefined)
+ {
+ value->local->definition = debug::LocalVariable::Definition::Values;
+ }
+ ASSERT(value->local->definition == debug::LocalVariable::Definition::Values);
+
+ auto node = &value->local->values;
for(uint32_t i = 8; i < insn.wordCount(); i++)
{
auto idx = shader->GetConstScalarInt(insn.word(i));
value->indexes.push_back(idx);
+
+ auto it = node->children.find(i);
+ if(it != node->children.end())
+ {
+ node = it->second.get();
+ }
+ else
+ {
+ auto parent = node;
+ auto child = std::make_unique<debug::LocalVariable::ValueNode>();
+ node = child.get();
+ parent->children.emplace(i, std::move(child));
+ }
}
- // DebugValue partially updates a DebugLocalVariable with an SSA
- // value. This is typically used to update a DebugLocalVariable
- // of a composite type, which holds structure member offsets
- // from a base address.
- // To handle these, we allocate shadow memory to hold a copy of
- // the entire variable in contiguous memory and have the
- // DebugLocalVariable point to this memory. Whenever we
- // encounter a DebugValue, we copy the necessary fields to the
- // shadow memory.
-
- // type of the full DebugLocalVariable.
- auto type = value->local->type;
-
- // base address of the variable.
- // Start by pointing base to the root of the shadow memory.
- // This will be offset to the variable, then the member within
- // the variable below.
- SIMD::Pointer base(*Pointer<Pointer<Byte>>(state->routine->dbgState + OFFSET(State, shadow)), shadow.size);
-
- // All variables are considered local, and therefore
- // interleaved.
- base = InterleaveByLane(base);
-
- // Have we already allocated shadow memory for this variable?
- auto it = shadow.offsets.find(value->local);
- if(it == shadow.offsets.end())
+ if(node->debugValueIndex == debug::LocalVariable::ValueNode::NoDebugValueIndex)
{
- // No shadow memory has been allocated for this local
- // variable yet.
-
- // Allocate the memory for the variable.
- auto offset = shadow.size;
- shadow.offsets.emplace(value->local, offset);
- auto size = type->sizeInBytes() * SIMD::Width;
- base += offset;
- shadow.size += size;
-
- // Expose the variable.
- auto name = value->local->name.c_str();
- auto scope = value->local->parent;
- auto offsets = base.offsets();
- foreachLane(name, scope, state, [&](int lane, const Group &group, auto &key) {
- auto ptr = base.base + Extract(offsets, lane);
- group.putPtr<const char *>(name, ptr, true, value->local->type);
- });
- }
- else
- {
- // Shadow memory already allocated for this variable.
- // Offset base to point to it.
- base += it->second;
+ node->debugValueIndex = numDebugValueSlots++;
}
- // Find the byte offset on the indexed member of the variable.
- std::queue<uint32_t> indices;
- for(auto idx : value->indexes)
- {
- indices.emplace(idx);
- }
- auto offset = type->index(std::move(indices)).second;
-
- // Update base to point to the particular member.
- base += offset;
-
- // Now copy the updated value into shadow memory representation
- // of the variable.
- // TODO(b/148401179): This assumes tight packing of all
- // components, which may not match with the debug structure
- // layout.
- auto &valObject = shader->getObject(value->value);
- auto &valType = shader->getType(valObject);
- for(auto i = 0u; i < valType.componentCount; i++)
- {
- auto val = Operand(shader, state, value->value).Int(i);
- auto dst = base + i * sizeof(uint32_t) * SIMD::Width;
- // Use RobustBufferAccess as the size as described by the
- // debug type may be smaller than the true SSA size.
- dst.Store(val, sw::OutOfBoundsBehavior::RobustBufferAccess, state->activeLaneMask());
- }
+ rr::Pointer<rr::Pointer<Byte>> lastReachedArray = *rr::Pointer<rr::Pointer<rr::Pointer<Byte>>>(
+ state->routine->dbgState + OFFSET(Impl::Debugger::State, lastReachedDebugValues));
+ rr::Pointer<rr::Pointer<Byte>> lastReached = &lastReachedArray[node->debugValueIndex];
+ *lastReached = rr::ConstantPointer(value);
});
break;
case OpenCLDebugInfo100DebugExpression:
@@ -1569,13 +1788,13 @@
if(insn.wordCount() > 6)
{
source->source = shader->getString(insn.word(6));
- auto file = dbg->ctx->lock().createVirtualFile(source->file.c_str(), source->source.c_str());
+ auto file = ctx->lock().createVirtualFile(source->file.c_str(), source->source.c_str());
source->dbgFile = file;
files.emplace(source->file.c_str(), file);
}
else
{
- auto file = dbg->ctx->lock().createPhysicalFile(source->file.c_str());
+ auto file = ctx->lock().createPhysicalFile(source->file.c_str());
source->dbgFile = file;
files.emplace(source->file.c_str(), file);
}
@@ -1612,32 +1831,6 @@
}
}
-void SpirvShader::Impl::Debugger::setNextSetLocationIsStep()
-{
- nextSetLocationIsStep = true;
-}
-
-void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &file, int line, int column)
-{
- if(line != lastSetLocationLine)
- {
- // If the line number has changed, then this is always a step.
- nextSetLocationIsStep = true;
- lastSetLocationLine = line;
- }
- rr::Call(&State::updateLocation, state->routine->dbgState, nextSetLocationIsStep, file->id, line, column);
- nextSetLocationIsStep = false;
-}
-
-void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const std::string &path, int line, int column)
-{
- auto it = files.find(path);
- if(it != files.end())
- {
- setLocation(state, it->second, line, column);
- }
-}
-
template<typename ID>
void SpirvShader::Impl::Debugger::add(ID id, std::unique_ptr<debug::Object> &&obj)
{
@@ -1681,242 +1874,594 @@
return ptr;
}
-template<typename Key, typename Func>
-void SpirvShader::Impl::Debugger::foreachLane(
- const Key &key,
- const debug::Scope *scope,
- EmitState *state,
- Func &&f) const
+////////////////////////////////////////////////////////////////////////////////
+// SpirvShader::Impl::Debugger::Shadow methods
+////////////////////////////////////////////////////////////////////////////////
+void SpirvShader::Impl::Debugger::Shadow::create(const SpirvShader *shader, const EmitState *state, Object::ID objId)
{
- auto dbgState = state->routine->dbgState;
- auto hover = Group::hovers(dbgState, scope).group<Key>(key);
- for(int lane = 0; lane < SIMD::Width; lane++)
+ ASSERT_MSG(entries.find(objId) == entries.end(),
+ "Object %%%d already has shadow memory allocated?", (int)objId.value());
+
+ Entry entry{};
+ entry.offset = size;
+
+ rr::Pointer<Byte> base = *rr::Pointer<rr::Pointer<Byte>>(state->routine->dbgState + OFFSET(Impl::Debugger::State, shadow));
+ base += entry.offset;
+
+ auto &obj = shader->getObject(objId);
+ auto &objTy = shader->getType(obj.typeId());
+ auto mask = state->activeLaneMask();
+ switch(obj.kind)
{
- f(lane, Group::localsLane(dbgState, scope, lane), key);
- f(lane, hover, laneNames[lane]);
+ case Object::Kind::Constant:
+ case Object::Kind::Intermediate:
+ {
+ size += objTy.componentCount * sizeof(uint32_t) * sw::SIMD::Width;
+ auto dst = InterleaveByLane(SIMD::Pointer(base, 0));
+ for(uint32_t i = 0u; i < objTy.componentCount; i++)
+ {
+ auto val = SpirvShader::Operand(shader, state, objId).Int(i);
+ dst.Store(val, sw::OutOfBoundsBehavior::UndefinedBehavior, mask);
+ dst += sizeof(uint32_t) * SIMD::Width;
+ }
+ entry.kind = Entry::Kind::Value;
+ break;
+ }
+ case Object::Kind::Pointer:
+ case Object::Kind::InterfaceVariable:
+ {
+ size += sizeof(void *) + sizeof(uint32_t) * SIMD::Width;
+ auto ptr = state->getPointer(objId);
+ store(base, ptr.base);
+ store(base + sizeof(void *), ptr.offsets());
+ entry.kind = Entry::Kind::Pointer;
+ break;
+ }
+ default:
+ break;
+ }
+ entries.emplace(objId, entry);
+}
+
+SpirvShader::Impl::Debugger::Shadow::Memory
+SpirvShader::Impl::Debugger::Shadow::get(const State *state, Object::ID objId, int lane) const
+{
+ auto entryIt = entries.find(objId);
+ ASSERT_MSG(entryIt != entries.end(), "Missing shadow entry for object %%%d (%s)",
+ (int)objId.value(),
+ OpcodeName(state->debugger->shader->getObject(objId).opcode()).c_str());
+ auto &entry = entryIt->second;
+ auto data = &state->shadow[entry.offset];
+ return Memory{ data };
+}
+
+SpirvShader::Impl::Debugger::Shadow::Memory::operator void *()
+{
+ return addr;
+}
+
+SpirvShader::Impl::Debugger::Shadow::Memory
+SpirvShader::Impl::Debugger::Shadow::Memory::dref(int lane) const
+{
+ auto ptr = *reinterpret_cast<Pointer *>(addr);
+ return Memory{ ptr.base + ptr.offsets[lane] };
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::LocalVariableValue methods
+////////////////////////////////////////////////////////////////////////////////
+sw::SpirvShader::Impl::Debugger::LocalVariableValue::LocalVariableValue(
+ debug::LocalVariable *variable,
+ State const *const state,
+ int lane)
+ : LocalVariableValue(std::make_shared<Shared>(variable, state, lane), variable->type, &variable->values)
+{}
+
+sw::SpirvShader::Impl::Debugger::LocalVariableValue::LocalVariableValue(
+ std::shared_ptr<const Shared> const &shared,
+ debug::Type const *ty,
+ debug::LocalVariable::ValueNode const *node)
+ : shared(shared)
+ , ty(ty)
+ , node(node)
+{
+}
+
+std::string sw::SpirvShader::Impl::Debugger::LocalVariableValue::type()
+{
+ updateValue();
+ return value->type();
+}
+
+std::string sw::SpirvShader::Impl::Debugger::LocalVariableValue::get(const vk::dbg::FormatFlags &fmt)
+{
+ updateValue();
+ return value->get(fmt);
+}
+
+std::shared_ptr<vk::dbg::Variables> sw::SpirvShader::Impl::Debugger::LocalVariableValue::children()
+{
+ updateValue();
+ return value->children();
+}
+
+void sw::SpirvShader::Impl::Debugger::LocalVariableValue::updateValue()
+{
+ // Fetch the last reached ::debug::Value for this local variable node.
+ auto newActiveValue = (node->debugValueIndex != debug::LocalVariable::ValueNode::NoDebugValueIndex)
+ ? shared->state->lastReachedDebugValues[node->debugValueIndex]
+ : nullptr;
+ auto activeValueChanged = activeValue != newActiveValue;
+ activeValue = newActiveValue;
+
+ if(activeValue && activeValueChanged)
+ { // We have a new ::debug::Value, read it.
+
+ ASSERT(activeValue->local == shared->variable); // If this isn't true, then something is very wonky.
+
+ // Update the value.
+ auto ptr = shared->state->debugger->shadow.get(shared->state, activeValue->value, shared->lane);
+ for(auto op : activeValue->expression->operations)
+ {
+ switch(op->opcode)
+ {
+ case OpenCLDebugInfo100Deref:
+ ptr = ptr.dref(shared->lane);
+ break;
+ default:
+ UNIMPLEMENTED("b/148401179 OpenCLDebugInfo100DebugOperation %d", (int)op->opcode);
+ break;
+ }
+ }
+ value = ty->value(ptr, true);
+ }
+ else if(!value || activeValueChanged)
+ { // We have no ::debug::Value. Display <undefined>
+
+ if(node->children.empty())
+ { // No children? Just have the node display <undefined>
+ value = ty->undefined();
+ }
+ else
+ { // Node has children.
+ // Display <undefined> for those that don't have sub-nodes, and
+ // create child LocalVariableValues for those that do.
+ value = vk::dbg::Struct::create(ty->name(), [&](auto &vc) {
+ auto numMembers = ty->numMembers();
+ for(size_t i = 0; i < numMembers; i++)
+ {
+ auto member = ty->getMember(i);
+
+ auto it = node->children.find(i);
+ if(it != node->children.end())
+ {
+ auto child = std::make_shared<LocalVariableValue>(shared, member.type, it->second.get());
+ vc->put(member.name, child);
+ }
+ else
+ {
+ vc->put(member.name, member.type->undefined());
+ }
+ }
+ });
+ }
}
}
-template<typename Key>
-void SpirvShader::Impl::Debugger::exposeVariable(
- const SpirvShader *shader,
- const Key &key,
- const debug::Scope *scope,
- const debug::Type *type,
- Object::ID id,
- EmitState *state) const
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::State methods
+////////////////////////////////////////////////////////////////////////////////
+SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger)
{
- foreachLane(key, scope, state, [&](int lane, const Group &group, auto &key) {
- exposeVariable(shader, group, lane, laneNames[lane], type, id, state);
- });
+ return new State(debugger);
}
-template<typename Key>
-void SpirvShader::Impl::Debugger::exposeVariable(
- const SpirvShader *shader,
- const Group &group,
- int lane,
- const Key &key,
- const debug::Type *type,
- Object::ID id,
- EmitState *state,
- int wordOffset /* = 0 */) const
+void SpirvShader::Impl::Debugger::State::destroy(State *state)
{
- auto &obj = shader->getObject(id);
+ delete state;
+}
- if(type != nullptr)
+SpirvShader::Impl::Debugger::State::State(const Debugger *debugger)
+ : debugger(debugger)
+ , traps(debugger->traps.memory.get())
+ , shadow(new uint8_t[debugger->shadow.size])
+ , lastReachedDebugValues(new debug::Value *[debugger->numDebugValueSlots])
+{
+ memset(shadow.get(), 0, debugger->shadow.size);
+ memset(lastReachedDebugValues.get(), 0, sizeof(lastReachedDebugValues[0]) * debugger->numDebugValueSlots);
+}
+
+SpirvShader::Impl::Debugger::State::~State()
+{
+ if(data) { data->terminate(this); }
+}
+
+void SpirvShader::Impl::Debugger::State::trap(int index)
+{
+ if(std::all_of(globals.activeLaneMask.data.begin(),
+ globals.activeLaneMask.data.end(),
+ [](auto v) { return v == 0; }))
{
- switch(obj.kind)
- {
- case Object::Kind::InterfaceVariable:
- case Object::Kind::Pointer:
- case Object::Kind::DescriptorSet:
- {
- ASSERT(wordOffset == 0); // TODO.
- auto ptr = shader->GetPointerToData(id, 0, state); // + sizeof(uint32_t) * wordOffset;
- auto &ptrTy = shader->getType(obj);
- auto interleaved = IsStorageInterleavedByLane(ptrTy.storageClass);
- if(interleaved)
- {
- ptr = InterleaveByLane(ptr);
- }
- auto addr = &ptr.base[Extract(ptr.offsets(), lane)];
- group.putPtr<Key>(key, addr, interleaved, type);
- }
- break;
-
- case Object::Kind::Constant:
- case Object::Kind::Intermediate:
- {
- if(auto ty = debug::cast<debug::BasicType>(type))
- {
- auto val = Operand(shader, state, id).Int(wordOffset);
- switch(ty->encoding)
- {
- case OpenCLDebugInfo100Address:
- // TODO: This function takes a SIMD vector, and pointers cannot
- // be held in them.
- break;
- case OpenCLDebugInfo100Boolean:
- group.put<Key, bool>(key, Extract(val, lane) != 0);
- break;
- case OpenCLDebugInfo100Float:
- group.put<Key, float>(key, Extract(As<SIMD::Float>(val), lane));
- break;
- case OpenCLDebugInfo100Signed:
- group.put<Key, int>(key, Extract(val, lane));
- break;
- case OpenCLDebugInfo100SignedChar:
- group.put<Key, int8_t>(key, SByte(Extract(val, lane)));
- break;
- case OpenCLDebugInfo100Unsigned:
- group.put<Key, unsigned int>(key, Extract(val, lane));
- break;
- case OpenCLDebugInfo100UnsignedChar:
- group.put<Key, uint8_t>(key, Byte(Extract(val, lane)));
- break;
- default:
- break;
- }
- }
- else if(auto ty = debug::cast<debug::VectorType>(type))
- {
- auto elWords = 1; // Currently vector elements must only be basic types, 32-bit wide
- auto elTy = ty->base;
- auto vecGroup = group.group<Key>(key);
- switch(ty->components)
- {
- case 1:
- exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
- break;
- case 2:
- exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
- exposeVariable(shader, vecGroup, lane, "y", elTy, id, state, wordOffset + 1 * elWords);
- break;
- case 3:
- exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
- exposeVariable(shader, vecGroup, lane, "y", elTy, id, state, wordOffset + 1 * elWords);
- exposeVariable(shader, vecGroup, lane, "z", elTy, id, state, wordOffset + 2 * elWords);
- break;
- case 4:
- exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
- exposeVariable(shader, vecGroup, lane, "y", elTy, id, state, wordOffset + 1 * elWords);
- exposeVariable(shader, vecGroup, lane, "z", elTy, id, state, wordOffset + 2 * elWords);
- exposeVariable(shader, vecGroup, lane, "w", elTy, id, state, wordOffset + 3 * elWords);
- break;
- default:
- for(uint32_t i = 0; i < ty->components; i++)
- {
- exposeVariable(shader, vecGroup, lane, tostring(i).c_str(), elTy, id, state, wordOffset + i * elWords);
- }
- break;
- }
- }
- else if(auto ty = debug::cast<debug::CompositeType>(type))
- {
- auto objectGroup = group.group<Key>(key);
-
- for(auto member : ty->members)
- {
- exposeVariable(shader, objectGroup, lane, member->name.c_str(), member->type, id, state, member->offset / 32);
- }
- }
- else if(auto ty = debug::cast<debug::ArrayType>(type))
- {
- ty->build(
- group.group<Key>(key),
- [&](Debugger::Group &parent, uint32_t idx) {
- return parent.template group<int>(idx);
- },
- [&](Debugger::Group &parent, uint32_t idx, uint32_t offset) {
- exposeVariable(shader, parent, lane, idx, ty->base, id, state, offset);
- });
- }
- else
- {
- UNIMPLEMENTED("b/145351270: Debug type: %s", cstr(type->kind));
- }
- return;
- }
- break;
-
- case Object::Kind::Unknown:
- UNIMPLEMENTED("b/145351270: Object kind: %d", (int)obj.kind);
- }
+ // Don't trap if no lanes are active.
+ // Ideally, we would be simply jumping over blocks that have no active
+ // lanes, but this is complicated due to ensuring that all reactor
+ // RValues dominate their usage blocks.
return;
}
- // No debug type information. Derive from SPIR-V.
- switch(shader->getType(obj).opcode())
+ if(!data)
{
- case spv::OpTypeInt:
- {
- Operand val(shader, state, id);
- group.put<Key, int>(key, Extract(val.Int(0), lane));
- }
- break;
- case spv::OpTypeFloat:
- {
- Operand val(shader, state, id);
- group.put<Key, float>(key, Extract(val.Float(0), lane));
- }
- break;
- case spv::OpTypeVector:
- {
- Operand val(shader, state, id);
- auto count = shader->getType(obj).definition.word(3);
- switch(count)
+ data = std::make_unique<Data>(this);
+ }
+ data->trap(index, this);
+}
+
+SpirvShader::Impl::Debugger::State::Data::Data(State *state)
+{
+ buildGlobals(state);
+
+ thread = state->debugger->ctx->lock().currentThread();
+
+ if(!state->debugger->shaderHasDebugInfo)
+ {
+ // Enter the stack frame entry for the SPIR-V.
+ thread->enter(state->debugger->spirvFile, "SPIR-V", [&](vk::dbg::Frame &frame) {
+ for(size_t lane = 0; lane < sw::SIMD::Width; lane++)
{
- case 1:
- group.put<Key, float>(key, Extract(val.Float(0), lane));
- break;
- case 2:
- group.put<Key, float>(key, Extract(val.Float(0), lane), Extract(val.Float(1), lane));
- break;
- case 3:
- group.put<Key, float>(key, Extract(val.Float(0), lane), Extract(val.Float(1), lane), Extract(val.Float(2), lane));
- break;
- case 4:
- group.put<Key, float>(key, Extract(val.Float(0), lane), Extract(val.Float(1), lane), Extract(val.Float(2), lane), Extract(val.Float(3), lane));
- break;
- default:
+ auto laneLocals = std::make_shared<vk::dbg::Struct>("Lane", globals.lanes[lane]);
+ frame.locals->variables->put(laneName(lane), laneLocals);
+ frame.hovers->variables->extend(std::make_shared<HoversFromLocals>(frame.locals->variables));
+ }
+ });
+ }
+}
+
+void SpirvShader::Impl::Debugger::State::Data::terminate(State *state)
+{
+ if(state->debugger->shaderHasDebugInfo)
+ {
+ for(size_t i = 0; i < stack.size(); i++)
+ {
+ thread->exit();
+ }
+ }
+ else
+ {
+ thread->exit();
+ }
+}
+
+void SpirvShader::Impl::Debugger::State::Data::trap(int index, State *state)
+{
+ auto debugger = state->debugger;
+
+ // Update the thread frames from the stack of scopes
+ auto const &locationAndScope = debugger->traps.byIndex[index];
+
+ if(locationAndScope.scope)
+ {
+ // Gather the new stack as LexicalBlocks.
+ std::vector<StackEntry> newStack;
+ if(auto block = debug::find<debug::LexicalBlock>(locationAndScope.scope->scope))
+ {
+ newStack.emplace_back(StackEntry{ block, block->line });
+ }
+ for(auto inlined = locationAndScope.scope->inlinedAt; inlined != nullptr; inlined = inlined->inlined)
+ {
+ if(auto block = debug::find<debug::LexicalBlock>(inlined->scope))
+ {
+ newStack.emplace_back(StackEntry{ block, inlined->line });
+ }
+ }
+ std::reverse(newStack.begin(), newStack.end());
+
+ // shrink pop stack frames until stack length is at most maxLen.
+ auto shrink = [&](size_t maxLen) {
+ while(stack.size() > maxLen)
+ {
+ thread->exit();
+ stack.pop_back();
+ }
+ };
+
+ // Pop stack frames until stack length is at most newStack length.
+ shrink(newStack.size());
+
+ // Find first deviation in stack frames, and shrink to that point.
+ // Special care is taken for deviation in just the top most frame so we
+ // don't end up reconstructing the top most stack frame every scope
+ // change.
+ for(size_t i = 0; i < stack.size(); i++)
+ {
+ if(stack[i] != newStack[i])
+ {
+ bool isTopMostFrame = i == (newStack.size() - 1);
+ auto oldFunction = debug::find<debug::Function>(stack[i].block);
+ auto newFunction = debug::find<debug::Function>(newStack[i].block);
+ if(isTopMostFrame && oldFunction == newFunction)
{
- auto vec = group.group<Key>(key);
- for(uint32_t i = 0; i < count; i++)
- {
- vec.template put<int, float>(i, Extract(val.Float(i), lane));
- }
+ // Deviation is just a movement in the top most frame's
+ // function.
+ // Don't exit() and enter() for the same function - it'll
+ // be treated as a step out and step in, breaking stepping
+ // commands. Instead, just update the frame variables for
+ // the new scope.
+ stack[i].block = newStack[i].block;
+ thread->update(true, [&](vk::dbg::Frame &frame) {
+ updateFrameLocals(state, frame, stack[i].block);
+ });
+ }
+ else
+ {
+ shrink(i);
}
break;
}
}
- break;
- case spv::OpTypePointer:
+
+ // Now rebuild the parts of stack frames that are new
+ for(size_t i = stack.size(); i < newStack.size(); i++)
{
- auto objectTy = shader->getType(shader->getObject(id));
- bool interleavedByLane = IsStorageInterleavedByLane(objectTy.storageClass);
- auto ptr = state->getPointer(id);
- auto ptrGroup = group.group<Key>(key);
- shader->VisitMemoryObject(id, [&](const MemoryElement &el) {
- auto p = ptr + el.offset;
- if(interleavedByLane) { p = InterleaveByLane(p); } // TODO: Interleave once, then add offset?
- auto simd = p.Load<SIMD::Float>(sw::OutOfBoundsBehavior::Nullify, state->activeLaneMask());
- ptrGroup.template put<int, float>(el.index, Extract(simd, lane));
+ auto entry = newStack[i];
+ stack.emplace_back(entry);
+ auto function = debug::find<debug::Function>(entry.block);
+ thread->enter(entry.block->source->dbgFile, function->name, [&](vk::dbg::Frame &frame) {
+ frame.location = vk::dbg::Location{ function->source->dbgFile, (int)entry.line };
+ frame.hovers->variables->extend(std::make_shared<HoversFromLocals>(frame.locals->variables));
+ updateFrameLocals(state, frame, entry.block);
});
}
- break;
+ }
+
+ // If the debugger thread is running, notify that we're pausing due to the
+ // trap.
+ if(thread->state() == vk::dbg::Thread::State::Running)
+ {
+ // pause() changes the thread state Paused, and will cause the next
+ // frame location changing call update() to block until the debugger
+ // instructs the thread to resume or step.
+ thread->pause();
+ debugger->ctx->serverEventBroadcast()->onLineBreakpointHit(thread->id);
+ }
+
+ // Update the frame location. This will likely block until the debugger
+ // instructs the thread to resume or step.
+ thread->update(true, [&](vk::dbg::Frame &frame) {
+ frame.location = locationAndScope.location;
+ });
+
+ // Clear the alwaysTrap state if the debugger instructed the thread to
+ // resume, or set it if we're single line stepping (so we can keep track of
+ // location).
+ state->alwaysTrap = thread->state() != vk::dbg::Thread::State::Running;
+}
+
+void SpirvShader::Impl::Debugger::State::Data::updateFrameLocals(State *state, vk::dbg::Frame &frame, debug::LexicalBlock *block)
+{
+ auto locals = getOrCreateLocals(state, block);
+ for(size_t lane = 0; lane < sw::SIMD::Width; lane++)
+ {
+ auto laneLocals = std::make_shared<vk::dbg::Struct>("Lane", locals[lane]);
+ frame.locals->variables->put(laneName(lane), laneLocals);
+ }
+}
+
+SpirvShader::Impl::Debugger::State::Data::PerLaneVariables
+SpirvShader::Impl::Debugger::State::Data::getOrCreateLocals(State *state, debug::LexicalBlock const *block)
+{
+ return getOrCreate(locals, block, [&] {
+ PerLaneVariables locals;
+ for(int lane = 0; lane < sw::SIMD::Width; lane++)
+ {
+ auto vc = std::make_shared<vk::dbg::VariableContainer>();
+
+ for(auto var : block->variables)
+ {
+ auto name = var->name;
+
+ switch(var->definition)
+ {
+ case debug::LocalVariable::Definition::Undefined:
+ {
+ vc->put(name, var->type->undefined());
+ break;
+ }
+ case debug::LocalVariable::Definition::Declaration:
+ {
+ auto data = state->debugger->shadow.get(state, var->declaration->variable, lane);
+ vc->put(name, var->type->value(data.dref(lane), true));
+ break;
+ }
+ case debug::LocalVariable::Definition::Values:
+ {
+ vc->put(name, std::make_shared<LocalVariableValue>(var, state, lane));
+ break;
+ }
+ }
+ }
+
+ locals[lane] = std::move(vc);
+ }
+ if(auto parent = debug::find<debug::LexicalBlock>(block->parent))
+ {
+ auto extend = getOrCreateLocals(state, parent);
+ for(int lane = 0; lane < sw::SIMD::Width; lane++)
+ {
+ locals[lane]->extend(extend[lane]);
+ }
+ }
+ else
+ {
+ for(int lane = 0; lane < sw::SIMD::Width; lane++)
+ {
+ locals[lane]->extend(globals.lanes[lane]);
+ }
+ }
+ return locals;
+ });
+}
+
+template<typename T>
+void SpirvShader::Impl::Debugger::State::Data::buildGlobal(const char *name, const T &val)
+{
+ globals.common->put(name, makeDbgValue(val));
+}
+
+template<typename T, int N>
+void SpirvShader::Impl::Debugger::State::Data::buildGlobal(const char *name, const sw::SIMD::PerLane<T, N> &simd)
+{
+ for(int lane = 0; lane < sw::SIMD::Width; lane++)
+ {
+ for(int i = 0; i < N; i++)
+ {
+ globals.lanes[lane]->put(name, makeDbgValue(simd[lane]));
+ }
+ }
+}
+
+void SpirvShader::Impl::Debugger::State::Data::buildGlobals(State *state)
+{
+ globals.common = std::make_shared<vk::dbg::VariableContainer>();
+ globals.common->put("subgroupSize", vk::dbg::make_reference(state->globals.subgroupSize));
+
+ for(int lane = 0; lane < sw::SIMD::Width; lane++)
+ {
+ auto vc = std::make_shared<vk::dbg::VariableContainer>();
+
+ vc->put("enabled", vk::dbg::make_reference(reinterpret_cast<const bool &>(state->globals.activeLaneMask[lane])));
+
+ for(auto &it : state->debugger->objects)
+ {
+ if(auto var = debug::cast<debug::GlobalVariable>(it.second.get()))
+ {
+ if(var->variable != 0)
+ {
+ auto data = state->debugger->shadow.get(state, var->variable, lane);
+ vc->put(var->name, var->type->value(data.dref(lane), true));
+ }
+ }
+ }
+
+ auto spirv = buildSpirvVariables(state, lane);
+ if(state->debugger->shaderHasDebugInfo)
+ {
+ vc->put("SPIR-V", spirv);
+ }
+ else
+ {
+ vc->extend(spirv->children());
+ }
+
+ vc->extend(globals.common);
+ globals.lanes[lane] = vc;
+ }
+
+ switch(state->debugger->shader->executionModel)
+ {
+ case spv::ExecutionModelGLCompute:
+ {
+ buildGlobal("numWorkgroups", state->globals.compute.numWorkgroups);
+ buildGlobal("workgroupID", state->globals.compute.workgroupID);
+ buildGlobal("workgroupSize", state->globals.compute.workgroupSize);
+ buildGlobal("numSubgroups", state->globals.compute.numSubgroups);
+ buildGlobal("subgroupIndex", state->globals.compute.subgroupIndex);
+ buildGlobal("globalInvocationId", state->globals.compute.globalInvocationId);
+ buildGlobal("localInvocationIndex", state->globals.compute.localInvocationIndex);
+ break;
+ }
+ case spv::ExecutionModelFragment:
+ {
+ buildGlobal("viewIndex", state->globals.fragment.viewIndex);
+ buildGlobal("fragCoord", state->globals.fragment.fragCoord);
+ buildGlobal("pointCoord", state->globals.fragment.pointCoord);
+ buildGlobal("windowSpacePosition", state->globals.fragment.windowSpacePosition);
+ buildGlobal("helperInvocation", state->globals.fragment.helperInvocation);
+ break;
+ }
+ case spv::ExecutionModelVertex:
+ {
+ buildGlobal("viewIndex", state->globals.vertex.viewIndex);
+ buildGlobal("instanceIndex", state->globals.vertex.instanceIndex);
+ buildGlobal("vertexIndex", state->globals.vertex.vertexIndex);
+ break;
+ }
default:
break;
}
}
-////////////////////////////////////////////////////////////////////////////////
-// sw::SpirvShader
-////////////////////////////////////////////////////////////////////////////////
-void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &dbgctx)
+std::shared_ptr<vk::dbg::Struct>
+SpirvShader::Impl::Debugger::State::Data::buildSpirvVariables(State *state, int lane) const
{
- impl.debugger = new Impl::Debugger();
- impl.debugger->ctx = dbgctx;
+ return vk::dbg::Struct::create("SPIR-V", [&](auto &vc) {
+ auto debugger = state->debugger;
+ auto &entries = debugger->shadow.entries;
+ std::vector<Object::ID> ids;
+ ids.reserve(entries.size());
+ for(auto it : entries)
+ {
+ ids.emplace_back(it.first);
+ }
+ std::sort(ids.begin(), ids.end());
+ for(auto id : ids)
+ {
+ auto &obj = debugger->shader->getObject(id);
+ auto &objTy = debugger->shader->getType(obj.typeId());
+ auto name = "%" + std::to_string(id.value());
+ auto memory = debugger->shadow.get(state, id, lane);
+ switch(obj.kind)
+ {
+ case Object::Kind::Intermediate:
+ case Object::Kind::Constant:
+ if(auto val = buildSpirvValue(state, memory, objTy, lane))
+ {
+ vc->put(name, val);
+ }
+ break;
+ default:
+ break; // Not handled yet.
+ }
+ }
+ });
+}
+
+std::shared_ptr<vk::dbg::Value>
+SpirvShader::Impl::Debugger::State::Data::buildSpirvValue(State *state, Shadow::Memory memory, const SpirvShader::Type &type, int lane) const
+{
+ auto debugger = state->debugger;
+ auto shader = debugger->shader;
+
+ switch(type.definition.opcode())
+ {
+ case spv::OpTypeInt:
+ return vk::dbg::make_reference(reinterpret_cast<uint32_t *>(memory.addr)[lane]);
+ case spv::OpTypeFloat:
+ return vk::dbg::make_reference(reinterpret_cast<float *>(memory.addr)[lane]);
+ case spv::OpTypeVector:
+ {
+ auto elTy = shader->getType(type.element);
+ return vk::dbg::Struct::create("vector", [&](auto &fields) {
+ for(uint32_t i = 0; i < type.componentCount; i++)
+ {
+ if(auto val = buildSpirvValue(state, memory, elTy, lane))
+ {
+ fields->put(vecElementName(i, type.componentCount), val);
+ memory.addr += sizeof(uint32_t) * sw::SIMD::Width;
+ }
+ }
+ });
+ }
+ default:
+ return nullptr; // Not handled yet
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader methods
+////////////////////////////////////////////////////////////////////////////////
+void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &ctx)
+{
+ impl.debugger = new Impl::Debugger(this, ctx);
}
void SpirvShader::dbgTerm()
@@ -1957,7 +2502,7 @@
default: name = "SPIR-V Shader"; break;
}
static std::atomic<int> id = { 0 };
- name += tostring(id++) + ".spvasm";
+ name += std::to_string(id++) + ".spvasm";
dbg->spirvFile = dbg->ctx->lock().createVirtualFile(name.c_str(), source.c_str());
}
@@ -1966,19 +2511,11 @@
auto dbg = impl.debugger;
if(!dbg) { return; }
- using Group = Impl::Debugger::Group;
+ dbg->shaderHasDebugInfo = extensionsImported.count(Extension::OpenCLDebugInfo100) > 0;
auto routine = state->routine;
- auto type = "SPIR-V";
- switch(executionModel)
- {
- case spv::ExecutionModelVertex: type = "VertexShader"; break;
- case spv::ExecutionModelFragment: type = "FragmentShader"; break;
- case spv::ExecutionModelGLCompute: type = "ComputeShader"; break;
- default: type = "SPIR-V Shader"; break;
- }
- auto dbgState = rr::Call(&Impl::Debugger::State::create, dbg, type);
+ auto dbgState = rr::Call(&Impl::Debugger::State::create, dbg);
routine->dbgState = dbgState;
@@ -1986,52 +2523,43 @@
for(int i = 0; i < SIMD::Width; i++)
{
- auto globals = Group::globals(dbgState, i);
- globals.put<const char *, int>("subgroupSize", routine->invocationsPerSubgroup);
+ using Globals = Impl::Debugger::State::Globals;
+
+ auto globals = dbgState + OFFSET(Impl::Debugger::State, globals);
+ store(globals + OFFSET(Globals, subgroupSize), routine->invocationsPerSubgroup);
switch(executionModel)
{
case spv::ExecutionModelGLCompute:
- globals.putVec3<const char *, int>("numWorkgroups", routine->numWorkgroups);
- globals.putVec3<const char *, int>("workgroupID", routine->workgroupID);
- globals.putVec3<const char *, int>("workgroupSize", routine->workgroupSize);
- globals.put<const char *, int>("numSubgroups", routine->subgroupsPerWorkgroup);
- globals.put<const char *, int>("subgroupIndex", routine->subgroupIndex);
-
- globals.put<const char *, int>("globalInvocationId",
- rr::Extract(routine->globalInvocationID[0], i),
- rr::Extract(routine->globalInvocationID[1], i),
- rr::Extract(routine->globalInvocationID[2], i));
- globals.put<const char *, int>("localInvocationId",
- rr::Extract(routine->localInvocationID[0], i),
- rr::Extract(routine->localInvocationID[1], i),
- rr::Extract(routine->localInvocationID[2], i));
- globals.put<const char *, int>("localInvocationIndex", rr::Extract(routine->localInvocationIndex, i));
+ {
+ auto compute = globals + OFFSET(Globals, compute);
+ store(compute + OFFSET(Globals::Compute, numWorkgroups), routine->numWorkgroups);
+ store(compute + OFFSET(Globals::Compute, workgroupID), routine->workgroupID);
+ store(compute + OFFSET(Globals::Compute, workgroupSize), routine->workgroupSize);
+ store(compute + OFFSET(Globals::Compute, numSubgroups), routine->subgroupsPerWorkgroup);
+ store(compute + OFFSET(Globals::Compute, subgroupIndex), routine->subgroupIndex);
+ store(compute + OFFSET(Globals::Compute, globalInvocationId), routine->globalInvocationID);
+ store(compute + OFFSET(Globals::Compute, localInvocationIndex), routine->localInvocationIndex);
break;
-
+ }
case spv::ExecutionModelFragment:
- globals.put<const char *, int>("viewIndex", routine->viewID);
- globals.put<const char *, float>("fragCoord",
- rr::Extract(routine->fragCoord[0], i),
- rr::Extract(routine->fragCoord[1], i),
- rr::Extract(routine->fragCoord[2], i),
- rr::Extract(routine->fragCoord[3], i));
- globals.put<const char *, float>("pointCoord",
- rr::Extract(routine->pointCoord[0], i),
- rr::Extract(routine->pointCoord[1], i));
- globals.put<const char *, int>("windowSpacePosition",
- rr::Extract(routine->windowSpacePosition[0], i),
- rr::Extract(routine->windowSpacePosition[1], i));
- globals.put<const char *, int>("helperInvocation", rr::Extract(routine->helperInvocation, i));
+ {
+ auto fragment = globals + OFFSET(Globals, fragment);
+ store(fragment + OFFSET(Globals::Fragment, viewIndex), routine->viewID);
+ store(fragment + OFFSET(Globals::Fragment, fragCoord), routine->fragCoord);
+ store(fragment + OFFSET(Globals::Fragment, pointCoord), routine->pointCoord);
+ store(fragment + OFFSET(Globals::Fragment, windowSpacePosition), routine->windowSpacePosition);
+ store(fragment + OFFSET(Globals::Fragment, helperInvocation), routine->helperInvocation);
break;
-
+ }
case spv::ExecutionModelVertex:
- globals.put<const char *, int>("viewIndex", routine->viewID);
- globals.put<const char *, int>("instanceIndex", routine->instanceID);
- globals.put<const char *, int>("vertexIndex",
- rr::Extract(routine->vertexIndex, i));
+ {
+ auto vertex = globals + OFFSET(Globals, vertex);
+ store(vertex + OFFSET(Globals::Vertex, viewIndex), routine->viewID);
+ store(vertex + OFFSET(Globals::Vertex, instanceIndex), routine->instanceID);
+ store(vertex + OFFSET(Globals::Vertex, vertexIndex), routine->vertexIndex);
break;
-
+ }
default:
break;
}
@@ -2043,6 +2571,8 @@
auto dbg = impl.debugger;
if(!dbg) { return; }
+ dbg->finalize();
+
rr::Call(&Impl::Debugger::State::destroy, state->routine->dbgState);
}
@@ -2085,17 +2615,16 @@
// For example:
// while(true) { foo(); }
// foo() should be repeatedly steppable.
- dbg->setNextSetLocationIsStep();
+ dbg->setNextSetLocationIsSteppable();
}
- if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0)
+ if(!dbg->shaderHasDebugInfo)
{
// We're emitting debugger logic for SPIR-V.
if(IsStatement(insn.opcode()))
{
auto line = dbg->spirvLineMappings.at(insn.wordPointer(0));
- auto column = 0;
- dbg->setLocation(state, dbg->spirvFile, line, column);
+ dbg->setLocation(state, dbg->spirvFile, line);
}
}
}
@@ -2106,38 +2635,35 @@
auto dbg = impl.debugger;
if(!dbg) { return; }
- // Don't display SSA values if rich debug info is available
- if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0)
+ switch(insn.opcode())
{
- // We're emitting debugger logic for SPIR-V.
- // Does this instruction emit a result that should be exposed to the
- // debugger?
- auto resIt = dbg->results.find(insn.wordPointer(0));
- if(resIt != dbg->results.end())
+ case spv::OpVariable:
+ case spv::OpConstant: // TODO: Move constants out of shadow memory.
+ case spv::OpConstantNull:
+ case spv::OpConstantTrue:
+ case spv::OpConstantFalse:
+ case spv::OpConstantComposite:
+ dbg->shadow.create(this, state, insn.resultId());
+ break;
+ default:
{
- auto id = resIt->second;
- dbgExposeIntermediate(id, state);
+ auto resIt = dbg->results.find(insn.wordPointer(0));
+ if(resIt != dbg->results.end())
+ {
+ dbg->shadow.create(this, state, resIt->second);
+ }
}
}
}
-void SpirvShader::dbgExposeIntermediate(Object::ID id, EmitState *state) const
-{
- auto dbg = impl.debugger;
- if(!dbg) { return; }
-
- dbg->exposeVariable(this, id, &debug::Scope::Global, nullptr, id, state);
-}
-
void SpirvShader::dbgUpdateActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const
{
auto dbg = impl.debugger;
if(!dbg) { return; }
- for(int lane = 0; lane < SIMD::Width; lane++)
- {
- rr::Call(&Impl::Debugger::State::updateActiveLaneMask, state->routine->dbgState, lane, rr::Extract(mask, lane) != 0);
- }
+ auto dbgState = state->routine->dbgState;
+ auto globals = dbgState + OFFSET(Impl::Debugger::State, globals);
+ store(globals + OFFSET(Impl::Debugger::State::Globals, activeLaneMask), mask);
}
void SpirvShader::dbgDeclareResult(const InsnIterator &insn, Object::ID resultId) const
@@ -2154,8 +2680,7 @@
{
auto path = getString(insn.word(1));
auto line = insn.word(2);
- auto column = insn.word(3);
- dbg->setLocation(state, path, line, column);
+ dbg->setLocation(state, path.c_str(), line);
}
return EmitResult::Continue;
}
@@ -2178,14 +2703,14 @@
auto dbg = impl.debugger;
if(!dbg) { return; }
- dbg->process(this, insn, nullptr, Impl::Debugger::Pass::Define);
+ dbg->process(insn, nullptr, Impl::Debugger::Pass::Define);
}
SpirvShader::EmitResult SpirvShader::EmitOpenCLDebugInfo100(InsnIterator insn, EmitState *state) const
{
if(auto dbg = impl.debugger)
{
- dbg->process(this, insn, state, Impl::Debugger::Pass::Emit);
+ dbg->process(insn, state, Impl::Debugger::Pass::Emit);
}
return EmitResult::Continue;
}