| // Copyright 2019 The SwiftShader Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "SpirvShader.hpp" |
| |
| // If enabled, each instruction will be printed before processing. |
| #define PRINT_EACH_PROCESSED_INSTRUCTION 0 |
| // If enabled, each instruction will be printed before executing. |
| #define PRINT_EACH_EXECUTED_INSTRUCTION 0 |
| // If enabled, debugger variables will contain debug information (addresses, |
| // byte offset, etc). |
| #define DEBUG_ANNOTATE_VARIABLE_KEYS 0 |
| |
| #ifdef ENABLE_VK_DEBUGGER |
| |
| # include "Vulkan/Debug/Context.hpp" |
| # include "Vulkan/Debug/File.hpp" |
| # include "Vulkan/Debug/Thread.hpp" |
| # include "Vulkan/Debug/Variable.hpp" |
| |
| # include "spirv/unified1/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 |
| // but with no public header. |
| // This is a C++ function, so the name is mangled, and signature changes will |
| // result in a linker error instead of runtime signature mismatches. |
| extern std::string spvInstructionBinaryToText(const spv_target_env env, |
| const uint32_t *inst_binary, |
| const size_t inst_word_count, |
| const uint32_t *binary, |
| const size_t word_count, |
| const uint32_t options); |
| |
| } // 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()); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // OpenCL.Debug.100 data structures |
| //////////////////////////////////////////////////////////////////////////////// |
| namespace debug { |
| |
| struct Member; |
| |
| struct Object |
| { |
| enum class Kind |
| { |
| Object, |
| Declare, |
| Expression, |
| Function, |
| InlinedAt, |
| GlobalVariable, |
| LocalVariable, |
| Member, |
| Operation, |
| Source, |
| SourceScope, |
| Value, |
| TemplateParameter, |
| |
| // Scopes |
| CompilationUnit, |
| LexicalBlock, |
| |
| // Types |
| BasicType, |
| ArrayType, |
| VectorType, |
| FunctionType, |
| CompositeType, |
| TemplateType, |
| }; |
| |
| using ID = sw::SpirvID<Object>; |
| static constexpr auto KIND = Kind::Object; |
| inline Object(Kind kind) |
| : kind(kind) |
| { |
| (void)KIND; // Used in debug builds. Avoid unused variable warnings in NDEBUG builds. |
| } |
| 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; } |
| |
| virtual ~Object() = default; |
| }; |
| |
| // cstr() returns the c-string name of the given Object::Kind. |
| constexpr const char *cstr(Object::Kind k) |
| { |
| switch(k) |
| { |
| case Object::Kind::Object: return "Object"; |
| case Object::Kind::Declare: return "Declare"; |
| case Object::Kind::Expression: return "Expression"; |
| case Object::Kind::Function: return "Function"; |
| case Object::Kind::InlinedAt: return "InlinedAt"; |
| case Object::Kind::GlobalVariable: return "GlobalVariable"; |
| case Object::Kind::LocalVariable: return "LocalVariable"; |
| case Object::Kind::Member: return "Member"; |
| case Object::Kind::Operation: return "Operation"; |
| case Object::Kind::Source: return "Source"; |
| case Object::Kind::SourceScope: return "SourceScope"; |
| case Object::Kind::Value: return "Value"; |
| case Object::Kind::TemplateParameter: return "TemplateParameter"; |
| case Object::Kind::CompilationUnit: return "CompilationUnit"; |
| case Object::Kind::LexicalBlock: return "LexicalBlock"; |
| case Object::Kind::BasicType: return "BasicType"; |
| case Object::Kind::ArrayType: return "ArrayType"; |
| case Object::Kind::VectorType: return "VectorType"; |
| case Object::Kind::FunctionType: return "FunctionType"; |
| case Object::Kind::CompositeType: return "CompositeType"; |
| case Object::Kind::TemplateType: return "TemplateType"; |
| } |
| return "<unknown>"; |
| } |
| |
| 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) |
| { |
| if(obj == nullptr) { return nullptr; } // None |
| return (TO::kindof(obj->kind)) ? static_cast<TO *>(obj) : nullptr; |
| } |
| |
| template<typename TO, typename FROM> |
| const TO *cast(const FROM *obj) |
| { |
| if(obj == nullptr) { return nullptr; } // None |
| return (TO::kindof(obj->kind)) ? static_cast<const TO *>(obj) : nullptr; |
| } |
| |
| struct Scope : public Object |
| { |
| // Global represents the global scope. |
| static const Scope Global; |
| |
| 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::Function || |
| kind == Kind::LexicalBlock; |
| } |
| |
| struct Source *source = nullptr; |
| Scope *parent = 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::ArrayType || |
| kind == Kind::VectorType || |
| kind == Kind::FunctionType || |
| kind == Kind::CompositeType || |
| kind == Kind::TemplateType; |
| } |
| |
| // sizeInBytes() returns the number of bytes of the given debug type. |
| virtual uint32_t sizeInBytes() const = 0; |
| |
| // value() returns a shared pointer to a vk::dbg::Value that views the data |
| // at ptr of this type. |
| virtual std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const = 0; |
| }; |
| |
| 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; |
| |
| uint32_t sizeInBytes() const override { return size / 8; } |
| |
| std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override |
| { |
| switch(encoding) |
| { |
| case OpenCLDebugInfo100Address: |
| // return vk::dbg::make_reference(*static_cast<void **>(ptr)); |
| UNIMPLEMENTED("b/148401179 OpenCLDebugInfo100 OpenCLDebugInfo100Address BasicType"); |
| return nullptr; |
| case OpenCLDebugInfo100Boolean: |
| return vk::dbg::make_reference(*static_cast<bool *>(ptr)); |
| case OpenCLDebugInfo100Float: |
| return vk::dbg::make_reference(*static_cast<float *>(ptr)); |
| case OpenCLDebugInfo100Signed: |
| return vk::dbg::make_reference(*static_cast<int32_t *>(ptr)); |
| case OpenCLDebugInfo100SignedChar: |
| return vk::dbg::make_reference(*static_cast<int8_t *>(ptr)); |
| case OpenCLDebugInfo100Unsigned: |
| return vk::dbg::make_reference(*static_cast<uint32_t *>(ptr)); |
| case OpenCLDebugInfo100UnsignedChar: |
| return vk::dbg::make_reference(*static_cast<uint8_t *>(ptr)); |
| default: |
| UNIMPLEMENTED("b/148401179 OpenCLDebugInfo100 encoding %d", int(encoding)); |
| return nullptr; |
| } |
| } |
| }; |
| |
| struct ArrayType : ObjectImpl<ArrayType, Type, Object::Kind::ArrayType> |
| { |
| Type *base = nullptr; |
| std::vector<uint32_t> dimensions; |
| |
| // 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 |
| { |
| 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 |
| } |
| } |
| } |
| |
| uint32_t sizeInBytes() const override |
| { |
| auto numBytes = base->sizeInBytes(); |
| for(auto dim : dimensions) |
| { |
| numBytes *= dim; |
| } |
| return numBytes; |
| } |
| |
| std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override |
| { |
| auto vc = lock.createVariableContainer(); |
| build( |
| vc, |
| [&](std::shared_ptr<vk::dbg::VariableContainer> &parent, uint32_t idx) { |
| auto child = lock.createVariableContainer(); |
| parent->put(tostring(idx), 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(lock, addr, interleaved); |
| auto key = tostring(idx); |
| # if DEBUG_ANNOTATE_VARIABLE_KEYS |
| key += " (" + tostring(addr) + " +" + tostring(offset) + ", idx: " + tostring(idx) + ")" + (interleaved ? "I" : "F"); |
| # endif |
| parent->put(key, child); |
| }); |
| return vc; |
| } |
| }; |
| |
| 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::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override |
| { |
| const auto elSize = base->sizeInBytes(); |
| auto vc = lock.createVariableContainer(); |
| 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"); |
| # endif |
| vc->put(elKey, base->value(lock, elPtr, interleaved)); |
| } |
| return vc; |
| } |
| }; |
| |
| struct FunctionType : ObjectImpl<FunctionType, Type, Object::Kind::FunctionType> |
| { |
| uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags |
| Type *returnTy = nullptr; |
| std::vector<Type *> paramTys; |
| |
| uint32_t sizeInBytes() const override { return 0; } |
| std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override { return nullptr; } |
| }; |
| |
| 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; |
| struct CompositeType *parent = nullptr; |
| uint32_t offset = 0; // in bits |
| uint32_t size = 0; // in bits |
| uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags |
| }; |
| |
| 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; |
| |
| uint32_t sizeInBytes() const override { return size / 8; } |
| std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override |
| { |
| auto vc = lock.createVariableContainer(); |
| 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"); |
| # endif |
| vc->put(elKey, member->type->value(lock, elPtr, interleaved)); |
| } |
| return vc; |
| } |
| }; |
| |
| struct TemplateParameter : ObjectImpl<TemplateParameter, Object, Object::Kind::TemplateParameter> |
| { |
| std::string name; |
| Type *type = nullptr; |
| uint32_t value = 0; |
| Source *source = nullptr; |
| uint32_t line = 0; |
| uint32_t column = 0; |
| }; |
| |
| struct TemplateType : ObjectImpl<TemplateType, Type, Object::Kind::TemplateType> |
| { |
| Type *target = nullptr; // Class, struct or function. |
| std::vector<TemplateParameter *> parameters; |
| |
| uint32_t sizeInBytes() const override { return target->sizeInBytes(); } |
| std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override |
| { |
| return target->value(lock, ptr, interleaved); |
| } |
| }; |
| |
| struct Function : ObjectImpl<Function, Scope, Object::Kind::Function> |
| { |
| std::string name; |
| FunctionType *type = nullptr; |
| uint32_t line = 0; |
| uint32_t column = 0; |
| 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; |
| std::string name; |
| }; |
| |
| 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> |
| { |
| Scope *scope = nullptr; |
| InlinedAt *inlinedAt = nullptr; |
| }; |
| |
| struct GlobalVariable : ObjectImpl<GlobalVariable, Object, Object::Kind::GlobalVariable> |
| { |
| std::string name; |
| Type *type = nullptr; |
| Source *source = nullptr; |
| uint32_t line = 0; |
| uint32_t column = 0; |
| Scope *parent = nullptr; |
| std::string linkage; |
| sw::SpirvShader::Object::ID variable; |
| uint32_t flags = 0; // OR'd from OpenCLDebugInfo100DebugInfoFlags |
| }; |
| |
| 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::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. |
| template<typename T> |
| T *find(Scope *scope) |
| { |
| if(auto out = cast<T>(scope)) { return out; } |
| 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. |
| //////////////////////////////////////////////////////////////////////////////// |
| 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. |
| //////////////////////////////////////////////////////////////////////////////// |
| struct SpirvShader::Impl::Debugger |
| { |
| class Group; |
| class State; |
| |
| enum class Pass |
| { |
| Define, |
| Emit |
| }; |
| |
| void process(const SpirvShader *shader, 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); |
| |
| // 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; |
| |
| // 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; |
| |
| 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 |
| |
| private: |
| // add() registers the debug object with the given id. |
| template<typename ID> |
| void add(ID id, std::unique_ptr<debug::Object> &&); |
| |
| // addNone() registers given id as a None value or type. |
| void addNone(debug::Object::ID id); |
| |
| // isNone() returns true if the given id was registered as none with |
| // addNone(). |
| bool isNone(debug::Object::ID id) const; |
| |
| // get() returns the debug object with the given id. |
| // The object must exist and be of type (or derive from type) T. |
| // A returned nullptr represents a None value or type. |
| 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; |
| |
| // 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); |
| |
| std::unordered_map<std::string, std::shared_ptr<vk::dbg::File>> files; |
| bool nextSetLocationIsStep = true; |
| int lastSetLocationLine = 0; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // sw::SpirvShader::Impl::Debugger::State |
| // |
| // State holds the runtime data structures for the shader debug session. |
| //////////////////////////////////////////////////////////////////////////////// |
| 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, vk::dbg::Context::Lock &lock); |
| ~State(); |
| |
| void enter(vk::dbg::Context::Lock &lock, 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 |
| { |
| 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; |
| }; |
| |
| // 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; |
| 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) |
| { |
| auto lock = debugger->ctx->lock(); |
| return new State(debugger, name, lock); |
| } |
| |
| void SpirvShader::Impl::Debugger::State::destroy(State *state) |
| { |
| delete state; |
| } |
| |
| SpirvShader::Impl::Debugger::State::State(const Debugger *debugger, const char *stackBase, vk::dbg::Context::Lock &lock) |
| : debugger(debugger) |
| , thread(lock.currentThread()) |
| , initialThreadDepth(thread->depth()) |
| { |
| enter(lock, 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 = lock.createVariableContainer(); |
| frame.locals->variables->put(laneNames[i], 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(vk::dbg::Context::Lock &lock, const char *name) |
| { |
| thread->enter(lock, 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 = debugger->ctx->lock().createVariableContainer(); |
| vc->put(tostring(key), 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) |
| { |
| auto lock = debugger->ctx->lock(); |
| vc->put(tostring(key), type->value(lock, 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 = lock.createVariableContainer(); |
| s.localsByLane[i] = locals; |
| s.locals->variables->put(laneNames[i], 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 lock = debugger->ctx->lock(); |
| auto thread = 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(lock, 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; |
| |
| private: |
| Ptr state; |
| Ptr ptr; |
| bool valid = false; |
| }; |
| |
| 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 |
| //////////////////////////////////////////////////////////////////////////////// |
| 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, std::unique_ptr<debug::Object>(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 OpenCLDebugInfo100DebugInfoNone: |
| if(pass == Pass::Define) |
| { |
| addNone(debug::Object::ID(insn.word(2))); |
| } |
| break; |
| 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 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->dimensions.emplace_back(shader->GetConstScalarInt(insn.word(i))); |
| } |
| }); |
| 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 = isNone(insn.word(12)) ? 0 : 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 OpenCLDebugInfo100DebugTypeTemplate: |
| defineOrEmit(insn, pass, [&](debug::TemplateType *tpl) { |
| tpl->target = get(debug::Type::ID(insn.word(5))); |
| for(size_t i = 6, c = insn.wordCount(); i < c; i++) |
| { |
| tpl->parameters.emplace_back(get(debug::TemplateParameter::ID(insn.word(i)))); |
| } |
| }); |
| break; |
| case OpenCLDebugInfo100DebugTypeTemplateParameter: |
| defineOrEmit(insn, pass, [&](debug::TemplateParameter *param) { |
| param->name = shader->getString(insn.word(5)); |
| param->type = get(debug::Type::ID(insn.word(6))); |
| param->value = 0; // TODO: Get value from OpConstant if "a template value parameter". |
| param->source = get(debug::Source::ID(insn.word(8))); |
| param->line = insn.word(9); |
| param->column = insn.word(10); |
| }); |
| break; |
| case OpenCLDebugInfo100DebugGlobalVariable: |
| defineOrEmit(insn, pass, [&](debug::GlobalVariable *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))); |
| var->linkage = shader->getString(insn.word(11)); |
| 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: |
| 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::Scope::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) |
| |
| rr::Call(&State::createScope, state->routine->dbgState, 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)); |
| } |
| |
| rr::Call(&State::createScope, state->routine->dbgState, scope); |
| }); |
| break; |
| case OpenCLDebugInfo100DebugScope: |
| defineOrEmit(insn, pass, [&](debug::SourceScope *ss) { |
| ss->scope = get(debug::Scope::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 OpenCLDebugInfo100DebugInlinedAt: |
| defineOrEmit(insn, pass, [&](debug::InlinedAt *ia) { |
| ia->line = insn.word(5); |
| ia->scope = get(debug::Scope::ID(insn.word(6))); |
| if(insn.wordCount() > 7) |
| { |
| ia->inlined = get(debug::InlinedAt::ID(insn.word(7))); |
| } |
| }); |
| 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; |
| files.emplace(source->file.c_str(), file); |
| } |
| else |
| { |
| auto file = dbg->ctx->lock().createPhysicalFile(source->file.c_str()); |
| source->dbgFile = file; |
| files.emplace(source->file.c_str(), file); |
| } |
| }); |
| break; |
| case OpenCLDebugInfo100DebugOperation: |
| defineOrEmit(insn, pass, [&](debug::Operation *operation) { |
| operation->opcode = insn.word(5); |
| for(uint32_t i = 6; i < insn.wordCount(); i++) |
| { |
| operation->operands.push_back(insn.word(i)); |
| } |
| }); |
| break; |
| |
| case OpenCLDebugInfo100DebugTypePointer: |
| case OpenCLDebugInfo100DebugTypeQualifier: |
| case OpenCLDebugInfo100DebugTypedef: |
| case OpenCLDebugInfo100DebugTypeEnum: |
| case OpenCLDebugInfo100DebugTypeInheritance: |
| case OpenCLDebugInfo100DebugTypePtrToMember: |
| case OpenCLDebugInfo100DebugTypeTemplateTemplateParameter: |
| case OpenCLDebugInfo100DebugTypeTemplateParameterPack: |
| case OpenCLDebugInfo100DebugFunctionDeclaration: |
| case OpenCLDebugInfo100DebugLexicalBlockDiscriminator: |
| case OpenCLDebugInfo100DebugInlinedVariable: |
| case OpenCLDebugInfo100DebugMacroDef: |
| case OpenCLDebugInfo100DebugMacroUndef: |
| case OpenCLDebugInfo100DebugImportedEntity: |
| UNIMPLEMENTED("b/148401179 OpenCLDebugInfo100 instruction %d", int(extInstIndex)); |
| break; |
| default: |
| UNSUPPORTED("OpenCLDebugInfo100 instruction %d", int(extInstIndex)); |
| } |
| } |
| |
| 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) |
| { |
| ASSERT_MSG(obj != nullptr, "add() called with nullptr obj"); |
| bool added = objects.emplace(debug::Object::ID(id.value()), std::move(obj)).second; |
| ASSERT_MSG(added, "Debug object with %d already exists", id.value()); |
| } |
| |
| void SpirvShader::Impl::Debugger::addNone(debug::Object::ID id) |
| { |
| bool added = objects.emplace(debug::Object::ID(id.value()), nullptr).second; |
| ASSERT_MSG(added, "Debug object with %d already exists", id.value()); |
| } |
| |
| bool SpirvShader::Impl::Debugger::isNone(debug::Object::ID id) const |
| { |
| auto it = objects.find(debug::Object::ID(id.value())); |
| if(it == objects.end()) { return false; } |
| return it->second.get() == nullptr; |
| } |
| |
| 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: %s, want: %s", |
| id.value(), cstr(it->second->kind), cstr(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, scope).group<Key>(key); |
| for(int lane = 0; lane < SIMD::Width; lane++) |
| { |
| exposeVariable(shader, Group::localsLane(dbgState, scope, lane), lane, key, type, id, state); |
| exposeVariable(shader, hover, lane, laneNames[lane], type, id, state); |
| } |
| } |
| |
| 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 |
| { |
| auto &obj = shader->getObject(id); |
| |
| if(type != nullptr) |
| { |
| 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); |
| } |
| return; |
| } |
| |
| // No debug type information. Derive from SPIR-V. |
| switch(shader->getType(obj).opcode()) |
| { |
| 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) |
| { |
| 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 vec = group.group<Key>(key); |
| for(uint32_t i = 0; i < count; i++) |
| { |
| vec.template put<int, float>(i, Extract(val.Float(i), lane)); |
| } |
| } |
| break; |
| } |
| } |
| break; |
| case spv::OpTypePointer: |
| { |
| 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)); |
| }); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // sw::SpirvShader |
| //////////////////////////////////////////////////////////////////////////////// |
| void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &dbgctx) |
| { |
| impl.debugger = new Impl::Debugger(); |
| impl.debugger->ctx = dbgctx; |
| } |
| |
| void SpirvShader::dbgTerm() |
| { |
| if(impl.debugger) |
| { |
| delete impl.debugger; |
| } |
| } |
| |
| void SpirvShader::dbgCreateFile() |
| { |
| auto dbg = impl.debugger; |
| if(!dbg) { return; } |
| |
| int currentLine = 1; |
| std::string source; |
| for(auto insn : *this) |
| { |
| auto instruction = spvtools::spvInstructionBinaryToText( |
| SPV_ENV_VULKAN_1_1, |
| insn.wordPointer(0), |
| insn.wordCount(), |
| insns.data(), |
| insns.size(), |
| SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) + |
| "\n"; |
| dbg->spirvLineMappings[insn.wordPointer(0)] = currentLine; |
| currentLine += std::count(instruction.begin(), instruction.end(), '\n'); |
| source += instruction; |
| } |
| std::string name; |
| switch(executionModel) |
| { |
| case spv::ExecutionModelVertex: name = "VertexShader"; break; |
| case spv::ExecutionModelFragment: name = "FragmentShader"; break; |
| case spv::ExecutionModelGLCompute: name = "ComputeShader"; break; |
| default: name = "SPIR-V Shader"; break; |
| } |
| static std::atomic<int> id = { 0 }; |
| name += tostring(id++) + ".spvasm"; |
| dbg->spirvFile = dbg->ctx->lock().createVirtualFile(name.c_str(), source.c_str()); |
| } |
| |
| void SpirvShader::dbgBeginEmit(EmitState *state) const |
| { |
| auto dbg = impl.debugger; |
| if(!dbg) { return; } |
| |
| using Group = Impl::Debugger::Group; |
| |
| 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); |
| |
| routine->dbgState = dbgState; |
| |
| SetActiveLaneMask(state->activeLaneMask(), state); |
| |
| for(int i = 0; i < SIMD::Width; i++) |
| { |
| auto globals = Group::globals(dbgState, i); |
| globals.put<const char *, int>("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)); |
| 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)); |
| 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)); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| void SpirvShader::dbgEndEmit(EmitState *state) const |
| { |
| auto dbg = impl.debugger; |
| if(!dbg) { return; } |
| |
| rr::Call(&Impl::Debugger::State::destroy, state->routine->dbgState); |
| } |
| |
| void SpirvShader::dbgBeginEmitInstruction(InsnIterator insn, EmitState *state) const |
| { |
| # if PRINT_EACH_PROCESSED_INSTRUCTION |
| { |
| auto instruction = spvtools::spvInstructionBinaryToText( |
| SPV_ENV_VULKAN_1_1, |
| insn.wordPointer(0), |
| insn.wordCount(), |
| insns.data(), |
| insns.size(), |
| SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); |
| printf("%s\n", instruction.c_str()); |
| } |
| # endif // PRINT_EACH_PROCESSED_INSTRUCTION |
| |
| # if PRINT_EACH_EXECUTED_INSTRUCTION |
| { |
| auto instruction = spvtools::spvInstructionBinaryToText( |
| SPV_ENV_VULKAN_1_1, |
| insn.wordPointer(0), |
| insn.wordCount(), |
| insns.data(), |
| insns.size(), |
| SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); |
| rr::Print("{0}\n", instruction); |
| } |
| # endif // PRINT_EACH_EXECUTED_INSTRUCTION |
| |
| // Only single line step over statement instructions. |
| |
| if(auto dbg = impl.debugger) |
| { |
| if(insn.opcode() == spv::OpLabel) |
| { |
| // Whenever we hit a label, force the next OpLine to be steppable. |
| // This handles the case where we have control flow on the same line |
| // For example: |
| // while(true) { foo(); } |
| // foo() should be repeatedly steppable. |
| dbg->setNextSetLocationIsStep(); |
| } |
| |
| if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0) |
| { |
| // 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); |
| } |
| } |
| } |
| } |
| |
| void SpirvShader::dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const |
| { |
| auto dbg = impl.debugger; |
| if(!dbg) { return; } |
| |
| // Don't display SSA values if rich debug info is available |
| if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0) |
| { |
| // 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()) |
| { |
| auto id = resIt->second; |
| dbgExposeIntermediate(id, state); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| void SpirvShader::dbgDeclareResult(const InsnIterator &insn, Object::ID resultId) const |
| { |
| auto dbg = impl.debugger; |
| if(!dbg) { return; } |
| |
| dbg->results.emplace(insn.wordPointer(0), resultId); |
| } |
| |
| SpirvShader::EmitResult SpirvShader::EmitLine(InsnIterator insn, EmitState *state) const |
| { |
| if(auto dbg = impl.debugger) |
| { |
| auto path = getString(insn.word(1)); |
| auto line = insn.word(2); |
| auto column = insn.word(3); |
| dbg->setLocation(state, path, line, column); |
| } |
| return EmitResult::Continue; |
| } |
| |
| 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; |
| } |
| |
| } // namespace sw |
| |
| #else // ENABLE_VK_DEBUGGER |
| |
| // Stub implementations of the dbgXXX functions. |
| namespace sw { |
| |
| void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &dbgctx) {} |
| void SpirvShader::dbgTerm() {} |
| void SpirvShader::dbgCreateFile() {} |
| void SpirvShader::dbgBeginEmit(EmitState *state) const {} |
| void SpirvShader::dbgEndEmit(EmitState *state) const {} |
| void SpirvShader::dbgBeginEmitInstruction(InsnIterator insn, EmitState *state) const {} |
| void SpirvShader::dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const {} |
| void SpirvShader::dbgExposeIntermediate(Object::ID id, EmitState *state) const {} |
| void SpirvShader::dbgUpdateActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const {} |
| void SpirvShader::dbgDeclareResult(const InsnIterator &insn, Object::ID resultId) const {} |
| |
| void SpirvShader::DefineOpenCLDebugInfo100(const InsnIterator &insn) {} |
| |
| SpirvShader::EmitResult SpirvShader::EmitOpenCLDebugInfo100(InsnIterator insn, EmitState *state) const |
| { |
| return EmitResult::Continue; |
| } |
| |
| SpirvShader::EmitResult SpirvShader::EmitLine(InsnIterator insn, EmitState *state) const |
| { |
| return EmitResult::Continue; |
| } |
| |
| } // namespace sw |
| |
| #endif // ENABLE_VK_DEBUGGER |