SpirvShaderDebugger: Implement OpenCL.Debug.100
This is a first, rough, implementation of the OpenCL.Debug.100 extension instruction set.
This is incomplete, and likely buggy.
It does however implement enough to perform single line stepping through hand-crafted HLSL, with basic variable inspection.
Many tests and fixes to come...
Bug: b/145351270
Change-Id: Ia570f5b22a8b3ce39df0438ca3c6700d1a00d014
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/40268
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro-Presubmit: 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 7c45d13..9b4ce13 100644
--- a/src/Pipeline/SpirvShaderDebugger.cpp
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -24,10 +24,31 @@
# include "Vulkan/Debug/Thread.hpp"
# include "Vulkan/Debug/Variable.hpp"
+# include "spirv-tools/ext/OpenCLDebugInfo100.h"
# include "spirv-tools/libspirv.h"
# include <algorithm>
+namespace {
+
+// ArgTy<F>::type resolves to the single argument type of the function F.
+template<typename F>
+struct ArgTy
+{
+ using type = typename ArgTy<decltype(&F::operator())>::type;
+};
+
+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;
+
+} // anonymous namespace
+
namespace spvtools {
// Function implemented in third_party/SPIRV-Tools/source/disassemble.cpp
@@ -54,6 +75,10 @@
{
return std::to_string(s);
}
+std::string tostring(char *s)
+{
+ return s;
+}
std::string tostring(const char *s)
{
return s;
@@ -63,6 +88,258 @@
return "%" + std::to_string(id.value());
}
+////////////////////////////////////////////////////////////////////////////////
+// OpenCL.Debug.100 data structures
+////////////////////////////////////////////////////////////////////////////////
+namespace debug {
+
+struct Member;
+
+struct Object
+{
+ enum class Kind
+ {
+ Object,
+ Declare,
+ Expression,
+ Function,
+ InlinedAt,
+ LocalVariable,
+ Member,
+ Operation,
+ Source,
+ SourceScope,
+ Value,
+
+ // Scopes
+ CompilationUnit,
+ LexicalBlock,
+
+ // Types
+ BasicType,
+ VectorType,
+ FunctionType,
+ CompositeType,
+ };
+
+ using ID = sw::SpirvID<Object>;
+ static constexpr auto KIND = Kind::Object;
+ inline Object(Kind kind)
+ : kind(kind)
+ {}
+ const Kind kind;
+
+ // kindof() returns true iff kind is of this type, or any type deriving from
+ // this type.
+ static constexpr bool kindof(Object::Kind kind) { return true; }
+};
+
+template<typename TYPE_, typename BASE, Object::Kind KIND_>
+struct ObjectImpl : public BASE
+{
+ using ID = sw::SpirvID<TYPE_>;
+ static constexpr auto KIND = KIND_;
+
+ ObjectImpl()
+ : 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; }
+};
+
+template<typename TO, typename FROM>
+TO *cast(FROM *obj)
+{
+ return (TO::kindof(obj->kind)) ? static_cast<TO *>(obj) : nullptr;
+}
+
+template<typename TO, typename FROM>
+const TO *cast(const FROM *obj)
+{
+ return (TO::kindof(obj->kind)) ? static_cast<const TO *>(obj) : nullptr;
+}
+
+struct Scope : public Object
+{
+ // Root represents the root stack frame scope.
+ static const Scope Root;
+
+ using ID = sw::SpirvID<Scope>;
+ inline Scope(Kind kind)
+ : Object(kind)
+ {}
+
+ // kindof() returns true iff kind is of this type, or any type deriving from
+ // this type.
+ static constexpr bool kindof(Kind kind)
+ {
+ return kind == Kind::CompilationUnit ||
+ kind == Kind::LexicalBlock;
+ }
+
+ struct Source *source = nullptr;
+};
+
+struct Type : public Object
+{
+ using ID = sw::SpirvID<Type>;
+ inline Type(Kind kind)
+ : Object(kind)
+ {}
+
+ // kindof() returns true iff kind is of this type, or any type deriving from
+ // this type.
+ static constexpr bool kindof(Kind kind)
+ {
+ return kind == Kind::BasicType ||
+ kind == Kind::VectorType ||
+ kind == Kind::FunctionType ||
+ kind == Kind::CompositeType;
+ }
+};
+
+struct CompilationUnit : ObjectImpl<CompilationUnit, Scope, Object::Kind::CompilationUnit>
+{
+};
+
+struct Source : ObjectImpl<Source, Object, Object::Kind::Source>
+{
+ spv::SourceLanguage language;
+ uint32_t version = 0;
+ std::string file;
+ std::string source;
+
+ std::shared_ptr<vk::dbg::File> dbgFile;
+};
+
+struct BasicType : ObjectImpl<BasicType, Type, Object::Kind::BasicType>
+{
+ std::string name;
+ uint32_t size = 0; // in bits.
+ OpenCLDebugInfo100DebugBaseTypeAttributeEncoding encoding = OpenCLDebugInfo100Unspecified;
+};
+
+struct VectorType : ObjectImpl<VectorType, Type, Object::Kind::VectorType>
+{
+ Type *base = nullptr;
+ uint32_t components = 0;
+};
+
+struct FunctionType : ObjectImpl<FunctionType, Type, Object::Kind::FunctionType>
+{
+ uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
+ Type *returnTy = nullptr;
+ std::vector<Type *> paramTys;
+};
+
+struct CompositeType : ObjectImpl<CompositeType, Type, Object::Kind::CompositeType>
+{
+ std::string name;
+ OpenCLDebugInfo100DebugCompositeType tag = OpenCLDebugInfo100Class;
+ Source *source = nullptr;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ Object *parent = nullptr;
+ std::string linkage;
+ uint32_t size = 0; // in bits.
+ uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
+ std::vector<Member *> members;
+};
+
+struct Member : ObjectImpl<Member, Object, Object::Kind::Member>
+{
+ std::string name;
+ Type *type = nullptr;
+ Source *source = nullptr;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ CompositeType *parent = nullptr;
+ uint32_t offset = 0; // in bits
+ uint32_t size = 0; // in bits
+ uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
+};
+
+struct Function : ObjectImpl<Function, Object, Object::Kind::Function>
+{
+ std::string name;
+ FunctionType *type = nullptr;
+ Source *source = nullptr;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ struct LexicalBlock *parent = nullptr;
+ std::string linkage;
+ uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags
+ uint32_t scopeLine = 0;
+ sw::SpirvShader::Function::ID function;
+};
+
+struct LexicalBlock : ObjectImpl<LexicalBlock, Scope, Object::Kind::LexicalBlock>
+{
+ uint32_t line = 0;
+ uint32_t column = 0;
+ Scope *parent = nullptr;
+ std::string name;
+ Function *function = nullptr;
+};
+
+struct InlinedAt : ObjectImpl<InlinedAt, Object, Object::Kind::InlinedAt>
+{
+ uint32_t line = 0;
+ Scope *scope = nullptr;
+ InlinedAt *inlined = nullptr;
+};
+
+struct SourceScope : ObjectImpl<SourceScope, Object, Object::Kind::SourceScope>
+{
+ LexicalBlock *scope = nullptr;
+ InlinedAt *inlinedAt = nullptr;
+};
+
+struct LocalVariable : ObjectImpl<LocalVariable, Object, Object::Kind::LocalVariable>
+{
+ static constexpr uint32_t NoArg = ~uint32_t(0);
+
+ std::string name;
+ Type *type = nullptr;
+ Source *source = nullptr;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ Scope *parent = nullptr;
+ uint32_t arg = NoArg;
+};
+
+struct Operation : ObjectImpl<Operation, Object, Object::Kind::Operation>
+{
+ uint32_t opcode = 0;
+ std::vector<uint32_t> operands;
+};
+
+struct Expression : ObjectImpl<Expression, Object, Object::Kind::Expression>
+{
+ std::vector<Operation *> operations;
+};
+
+struct Declare : ObjectImpl<Declare, Object, Object::Kind::Declare>
+{
+ LocalVariable *local = nullptr;
+ sw::SpirvShader::Object::ID variable;
+ Expression *expression = nullptr;
+};
+
+struct Value : ObjectImpl<Value, Object, Object::Kind::Value>
+{
+ LocalVariable *local = nullptr;
+ sw::SpirvShader::Object::ID variable;
+ Expression *expression = nullptr;
+ std::vector<uint32_t> indexes;
+};
+
+const Scope Scope::Root = CompilationUnit{};
+
+} // namespace debug
} // anonymous namespace
namespace rr {
@@ -98,6 +375,14 @@
class Group;
class State;
+ enum class Pass
+ {
+ Define,
+ Emit
+ };
+
+ void process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass);
+
void setPosition(EmitState *state, const std::string &path, uint32_t line, uint32_t column);
// exposeVariable exposes the variable with the given ID to the debugger
@@ -106,6 +391,8 @@
void exposeVariable(
const SpirvShader *shader,
const Key &key,
+ const debug::Scope *scope,
+ const debug::Type *type,
Object::ID id,
EmitState *state) const;
@@ -117,6 +404,7 @@
const Group &group,
int lane,
const Key &key,
+ const debug::Type *type,
Object::ID id,
EmitState *state,
int wordOffset = 0) const;
@@ -127,7 +415,32 @@
std::unordered_map<const void *, Object::ID> results; // instruction pointer to result ID
private:
+ // add() registers the debug object with the given id.
+ template<typename ID, typename T>
+ void add(ID id, T *);
+
+ // get() returns the debug object with the given id.
+ // The object must exist and be of type (or derive from type) T.
+ template<typename T>
+ T *get(SpirvID<T> id) const;
+
+ // use get() and add() to access this
+ std::unordered_map<debug::Object::ID, std::unique_ptr<debug::Object>> objects;
+
std::unordered_map<std::string, vk::dbg::File::ID> fileIDs;
+
+ // defineOrEmit() when called in Pass::Define, creates and stores a
+ // zero-initialized object into the Debugger::objects map using the
+ // object identifier held by second instruction operand.
+ // When called in Pass::Emit, defineOrEmit() calls the function F with the
+ // previously-built object.
+ //
+ // F must be a function with the signature:
+ // void(OBJECT_TYPE *)
+ //
+ // The object type is automatically inferred from the function signature.
+ template<typename F, typename T = typename std::remove_pointer<ArgTyT<F>>::type>
+ void defineOrEmit(InsnIterator insn, Pass pass, F &&emit);
};
////////////////////////////////////////////////////////////////////////////////
@@ -148,10 +461,12 @@
void exit();
void updateActiveLaneMask(int lane, bool enabled);
void update(vk::dbg::File::ID file, int line, int column);
+ void createScope(const debug::Scope *);
+ void setScope(debug::SourceScope *newScope);
- vk::dbg::VariableContainer *locals();
- vk::dbg::VariableContainer *hovers();
- vk::dbg::VariableContainer *localsLane(int lane);
+ vk::dbg::VariableContainer *locals(const debug::Scope *);
+ 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);
@@ -159,6 +474,11 @@
template<typename K, typename V>
void putVal(vk::dbg::VariableContainer *vc, K key, V value);
+ 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
{
std::shared_ptr<vk::dbg::Scope> locals;
@@ -166,9 +486,14 @@
std::array<std::shared_ptr<vk::dbg::VariableContainer>, sw::SIMD::Width> localsByLane;
};
+ // getScopes() returns the Scopes object for the given debug::Scope.
+ const Scopes &getScopes(const debug::Scope *scope);
+
const Debugger *debugger;
const std::shared_ptr<vk::dbg::Thread> thread;
- Scopes threadScopes;
+ std::unordered_map<const debug::Scope *, Scopes> scopes;
+ Scopes rootScopes; // Scopes for the root stack frame.
+ debug::SourceScope *srcScope = nullptr; // Current source scope.
};
SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger, const char *name)
@@ -188,12 +513,12 @@
{
enter(lock, stackBase);
thread->update([&](vk::dbg::Frame &frame) {
- threadScopes.locals = frame.locals;
- threadScopes.hovers = frame.hovers;
+ rootScopes.locals = frame.locals;
+ rootScopes.hovers = frame.hovers;
for(int i = 0; i < sw::SIMD::Width; i++)
{
- threadScopes.localsByLane[i] = lock.createVariableContainer();
- frame.locals->variables->put(laneNames[i], threadScopes.localsByLane[i]);
+ rootScopes.localsByLane[i] = lock.createVariableContainer();
+ frame.locals->variables->put(laneNames[i], rootScopes.localsByLane[i]);
}
});
}
@@ -215,7 +540,7 @@
void SpirvShader::Impl::Debugger::State::updateActiveLaneMask(int lane, bool enabled)
{
- threadScopes.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
+ rootScopes.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
}
void SpirvShader::Impl::Debugger::State::update(vk::dbg::File::ID fileID, int line, int column)
@@ -226,19 +551,19 @@
});
}
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::locals()
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::locals(const debug::Scope *scope)
{
- return threadScopes.locals->variables.get();
+ return getScopes(scope).locals->variables.get();
}
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers()
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers(const debug::Scope *scope)
{
- return threadScopes.hovers->variables.get();
+ return getScopes(scope).hovers->variables.get();
}
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(int i)
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(const debug::Scope *scope, int i)
{
- return threadScopes.localsByLane[i].get();
+ return getScopes(scope).localsByLane[i].get();
}
template<typename K>
@@ -255,6 +580,67 @@
vc->put(tostring(key), vk::dbg::make_constant(value));
}
+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)
+{
+ ASSERT(spirvScope != nullptr);
+
+ // TODO: Deal with scope nesting.
+
+ 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++)
+ {
+ s.localsByLane[i] = lock.createVariableContainer();
+ s.locals->variables->put(laneNames[i], s.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;
+
+ auto lock = debugger->ctx->lock();
+ auto thread = lock.currentThread();
+
+ debug::Function *oldFunction = oldSrcScope ? oldSrcScope->scope->function : nullptr;
+ debug::Function *newFunction = newSrcScope ? newSrcScope->scope->function : nullptr;
+
+ if(oldFunction != newFunction)
+ {
+ if(oldFunction) { thread->exit(); }
+ if(newFunction) { thread->enter(lock, newFunction->source->dbgFile, newFunction->name); }
+ }
+
+ auto dbgScope = getScopes(srcScope->scope);
+ thread->update([&](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::Root)
+ {
+ return rootScopes;
+ }
+
+ auto dbgScopeIt = scopes.find(scope);
+ ASSERT_MSG(dbgScopeIt != scopes.end(), "createScope() not called for debug::Scope %p", scope);
+ return dbgScopeIt->second;
+}
+
////////////////////////////////////////////////////////////////////////////////
// sw::SpirvShader::Impl::Debugger::Group
//
@@ -266,9 +652,9 @@
public:
using Ptr = rr::Pointer<rr::Byte>;
- static Group hovers(Ptr state);
- static Group locals(Ptr state);
- static Group localsLane(Ptr state, int lane);
+ 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);
Group(Ptr state, Ptr group);
@@ -278,6 +664,9 @@
template<typename K, typename V, typename RK, typename RV>
void put(RK key, RV value) 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;
@@ -296,21 +685,21 @@
};
SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::hovers(Ptr state)
+SpirvShader::Impl::Debugger::Group::hovers(Ptr state, const debug::Scope *scope)
{
- return Group(state, rr::Call(&State::hovers, state));
+ return Group(state, rr::Call(&State::hovers, state, scope));
}
SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::locals(Ptr state)
+SpirvShader::Impl::Debugger::Group::locals(Ptr state, const debug::Scope *scope)
{
- return Group(state, rr::Call(&State::locals, state));
+ return Group(state, rr::Call(&State::locals, state, scope));
}
SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, int lane)
+SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, const debug::Scope *scope, int lane)
{
- return Group(state, rr::Call(&State::localsLane, state, lane));
+ return Group(state, rr::Call(&State::localsLane, state, scope, lane));
}
SpirvShader::Impl::Debugger::Group::Group(Ptr state, Ptr group)
@@ -330,6 +719,12 @@
rr::Call(&State::putVal<K, V>, state, ptr, key, value);
}
+template<typename K, typename V, typename RK>
+void SpirvShader::Impl::Debugger::Group::putRef(RK key, RValue<Pointer<Byte>> ref) const
+{
+ 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
{
@@ -369,6 +764,201 @@
////////////////////////////////////////////////////////////////////////////////
// sw::SpirvShader::Impl::Debugger methods
////////////////////////////////////////////////////////////////////////////////
+template<typename F, typename T>
+void SpirvShader::Impl::Debugger::defineOrEmit(InsnIterator insn, Pass pass, F &&emit)
+{
+ auto id = SpirvID<T>(insn.word(2));
+ switch(pass)
+ {
+ case Pass::Define:
+ add(id, new T());
+ break;
+ case Pass::Emit:
+ emit(get<T>(id));
+ break;
+ }
+}
+
+void SpirvShader::Impl::Debugger::process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass)
+{
+ auto dbg = shader->impl.debugger;
+ auto extInstIndex = insn.word(4);
+ switch(extInstIndex)
+ {
+ case OpenCLDebugInfo100DebugCompilationUnit:
+ defineOrEmit(insn, pass, [&](debug::CompilationUnit *cu) {
+ cu->source = get(debug::Source::ID(insn.word(7)));
+ });
+ break;
+ case OpenCLDebugInfo100DebugTypeBasic:
+ defineOrEmit(insn, pass, [&](debug::BasicType *type) {
+ type->name = shader->getString(insn.word(5));
+ type->size = shader->GetConstScalarInt(insn.word(6));
+ type->encoding = static_cast<OpenCLDebugInfo100DebugBaseTypeAttributeEncoding>(insn.word(7));
+ });
+ break;
+ case OpenCLDebugInfo100DebugTypeVector:
+ defineOrEmit(insn, pass, [&](debug::VectorType *type) {
+ type->base = get(debug::Type::ID(insn.word(5)));
+ type->components = insn.word(6);
+ });
+ break;
+ case OpenCLDebugInfo100DebugTypeFunction:
+ defineOrEmit(insn, pass, [&](debug::FunctionType *type) {
+ type->flags = insn.word(5);
+ type->returnTy = get(debug::Type::ID(insn.word(6)));
+ for(uint32_t i = 7; i < insn.wordCount(); i++)
+ {
+ type->paramTys.push_back(get(debug::Type::ID(insn.word(i))));
+ }
+ });
+ break;
+ case OpenCLDebugInfo100DebugTypeComposite:
+ defineOrEmit(insn, pass, [&](debug::CompositeType *type) {
+ 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);
+ type->column = insn.word(9);
+ type->parent = get(debug::Object::ID(insn.word(10)));
+ type->linkage = shader->getString(insn.word(11));
+ type->size = shader->GetConstScalarInt(insn.word(12));
+ type->flags = insn.word(13);
+ for(uint32_t i = 14; i < insn.wordCount(); i++)
+ {
+ 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);
+ }
+ }
+ });
+ break;
+ case OpenCLDebugInfo100DebugTypeMember:
+ defineOrEmit(insn, pass, [&](debug::Member *member) {
+ member->name = shader->getString(insn.word(5));
+ member->type = get(debug::Type::ID(insn.word(6)));
+ member->source = get(debug::Source::ID(insn.word(7)));
+ member->line = insn.word(8);
+ member->column = insn.word(9);
+ member->parent = get(debug::CompositeType::ID(insn.word(10)));
+ member->offset = shader->GetConstScalarInt(insn.word(11));
+ member->size = shader->GetConstScalarInt(insn.word(12));
+ member->flags = insn.word(13);
+ });
+ break;
+ case OpenCLDebugInfo100DebugFunction:
+ defineOrEmit(insn, pass, [&](debug::Function *func) {
+ func->name = shader->getString(insn.word(5));
+ func->type = get(debug::FunctionType::ID(insn.word(6)));
+ func->source = get(debug::Source::ID(insn.word(7)));
+ func->line = insn.word(8);
+ func->column = insn.word(9);
+ func->parent = get(debug::LexicalBlock::ID(insn.word(10)));
+ func->linkage = shader->getString(insn.word(11));
+ func->flags = insn.word(12);
+ func->scopeLine = insn.word(13);
+ func->function = Function::ID(insn.word(14));
+ // declaration: word(13)
+
+ func->parent->function = func;
+ });
+ break;
+ case OpenCLDebugInfo100DebugLexicalBlock:
+ defineOrEmit(insn, pass, [&](debug::LexicalBlock *scope) {
+ scope->source = get(debug::Source::ID(insn.word(5)));
+ scope->line = insn.word(6);
+ scope->column = insn.word(7);
+ scope->parent = get(debug::Scope::ID(insn.word(8)));
+ if(insn.wordCount() > 9)
+ {
+ scope->name = shader->getString(insn.word(9));
+ }
+
+ // TODO: We're creating scopes per-shader invocation.
+ // This is all really static information, and should only be created
+ // once *per program*.
+ rr::Call(&State::createScope, state->routine->dbgState, scope);
+ });
+ break;
+ case OpenCLDebugInfo100DebugScope:
+ defineOrEmit(insn, pass, [&](debug::SourceScope *ss) {
+ ss->scope = get(debug::LexicalBlock::ID(insn.word(5)));
+ if(insn.wordCount() > 6)
+ {
+ ss->inlinedAt = get(debug::InlinedAt::ID(insn.word(6)));
+ }
+
+ rr::Call(&State::setScope, state->routine->dbgState, ss);
+ });
+ break;
+ case OpenCLDebugInfo100DebugNoScope:
+ break;
+ case OpenCLDebugInfo100DebugLocalVariable:
+ defineOrEmit(insn, pass, [&](debug::LocalVariable *var) {
+ var->name = shader->getString(insn.word(5));
+ var->type = get(debug::Type::ID(insn.word(6)));
+ var->source = get(debug::Source::ID(insn.word(7)));
+ var->line = insn.word(8);
+ var->column = insn.word(9);
+ var->parent = get(debug::Scope::ID(insn.word(10)));
+ if(insn.wordCount() > 11)
+ {
+ var->arg = insn.word(11);
+ }
+ });
+ break;
+ case OpenCLDebugInfo100DebugDeclare:
+ defineOrEmit(insn, pass, [&](debug::Declare *decl) {
+ 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);
+ });
+ break;
+ case OpenCLDebugInfo100DebugValue:
+ defineOrEmit(insn, pass, [&](debug::Value *value) {
+ value->local = get(debug::LocalVariable::ID(insn.word(5)));
+ value->variable = Object::ID(insn.word(6));
+ value->expression = get(debug::Expression::ID(insn.word(7)));
+ for(uint32_t i = 8; i < insn.wordCount(); i++)
+ {
+ value->indexes.push_back(insn.word(i));
+ }
+ });
+ break;
+ case OpenCLDebugInfo100DebugExpression:
+ defineOrEmit(insn, pass, [&](debug::Expression *expr) {
+ for(uint32_t i = 5; i < insn.wordCount(); i++)
+ {
+ expr->operations.push_back(get(debug::Operation::ID(insn.word(i))));
+ }
+ });
+ break;
+ case OpenCLDebugInfo100DebugSource:
+ defineOrEmit(insn, pass, [&](debug::Source *source) {
+ source->file = shader->getString(insn.word(5));
+ 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());
+ source->dbgFile = file;
+ fileIDs.emplace(source->file.c_str(), file->id);
+ });
+ break;
+ default:
+ UNSUPPORTED("Unsupported OpenCLDebugInfo100 instruction %d", int(extInstIndex));
+ }
+}
+
void SpirvShader::Impl::Debugger::setPosition(EmitState *state, const std::string &path, uint32_t line, uint32_t column)
{
auto it = fileIDs.find(path);
@@ -378,19 +968,39 @@
}
}
+template<typename ID, typename T>
+void SpirvShader::Impl::Debugger::add(ID id, T *obj)
+{
+ auto added = objects.emplace(debug::Object::ID(id.value()), obj).second;
+ ASSERT_MSG(added, "Debug object with %d already exists", id.value());
+}
+
+template<typename T>
+T *SpirvShader::Impl::Debugger::get(SpirvID<T> id) const
+{
+ auto it = objects.find(debug::Object::ID(id.value()));
+ ASSERT_MSG(it != objects.end(), "Unknown debug object %d", id.value());
+ auto ptr = debug::cast<T>(it->second.get());
+ ASSERT_MSG(ptr, "Debug object %d is not of the correct type. Got: %d, want: %d",
+ id.value(), int(it->second->kind), int(T::KIND));
+ return ptr;
+}
+
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
{
auto dbgState = state->routine->dbgState;
- auto hover = Group::hovers(dbgState).group<Key>(key);
+ auto hover = Group::hovers(dbgState, scope).group<Key>(key);
for(int lane = 0; lane < SIMD::Width; lane++)
{
- exposeVariable(shader, Group::localsLane(dbgState, lane), lane, key, id, state);
- exposeVariable(shader, hover, lane, laneNames[lane], id, state);
+ exposeVariable(shader, Group::localsLane(dbgState, scope, lane), lane, key, type, id, state);
+ exposeVariable(shader, hover, lane, laneNames[lane], type, id, state);
}
}
@@ -400,10 +1010,146 @@
const Group &group,
int l,
const Key &key,
+ const debug::Type *type,
Object::ID id,
EmitState *state,
int wordOffset /* = 0 */) const
{
+ if(type != nullptr)
+ {
+ if(auto ty = debug::cast<debug::BasicType>(type))
+ {
+ auto &obj = shader->getObject(id);
+ SIMD::Int val;
+ switch(obj.kind)
+ {
+ case Object::Kind::InterfaceVariable:
+ case Object::Kind::Pointer:
+ {
+ auto ptr = shader->GetPointerToData(id, 0, state) + sizeof(uint32_t) * wordOffset;
+ auto &ptrTy = shader->getType(obj.type);
+ if(IsStorageInterleavedByLane(ptrTy.storageClass))
+ {
+ ptr = InterleaveByLane(ptr);
+ }
+ auto addr = &ptr.base[Extract(ptr.offsets(), l)];
+ switch(ty->encoding)
+ {
+ case OpenCLDebugInfo100Address:
+ // TODO: This function takes a SIMD vector, and pointers cannot
+ // be held in them.
+ break;
+ case OpenCLDebugInfo100Boolean:
+ group.putRef<Key, bool>(key, addr);
+ break;
+ case OpenCLDebugInfo100Float:
+ group.putRef<Key, float>(key, addr);
+ break;
+ case OpenCLDebugInfo100Signed:
+ group.putRef<Key, int>(key, addr);
+ break;
+ case OpenCLDebugInfo100SignedChar:
+ group.putRef<Key, int8_t>(key, addr);
+ break;
+ case OpenCLDebugInfo100Unsigned:
+ group.putRef<Key, unsigned int>(key, addr);
+ break;
+ case OpenCLDebugInfo100UnsignedChar:
+ group.putRef<Key, uint8_t>(key, addr);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case Object::Kind::Constant:
+ case Object::Kind::Intermediate:
+ {
+ auto val = GenericValue(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, l) != 0);
+ break;
+ case OpenCLDebugInfo100Float:
+ group.put<Key, float>(key, Extract(As<SIMD::Float>(val), l));
+ break;
+ case OpenCLDebugInfo100Signed:
+ group.put<Key, int>(key, Extract(val, l));
+ break;
+ case OpenCLDebugInfo100SignedChar:
+ group.put<Key, int8_t>(key, SByte(Extract(val, l)));
+ break;
+ case OpenCLDebugInfo100Unsigned:
+ group.put<Key, unsigned int>(key, Extract(val, l));
+ break;
+ case OpenCLDebugInfo100UnsignedChar:
+ group.put<Key, uint8_t>(key, Byte(Extract(val, l)));
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+ }
+ 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, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+ break;
+ case 2:
+ exposeVariable(shader, vecGroup, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+ exposeVariable(shader, vecGroup, l, "y", elTy, id, state, wordOffset + 1 * elWords);
+ break;
+ case 3:
+ exposeVariable(shader, vecGroup, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+ exposeVariable(shader, vecGroup, l, "y", elTy, id, state, wordOffset + 1 * elWords);
+ exposeVariable(shader, vecGroup, l, "z", elTy, id, state, wordOffset + 2 * elWords);
+ break;
+ case 4:
+ exposeVariable(shader, vecGroup, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+ exposeVariable(shader, vecGroup, l, "y", elTy, id, state, wordOffset + 1 * elWords);
+ exposeVariable(shader, vecGroup, l, "z", elTy, id, state, wordOffset + 2 * elWords);
+ exposeVariable(shader, vecGroup, l, "w", elTy, id, state, wordOffset + 3 * elWords);
+ break;
+ default:
+ for(uint32_t i = 0; i < ty->components; i++)
+ {
+ exposeVariable(shader, vecGroup, l, std::to_string(i).c_str(), elTy, id, state, wordOffset + i * elWords);
+ }
+ break;
+ }
+ return;
+ }
+ else if(auto ty = debug::cast<debug::CompositeType>(type))
+ {
+ auto objectGroup = group.group<Key>(key);
+
+ for(auto member : ty->members)
+ {
+ auto memberGroup = objectGroup.template group<const char *>(member->name.c_str());
+ exposeVariable(shader, memberGroup, l, member->name.c_str(), member->type, id, state, member->offset / 32);
+ }
+
+ return;
+ }
+ }
+
+ // No debug type information. Derive from SPIR-V.
GenericValue val(shader, state, id);
switch(shader->getType(val.type).opcode())
{
@@ -539,7 +1285,7 @@
SetActiveLaneMask(state->activeLaneMask(), state);
- auto locals = Group::locals(dbgState);
+ auto locals = Group::locals(dbgState, &debug::Scope::Root);
locals.put<const char *, int>("subgroupSize", routine->invocationsPerSubgroup);
switch(executionModel)
@@ -553,7 +1299,7 @@
for(int i = 0; i < SIMD::Width; i++)
{
- auto lane = Group::localsLane(dbgState, i);
+ auto lane = Group::localsLane(dbgState, &debug::Scope::Root, i);
lane.put<const char *, int>("globalInvocationId",
rr::Extract(routine->globalInvocationID[0], i),
rr::Extract(routine->globalInvocationID[1], i),
@@ -570,7 +1316,7 @@
locals.put<const char *, int>("viewIndex", routine->viewID);
for(int i = 0; i < SIMD::Width; i++)
{
- auto lane = Group::localsLane(dbgState, i);
+ auto lane = Group::localsLane(dbgState, &debug::Scope::Root, i);
lane.put<const char *, float>("fragCoord",
rr::Extract(routine->fragCoord[0], i),
rr::Extract(routine->fragCoord[1], i),
@@ -612,12 +1358,15 @@
printf("%s\n", instruction.c_str());
# endif // PRINT_EACH_PROCESSED_INSTRUCTION
- auto dbg = impl.debugger;
- if(!dbg) { return; }
+ if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0)
+ {
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
- auto line = dbg->spirvLineMappings.at(insn.wordPointer(0));
- auto column = 0;
- rr::Call(&Impl::Debugger::State::update, state->routine->dbgState, dbg->spirvFile->id, line, column);
+ auto line = dbg->spirvLineMappings.at(insn.wordPointer(0));
+ auto column = 0;
+ rr::Call(&Impl::Debugger::State::update, state->routine->dbgState, dbg->spirvFile->id, line, column);
+ }
}
void SpirvShader::dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const
@@ -638,7 +1387,7 @@
auto dbg = impl.debugger;
if(!dbg) { return; }
- dbg->exposeVariable(this, id, id, state);
+ dbg->exposeVariable(this, id, &debug::Scope::Root, nullptr, id, state);
}
void SpirvShader::dbgUpdateActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const
@@ -674,10 +1423,18 @@
void SpirvShader::DefineOpenCLDebugInfo100(const InsnIterator &insn)
{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ dbg->process(this, 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);
+ }
return EmitResult::Continue;
}