Fix some differences with DXC's disassembly

* We also add an option to tweak our disassembly output to be more easily
  compared with dxc's
This commit is contained in:
baldurk
2021-09-09 12:35:19 +01:00
parent a42401c957
commit 44eee83aa2
5 changed files with 184 additions and 80 deletions
+58 -23
View File
@@ -28,7 +28,6 @@
#include <string>
#include "common/common.h"
#include "common/formatting.h"
#include "maths/half_convert.h"
#include "os/os_specific.h"
#include "llvm_decoder.h"
@@ -277,12 +276,7 @@ void ParseConstant(const LLVMBC::BlockOrRecord &constant, const Type *&curType,
{
Constant v;
v.type = curType;
if(curType->bitWidth == 16)
v.val.f32v[0] = ConvertFromHalf(uint16_t(constant.ops[0] & 0xffff));
else if(curType->bitWidth == 32)
memcpy(&v.val.f32v[0], &constant.ops[0], sizeof(float));
else
memcpy(&v.val.f64v[0], &constant.ops[0], sizeof(double));
memcpy(&v.val.f64v[0], &constant.ops[0], curType->bitWidth / 8);
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::STRING) ||
@@ -563,6 +557,39 @@ Program::Program(const byte *bytes, size_t length)
if(rootchild.ops[1] & 0x1)
g.flags |= GlobalFlags::IsConst;
if(rootchild.ops[2])
g.initialiser = Symbol(SymbolType::Constant, rootchild.ops[2] - 1);
switch(rootchild.ops[3])
{
case 0: g.flags |= GlobalFlags::ExternalLinkage; break;
case 16: g.flags |= GlobalFlags::WeakAnyLinkage; break;
case 2: g.flags |= GlobalFlags::AppendingLinkage; break;
case 3: g.flags |= GlobalFlags::InternalLinkage; break;
case 18: g.flags |= GlobalFlags::LinkOnceAnyLinkage; break;
case 7: g.flags |= GlobalFlags::ExternalWeakLinkage; break;
case 8: g.flags |= GlobalFlags::CommonLinkage; break;
case 9: g.flags |= GlobalFlags::PrivateLinkage; break;
case 17: g.flags |= GlobalFlags::WeakODRLinkage; break;
case 19: g.flags |= GlobalFlags::LinkOnceODRLinkage; break;
case 12: g.flags |= GlobalFlags::AvailableExternallyLinkage; break;
default: break;
}
g.align = (1ULL << rootchild.ops[4]) >> 1;
g.section = int32_t(rootchild.ops[5]) - 1;
if(rootchild.ops.size() > 6)
{
RDCASSERTMSG("global has non-default visibility", rootchild.ops[6] == 0);
}
if(rootchild.ops.size() > 7)
{
RDCASSERTMSG("global has non-default TLS mode", rootchild.ops[7] == 0);
}
if(rootchild.ops.size() > 8)
{
if(rootchild.ops[8] == 1)
@@ -571,23 +598,22 @@ Program::Program(const byte *bytes, size_t length)
g.flags |= GlobalFlags::LocalUnnamedAddr;
}
if(rootchild.ops[2])
g.initialiser = Symbol(SymbolType::Constant, rootchild.ops[2] - 1);
switch(rootchild.ops[3])
if(rootchild.ops.size() > 9)
{
case 0:
case 5:
case 6:
case 7:
case 15: g.flags |= GlobalFlags::IsExternal; break;
case 2: g.flags |= GlobalFlags::IsAppending; break;
default: break;
if(rootchild.ops[9])
g.flags |= GlobalFlags::ExternallyInitialised;
}
g.align = (1ULL << rootchild.ops[4]) >> 1;
if(rootchild.ops.size() > 10)
{
RDCASSERTMSG("global has non-default DLL storage class", rootchild.ops[10] == 0);
}
g.section = int32_t(rootchild.ops[5]) - 1;
if(rootchild.ops.size() > 11)
{
// assume no comdat
RDCASSERTMSG("global has comdat", rootchild.ops[11] == 0);
}
// symbols refer into any of N types in declaration order
m_Symbols.push_back({SymbolType::GlobalVar, m_GlobalVars.size()});
@@ -610,16 +636,23 @@ Program::Program(const byte *bytes, size_t length)
else if(IS_KNOWN(rootchild.id, ModuleRecord::FUNCTION))
{
// [type, callingconv, isproto, linkage, paramattrs, alignment, section, visibility, gc,
// unnamed_addr]
// unnamed_addr, prologuedata, dllstorageclass, comdat, prefixdata]
Function f;
f.funcType = &m_Types[(size_t)rootchild.ops[0]];
// ignore callingconv
RDCASSERTMSG("Calling convention is non-default", rootchild.ops[1] == 0);
f.external = (rootchild.ops[2] != 0);
// ignore linkage
RDCASSERTMSG("Linkage is non-default", rootchild.ops[3] == 0);
if(rootchild.ops[4] > 0 && rootchild.ops[4] - 1 < m_Attributes.size())
f.attrs = &m_Attributes[(size_t)rootchild.ops[4] - 1];
// ignore rest of properties
f.align = rootchild.ops[5];
// ignore rest of properties, assert that if present they are 0
for(size_t p = 6; p < rootchild.ops.size(); p++)
RDCASSERT(rootchild.ops[p] == 0, p, rootchild.ops[p]);
// symbols refer into any of N types in declaration order
m_Symbols.push_back({SymbolType::Function, m_Functions.size()});
@@ -1394,7 +1427,9 @@ Program::Program(const byte *bytes, size_t length)
{
Instruction inst;
inst.op = Operation::Call;
inst.paramAttrs = &m_Attributes[op.get<size_t>()];
size_t attr = op.get<size_t>();
if(attr > 0)
inst.paramAttrs = &m_Attributes[attr - 1];
uint64_t callingFlags = op.get<uint64_t>();
+20 -5
View File
@@ -117,11 +117,24 @@ struct Symbol
enum class GlobalFlags : uint32_t
{
NoFlags = 0,
IsConst = 0x1,
IsExternal = 0x2,
LocalUnnamedAddr = 0x4,
GlobalUnnamedAddr = 0x8,
IsAppending = 0x10,
ExternalLinkage = 1,
AvailableExternallyLinkage = 2,
LinkOnceAnyLinkage = 3,
LinkOnceODRLinkage = 4,
WeakAnyLinkage = 5,
WeakODRLinkage = 6,
AppendingLinkage = 7,
InternalLinkage = 8,
PrivateLinkage = 9,
ExternalWeakLinkage = 10,
CommonLinkage = 11,
LinkageMask = 0xf,
IsConst = 0x10,
IsExternal = 0x20,
LocalUnnamedAddr = 0x40,
GlobalUnnamedAddr = 0x80,
IsAppending = 0x100,
ExternallyInitialised = 0x200,
};
BITMASK_OPERATORS(GlobalFlags);
@@ -483,6 +496,8 @@ struct Function
bool external = false;
const Attributes *attrs = NULL;
uint64_t align = 0;
rdcarray<Instruction> args;
rdcarray<Instruction> instructions;
@@ -26,9 +26,18 @@
#include <stdlib.h>
#include <algorithm>
#include "common/formatting.h"
#include "maths/half_convert.h"
#include "dxil_bytecode.h"
#include "dxil_common.h"
#define DXC_COMPATIBLE_DISASM OPTION_OFF
#if ENABLED(DXC_COMPATIBLE_DISASM) && ENABLED(RDOC_RELEASE)
#error "DXC compatible disassembly should only be enabled in debug builds for testing"
#endif
namespace DXIL
{
struct TypeOrderer
@@ -441,21 +450,31 @@ void Program::MakeDisassemblyString()
const GlobalVar &g = m_GlobalVars[i];
m_Disassembly += StringFormat::Fmt("@%s = ", escapeStringIfNeeded(g.name).c_str());
if(g.initialiser.type != SymbolType::Constant)
switch(g.flags & GlobalFlags::LinkageMask)
{
if(g.flags & GlobalFlags::IsExternal)
m_Disassembly += "external ";
case GlobalFlags::ExternalLinkage:
if(g.initialiser.type != SymbolType::Constant)
m_Disassembly += "external ";
break;
case GlobalFlags::PrivateLinkage: m_Disassembly += "private "; break;
case GlobalFlags::InternalLinkage: m_Disassembly += "internal "; break;
case GlobalFlags::LinkOnceAnyLinkage: m_Disassembly += "linkonce "; break;
case GlobalFlags::LinkOnceODRLinkage: m_Disassembly += "linkonce_odr "; break;
case GlobalFlags::WeakAnyLinkage: m_Disassembly += "weak "; break;
case GlobalFlags::WeakODRLinkage: m_Disassembly += "weak_odr "; break;
case GlobalFlags::CommonLinkage: m_Disassembly += "common "; break;
case GlobalFlags::AppendingLinkage: m_Disassembly += "appending "; break;
case GlobalFlags::ExternalWeakLinkage: m_Disassembly += "extern_weak "; break;
case GlobalFlags::AvailableExternallyLinkage: m_Disassembly += "available_externally "; break;
default: break;
}
if(g.flags & GlobalFlags::IsAppending)
m_Disassembly += "appending ";
else if(!(g.flags & GlobalFlags::IsExternal))
m_Disassembly += "internal ";
if(g.type->addrSpace)
m_Disassembly += StringFormat::Fmt("addrspace(%d) ", g.type->addrSpace);
if(g.flags & GlobalFlags::LocalUnnamedAddr)
m_Disassembly += "local_unnamed_addr ";
else if(g.flags & GlobalFlags::GlobalUnnamedAddr)
m_Disassembly += "unnamed_addr ";
if(g.type->addrSpace != Type::PointerAddrSpace::Default)
m_Disassembly += StringFormat::Fmt("addrspace(%d) ", g.type->addrSpace);
if(g.flags & GlobalFlags::IsConst)
m_Disassembly += "constant ";
else
@@ -527,11 +546,16 @@ void Program::MakeDisassemblyString()
if(m.isConstant && m.constant && m.constant->symbol)
ret += m.constant->toString(withTypes);
else if(m.isConstant && m.constant &&
(m.constant->type->type == Type::Scalar || m.constant->type->type == Type::Vector ||
(m.constant->type->type == Type::Scalar ||
m.constant->type->type == Type::Vector || m.constant->undef ||
m.constant->nullconst || m.constant->type->name.beginsWith("class.matrix.")))
{
ret += m.constant->toString(withTypes);
}
else
{
ret += StringFormat::Fmt("!%u", GetOrAssignMetaID(&m));
}
}
else
{
@@ -638,6 +662,8 @@ void Program::MakeDisassemblyString()
}
m_Disassembly += ")";
debugCall = inst.funcCall->name.beginsWith("llvm.dbg.");
if(inst.paramAttrs)
m_Disassembly += StringFormat::Fmt(" #%u", inst.paramAttrs - m_Attributes.begin());
break;
}
case Operation::Trunc:
@@ -1365,6 +1391,12 @@ void Program::MakeDisassemblyString()
labelName.push_back(' ');
labelName += "; preds = ";
#if ENABLED(DXC_COMPATIBLE_DISASM)
// unfortunately due to how llvm/dxc packs its preds, this is not feasible to replicate so
// instead we omit the pred list entirely and dxc's output needs to be regex replaced to
// match
labelName += "...";
#else
bool first = true;
for(const Block *pred : func.blocks[curBlock].preds)
{
@@ -1376,6 +1408,7 @@ void Program::MakeDisassemblyString()
else
labelName += "%" + escapeStringIfNeeded(pred->name);
}
#endif
m_Disassembly += labelName;
m_Disassembly += "\n";
@@ -1408,9 +1441,15 @@ void Program::MakeDisassemblyString()
{
if(numIdx < m_NumberedMeta.size() && m_NumberedMeta[numIdx]->id == i)
{
m_Disassembly += StringFormat::Fmt("!%u = %s%s\n", i,
m_NumberedMeta[numIdx]->isDistinct ? "distinct " : "",
m_NumberedMeta[numIdx]->valString().c_str());
rdcstr metaline = StringFormat::Fmt("!%u = %s%s\n", i,
m_NumberedMeta[numIdx]->isDistinct ? "distinct " : "",
m_NumberedMeta[numIdx]->valString().c_str());
#if ENABLED(DXC_COMPATIBLE_DISASM)
for(size_t c = 0; c < metaline.size(); c += 4096)
m_Disassembly += metaline.substr(c, 4096);
#else
m_Disassembly += metaline;
#endif
if(m_NumberedMeta[numIdx]->dwarf)
m_NumberedMeta[numIdx]->dwarf->setID(i);
numIdx++;
@@ -1555,8 +1594,10 @@ rdcstr Metadata::refString() const
rdcstr DebugLocation::toString() const
{
rdcstr ret = StringFormat::Fmt("!DILocation(line: %llu, column: %llu, scope: %s", line, col,
scope ? scope->refString().c_str() : "null");
rdcstr ret = StringFormat::Fmt("!DILocation(line: %llu", line);
if(col)
ret += StringFormat::Fmt(", column: %llu", col);
ret += StringFormat::Fmt(", scope: %s", scope ? scope->refString().c_str() : "null");
if(inlinedAt)
ret += StringFormat::Fmt(", inlinedAt: %s", inlinedAt->refString().c_str());
ret += ")";
@@ -1577,6 +1618,9 @@ rdcstr Metadata::valString() const
{
if(type == NULL)
{
// don't truncate here for dxc-compatible disassembly, instead we wrap at 4096 columns at a higher
// level
#if DISABLED(DXC_COMPATIBLE_DISASM)
// truncate very long strings - most likely these are shader source
if(str.length() > 400)
{
@@ -1585,10 +1629,9 @@ rdcstr Metadata::valString() const
trunc.insert(200, "...");
return StringFormat::Fmt("!%s", escapeString(trunc).c_str());
}
else
{
return StringFormat::Fmt("!%s", escapeString(str).c_str());
}
#endif
return StringFormat::Fmt("!%s", escapeString(str).c_str());
}
else
{
@@ -1637,6 +1680,43 @@ rdcstr Metadata::valString() const
}
}
static void floatAppendToString(const Type *t, const ShaderValue &val, uint32_t i, rdcstr &ret)
{
#if ENABLED(DXC_COMPATIBLE_DISASM)
// dxc/llvm always prints half floats as their 16-bit hex representation.
if(t->bitWidth == 16)
{
ret += StringFormat::Fmt("0xH%04X", val.u64v[i]);
return;
}
#endif
double d = t->bitWidth == 64 ? val.f64v[i] : val.f32v[i];
// NaNs/infs are printed as hex to ensure we don't lose bits
if(RDCISFINITE(d))
{
// check we can reparse precisely a float-formatted string. Otherwise we print as hex
rdcstr flt = StringFormat::Fmt("%.6le", d);
#if ENABLED(DXC_COMPATIBLE_DISASM)
// dxc/llvm only prints floats as floats if they roundtrip, but our disassembly doesn't need to
// roundtrip so it's better to display the value in all cases
double reparse = strtod(flt.begin(), NULL);
if(d == reparse)
{
ret += flt;
return;
}
#else
ret += flt;
#endif
}
ret += StringFormat::Fmt("0x%llX", d);
}
rdcstr Constant::toString(bool withType) const
{
if(type == NULL)
@@ -1719,25 +1799,7 @@ rdcstr Constant::toString(bool withType) const
{
if(type->scalarType == Type::Float)
{
double orig;
if(type->bitWidth > 32)
orig = val.f64v[0];
else
orig = val.f32v[0];
// NaNs/infs are printed as hex to ensure we don't lose bits
if(RDCISFINITE(orig))
{
// check we can reparse precisely a float-formatted string. Otherwise we print as hex
rdcstr flt = StringFormat::Fmt("%.6le", orig);
double reparse = strtod(flt.begin(), NULL);
if(orig == reparse)
return ret + flt;
}
ret += StringFormat::Fmt("0x%llX", orig);
floatAppendToString(type, val, 0, ret);
}
else if(type->scalarType == Type::Int)
{
@@ -1765,19 +1827,16 @@ rdcstr Constant::toString(bool withType) const
ret += type->inner->toString() + " ";
if(type->scalarType == Type::Float)
{
// TODO need to know how to determine signedness here
if(type->bitWidth > 32)
ret += StringFormat::Fmt("%le", val.f64v[i]);
else
ret += StringFormat::Fmt("%e", val.f32v[i]);
floatAppendToString(type, val, i, ret);
}
else if(type->scalarType == Type::Int)
{
// TODO need to know how to determine signedness here
if(type->bitWidth > 32)
ret += StringFormat::Fmt("%llu", val.u64v[i]);
ret += StringFormat::Fmt("%lld", val.s64v[i]);
else if(type->bitWidth == 1)
ret += val.u32v[i] ? "true" : "false";
else
ret += StringFormat::Fmt("%u", val.u32v[i]);
ret += StringFormat::Fmt("%d", val.s32v[i]);
}
}
ret += ">";
-5
View File
@@ -1281,11 +1281,6 @@ TEST_CASE("Check format conversion", "[format]")
float f2 = ConvertFromHalf(i2);
CHECK(!RDCISFINITE(f2));
}
else if(i == 0x8000)
{
// signed 0
CHECK(i2 == 0);
}
else
{
CHECK(i == i2);
+1 -1
View File
@@ -94,7 +94,7 @@ inline float ConvertFromHalf(uint16_t comp)
if(exponent == 0x00)
{
if(mantissa == 0)
return 0.0f;
return sign ? -0.0f : 0.0f;
// subnormal
float ret = (float)mantissa;