blob: ab596f78b16d39851767f2151bad9e383800329c [file] [log] [blame]
// 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"
#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-tools/libspirv.h"
# include <algorithm>
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(const char *s)
{
return s;
}
std::string tostring(sw::SpirvShader::Object::ID id)
{
return "%" + std::to_string(id.value());
}
} // 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;
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
// using the specified key.
template<typename Key>
void exposeVariable(
const SpirvShader *shader,
const Key &key,
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,
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:
std::unordered_map<std::string, vk::dbg::File::ID> fileIDs;
};
////////////////////////////////////////////////////////////////////////////////
// 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 update(vk::dbg::File::ID file, int line, int column);
vk::dbg::VariableContainer *locals();
vk::dbg::VariableContainer *hovers();
vk::dbg::VariableContainer *localsLane(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);
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;
};
const Debugger *debugger;
const std::shared_ptr<vk::dbg::Thread> thread;
Scopes threadScopes;
};
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())
{
enter(lock, stackBase);
thread->update([&](vk::dbg::Frame &frame) {
threadScopes.locals = frame.locals;
threadScopes.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]);
}
});
}
SpirvShader::Impl::Debugger::State::~State()
{
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)
{
threadScopes.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
}
void SpirvShader::Impl::Debugger::State::update(vk::dbg::File::ID fileID, int line, int column)
{
auto file = debugger->ctx->lock().get(fileID);
thread->update([&](vk::dbg::Frame &frame) {
frame.location = { file, line, column };
});
}
vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::locals()
{
return threadScopes.locals->variables.get();
}
vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers()
{
return threadScopes.hovers->variables.get();
}
vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(int i)
{
return threadScopes.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));
}
////////////////////////////////////////////////////////////////////////////////
// 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);
static Group locals(Ptr state);
static Group localsLane(Ptr state, int lane);
Group(Ptr state, Ptr group);
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 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;
};
SpirvShader::Impl::Debugger::Group
SpirvShader::Impl::Debugger::Group::hovers(Ptr state)
{
return Group(state, rr::Call(&State::hovers, state));
}
SpirvShader::Impl::Debugger::Group
SpirvShader::Impl::Debugger::Group::locals(Ptr state)
{
return Group(state, rr::Call(&State::locals, state));
}
SpirvShader::Impl::Debugger::Group
SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, int lane)
{
return Group(state, rr::Call(&State::localsLane, state, lane));
}
SpirvShader::Impl::Debugger::Group::Group(Ptr state, Ptr group)
: state(state)
, ptr(group)
{}
template<typename K, typename RK>
SpirvShader::Impl::Debugger::Group SpirvShader::Impl::Debugger::Group::group(RK key) const
{
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
{
rr::Call(&State::putVal<K, V>, state, ptr, key, value);
}
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
////////////////////////////////////////////////////////////////////////////////
void SpirvShader::Impl::Debugger::setPosition(EmitState *state, const std::string &path, uint32_t line, uint32_t column)
{
auto it = fileIDs.find(path);
if(it != fileIDs.end())
{
rr::Call(&State::update, state->routine->dbgState, it->second, line, column);
}
}
template<typename Key>
void SpirvShader::Impl::Debugger::exposeVariable(
const SpirvShader *shader,
const Key &key,
Object::ID id,
EmitState *state) const
{
auto dbgState = state->routine->dbgState;
auto hover = Group::hovers(dbgState).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);
}
}
template<typename Key>
void SpirvShader::Impl::Debugger::exposeVariable(
const SpirvShader *shader,
const Group &group,
int l,
const Key &key,
Object::ID id,
EmitState *state,
int wordOffset /* = 0 */) const
{
GenericValue val(shader, state, id);
switch(shader->getType(val.type).opcode())
{
case spv::OpTypeInt:
{
group.put<Key, int>(key, Extract(val.Int(0), l));
}
break;
case spv::OpTypeFloat:
{
group.put<Key, float>(key, Extract(val.Float(0), l));
}
break;
case spv::OpTypeVector:
{
auto count = shader->getType(val.type).definition.word(3);
switch(count)
{
case 1:
group.put<Key, float>(key, Extract(val.Float(0), l));
break;
case 2:
group.put<Key, float>(key, Extract(val.Float(0), l), Extract(val.Float(1), l));
break;
case 3:
group.put<Key, float>(key, Extract(val.Float(0), l), Extract(val.Float(1), l), Extract(val.Float(2), l));
break;
case 4:
group.put<Key, float>(key, Extract(val.Float(0), l), Extract(val.Float(1), l), Extract(val.Float(2), l), Extract(val.Float(3), l));
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), l));
}
}
break;
}
}
break;
case spv::OpTypePointer:
{
auto objectTy = shader->getType(shader->getObject(id).type);
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, l));
});
}
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 += std::to_string(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);
auto locals = Group::locals(dbgState);
locals.put<const char *, int>("subgroupSize", routine->invocationsPerSubgroup);
switch(executionModel)
{
case spv::ExecutionModelGLCompute:
locals.putVec3<const char *, int>("numWorkgroups", routine->numWorkgroups);
locals.putVec3<const char *, int>("workgroupID", routine->workgroupID);
locals.putVec3<const char *, int>("workgroupSize", routine->workgroupSize);
locals.put<const char *, int>("numSubgroups", routine->subgroupsPerWorkgroup);
locals.put<const char *, int>("subgroupIndex", routine->subgroupIndex);
for(int i = 0; i < SIMD::Width; i++)
{
auto lane = Group::localsLane(dbgState, i);
lane.put<const char *, int>("globalInvocationId",
rr::Extract(routine->globalInvocationID[0], i),
rr::Extract(routine->globalInvocationID[1], i),
rr::Extract(routine->globalInvocationID[2], i));
lane.put<const char *, int>("localInvocationId",
rr::Extract(routine->localInvocationID[0], i),
rr::Extract(routine->localInvocationID[1], i),
rr::Extract(routine->localInvocationID[2], i));
lane.put<const char *, int>("localInvocationIndex", rr::Extract(routine->localInvocationIndex, i));
}
break;
case spv::ExecutionModelFragment:
locals.put<const char *, int>("viewIndex", routine->viewID);
for(int i = 0; i < SIMD::Width; i++)
{
auto lane = Group::localsLane(dbgState, i);
lane.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));
lane.put<const char *, float>("pointCoord",
rr::Extract(routine->pointCoord[0], i),
rr::Extract(routine->pointCoord[1], i));
lane.put<const char *, int>("windowSpacePosition",
rr::Extract(routine->windowSpacePosition[0], i),
rr::Extract(routine->windowSpacePosition[1], i));
lane.put<const char *, int>("helperInvocation", rr::Extract(routine->helperInvocation, 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
{
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);
}
void SpirvShader::dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const
{
auto dbg = impl.debugger;
if(!dbg) { return; }
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, 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->setPosition(state, path, line, column);
}
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 {}
SpirvShader::EmitResult SpirvShader::EmitLine(InsnIterator insn, EmitState *state) const
{
return EmitResult::Continue;
}
} // namespace sw
#endif // ENABLE_VK_DEBUGGER