Commit 779a2f34 by Ben Clayton

SpirvShaderDebugger: Implement DebugValue

This debug instruction partially updates a local variable with SSA values. This is typically used to update a `DebugLocalVariable` of a composite type, which holds structure member offsets from a base address. To handle these, we allocate shadow memory to hold a copy of the entire variable in contiguous memory and have the `DebugLocalVariable` point to this memory. Whenever we encounter a `DebugValue`, we copy the necessary fields to the shadow memory. This approach is simplier and more performant than attempting to fiddle with `vk::dbg::Value`s for each `DebugValue`. Bug: b/148401179 Change-Id: I6aafce1f7553f4a3a97bbf84e6e9dac97ce71a68 Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48429Reviewed-by: 's avatarJaebaek Seo <jaebaek@google.com> Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com> Kokoro-Result: kokoro <noreply+kokoro@google.com> Tested-by: 's avatarBen Clayton <bclayton@google.com>
parent 6740e07a
......@@ -33,6 +33,7 @@
# include "spirv-tools/libspirv.h"
# include <algorithm>
# include <queue>
namespace {
......@@ -52,6 +53,14 @@ struct ArgTy<R (C::*)(Arg) const>
template<typename T>
using ArgTyT = typename ArgTy<T>::type;
template<typename T>
T take(std::queue<T> &queue)
{
auto v = queue.front();
queue.pop();
return v;
}
} // anonymous namespace
namespace spvtools {
......@@ -256,12 +265,26 @@ struct Type : public Object
kind == Kind::TemplateType;
}
std::pair<const Type *, uint32_t> index(std::queue<uint32_t> &&indices) const
{
if(indices.size() == 0)
{
return { this, 0 };
}
return indexMember(std::move(indices));
}
// 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(void *ptr, bool interleaved) const = 0;
protected:
// indexMember() returns the nested inner element debug type and byte offset
// from the base of this type, using the list of indices.
virtual std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&) const = 0;
};
struct CompilationUnit : ObjectImpl<CompilationUnit, Scope, Object::Kind::CompilationUnit>
......@@ -286,6 +309,12 @@ struct BasicType : ObjectImpl<BasicType, Type, Object::Kind::BasicType>
uint32_t sizeInBytes() const override { return size / 8; }
std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&) const override
{
DABORT("indexMember() called on BasicType %s", name.c_str());
return {};
}
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
switch(encoding)
......@@ -397,6 +426,24 @@ struct ArrayType : ObjectImpl<ArrayType, Type, Object::Kind::ArrayType>
return numBytes;
}
std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
{
std::vector<uint32_t> arrIndices(dimensions.size());
for(size_t i = 0; i < dimensions.size(); i++)
{
arrIndices[i] = take(indices);
}
auto out = base->index(std::move(indices));
auto stride = base->sizeInBytes();
for(int i = static_cast<int>(dimensions.size()) - 1; i >= 0; i--)
{
out.second += arrIndices[i] * stride;
stride *= dimensions[i];
}
return out;
}
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
auto vc = std::make_shared<vk::dbg::VariableContainer>();
......@@ -431,6 +478,14 @@ struct VectorType : ObjectImpl<VectorType, Type, Object::Kind::VectorType>
return base->sizeInBytes() * components;
}
std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
{
auto idx = take(indices);
auto out = base->index(std::move(indices));
out.second += base->sizeInBytes() * idx;
return out;
}
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
const auto elSize = base->sizeInBytes();
......@@ -456,6 +511,11 @@ struct FunctionType : ObjectImpl<FunctionType, Type, Object::Kind::FunctionType>
std::vector<Type *> paramTys;
uint32_t sizeInBytes() const override { return 0; }
std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
{
DABORT("indexMember() called on FunctionType");
return {};
}
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override { return nullptr; }
};
......@@ -487,6 +547,15 @@ struct CompositeType : ObjectImpl<CompositeType, Type, Object::Kind::CompositeTy
uint32_t sizeInBytes() const override { return size / 8; }
std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
{
auto idx = take(indices);
auto member = members[idx];
auto out = member->type->index(std::move(indices));
out.second += member->offset / 8;
return out;
}
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
auto vc = std::make_shared<vk::dbg::VariableContainer>();
......@@ -520,6 +589,10 @@ struct TemplateType : ObjectImpl<TemplateType, Type, Object::Kind::TemplateType>
std::vector<TemplateParameter *> parameters;
uint32_t sizeInBytes() const override { return target->sizeInBytes(); }
std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
{
return target->index(std::move(indices));
}
std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
{
return target->value(ptr, interleaved);
......@@ -605,7 +678,7 @@ struct Declare : ObjectImpl<Declare, Object, Object::Kind::Declare>
struct Value : ObjectImpl<Value, Object, Object::Kind::Value>
{
LocalVariable *local = nullptr;
sw::SpirvShader::Object::ID variable;
sw::SpirvShader::Object::ID value;
Expression *expression = nullptr;
std::vector<uint32_t> indexes;
};
......@@ -658,6 +731,7 @@ namespace sw {
// sw::SpirvShader::Impl::Debugger
//
// Private struct holding debugger information for the SpirvShader.
// There is an instance of this class per shader program.
////////////////////////////////////////////////////////////////////////////////
struct SpirvShader::Impl::Debugger
{
......@@ -676,6 +750,13 @@ struct SpirvShader::Impl::Debugger
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);
// foreachLane() calls f for each debugger group representing the SIMD
// lanes of execution.
// FUNC is a function with the signature:
// (int lane, const Group &group, auto &key)
template<typename Key, typename Func>
void foreachLane(const Key &key, const debug::Scope *scope, EmitState *state, Func &&f) const;
// exposeVariable exposes the variable with the given ID to the debugger
// using the specified key.
template<typename Key>
......@@ -705,6 +786,17 @@ struct SpirvShader::Impl::Debugger
std::unordered_map<const void *, int> spirvLineMappings; // instruction pointer to line
std::unordered_map<const void *, Object::ID> results; // instruction pointer to result ID
// Shadow memory is used to construct a contiguous memory block for local
// variables that may be formed from multiple SSA values.
struct Shadow
{
// Offset in the shadow memory allocation for the given local variable.
std::unordered_map<debug::LocalVariable *, uint32_t> offsets;
// Total size of the shadow memory in bytes.
uint32_t size;
} shadow;
private:
// add() registers the debug object with the given id.
template<typename ID>
......@@ -754,6 +846,7 @@ private:
// sw::SpirvShader::Impl::Debugger::State
//
// State holds the runtime data structures for the shader debug session.
// There is an instance of this class per shader invocation.
////////////////////////////////////////////////////////////////////////////////
class SpirvShader::Impl::Debugger::State
{
......@@ -800,6 +893,7 @@ public:
const Debugger *debugger;
const std::shared_ptr<vk::dbg::Thread> thread;
std::unique_ptr<uint8_t[]> const shadow;
std::unordered_map<const debug::Scope *, Scopes> scopes;
Scopes globals; // Scope for globals.
debug::SourceScope *srcScope = nullptr; // Current source scope.
......@@ -821,6 +915,7 @@ void SpirvShader::Impl::Debugger::State::destroy(State *state)
SpirvShader::Impl::Debugger::State::State(const Debugger *debugger, const char *stackBase, vk::dbg::Context::Lock &lock)
: debugger(debugger)
, thread(lock.currentThread())
, shadow(new uint8_t[debugger->shadow.size])
, initialThreadDepth(thread->depth())
{
enter(lock, stackBase);
......@@ -1371,11 +1466,92 @@ void SpirvShader::Impl::Debugger::process(const SpirvShader *shader, const InsnI
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->value = 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));
auto idx = shader->GetConstScalarInt(insn.word(i));
value->indexes.push_back(idx);
}
// DebugValue partially updates a DebugLocalVariable with an SSA
// value. This is typically used to update a DebugLocalVariable
// of a composite type, which holds structure member offsets
// from a base address.
// To handle these, we allocate shadow memory to hold a copy of
// the entire variable in contiguous memory and have the
// DebugLocalVariable point to this memory. Whenever we
// encounter a DebugValue, we copy the necessary fields to the
// shadow memory.
// type of the full DebugLocalVariable.
auto type = value->local->type;
// base address of the variable.
// Start by pointing base to the root of the shadow memory.
// This will be offset to the variable, then the member within
// the variable below.
SIMD::Pointer base(*Pointer<Pointer<Byte>>(state->routine->dbgState + OFFSET(State, shadow)), shadow.size);
// All variables are considered local, and therefore
// interleaved.
base = InterleaveByLane(base);
// Have we already allocated shadow memory for this variable?
auto it = shadow.offsets.find(value->local);
if(it == shadow.offsets.end())
{
// No shadow memory has been allocated for this local
// variable yet.
// Allocate the memory for the variable.
auto offset = shadow.size;
shadow.offsets.emplace(value->local, offset);
auto size = type->sizeInBytes() * SIMD::Width;
base += offset;
shadow.size += size;
// Expose the variable.
auto name = value->local->name.c_str();
auto scope = value->local->parent;
auto offsets = base.offsets();
foreachLane(name, scope, state, [&](int lane, const Group &group, auto &key) {
auto ptr = base.base + Extract(offsets, lane);
group.putPtr<const char *>(name, ptr, true, value->local->type);
});
}
else
{
// Shadow memory already allocated for this variable.
// Offset base to point to it.
base += it->second;
}
// Find the byte offset on the indexed member of the variable.
std::queue<uint32_t> indices;
for(auto idx : value->indexes)
{
indices.emplace(idx);
}
auto offset = type->index(std::move(indices)).second;
// Update base to point to the particular member.
base += offset;
// Now copy the updated value into shadow memory representation
// of the variable.
// TODO(b/148401179): This assumes tight packing of all
// components, which may not match with the debug structure
// layout.
auto &valObject = shader->getObject(value->value);
auto &valType = shader->getType(valObject);
for(auto i = 0u; i < valType.componentCount; i++)
{
auto val = Operand(shader, state, value->value).Int(i);
auto dst = base + i * sizeof(uint32_t) * SIMD::Width;
// Use RobustBufferAccess as the size as described by the
// debug type may be smaller than the true SSA size.
dst.Store(val, sw::OutOfBoundsBehavior::RobustBufferAccess, state->activeLaneMask());
}
});
break;
......@@ -1505,6 +1681,22 @@ T *SpirvShader::Impl::Debugger::getOrNull(SpirvID<T> id) const
return ptr;
}
template<typename Key, typename Func>
void SpirvShader::Impl::Debugger::foreachLane(
const Key &key,
const debug::Scope *scope,
EmitState *state,
Func &&f) const
{
auto dbgState = state->routine->dbgState;
auto hover = Group::hovers(dbgState, scope).group<Key>(key);
for(int lane = 0; lane < SIMD::Width; lane++)
{
f(lane, Group::localsLane(dbgState, scope, lane), key);
f(lane, hover, laneNames[lane]);
}
}
template<typename Key>
void SpirvShader::Impl::Debugger::exposeVariable(
const SpirvShader *shader,
......@@ -1514,13 +1706,9 @@ void SpirvShader::Impl::Debugger::exposeVariable(
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);
}
foreachLane(key, scope, state, [&](int lane, const Group &group, auto &key) {
exposeVariable(shader, group, lane, laneNames[lane], type, id, state);
});
}
template<typename Key>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment