Files
renderdoc/renderdoc/driver/shaders/dxil/dxil_bytecode.cpp
T
baldurk 045aad23ad Write param attr blocks
* We cheat slightly with the attributes - we combine these on load, so we lose
  information about multiple groups they may reference. So we save the groups
  that we read from and use that to write the attribute.
2021-09-21 18:04:56 +01:00

2381 lines
80 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2021 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "dxil_bytecode.h"
#include <ctype.h>
#include <stdio.h>
#include <string>
#include "common/common.h"
#include "common/formatting.h"
#include "os/os_specific.h"
#include "llvm_common.h"
#include "llvm_decoder.h"
#define IS_KNOWN(val, KnownID) (decltype(KnownID)(val) == KnownID)
namespace DXIL
{
using namespace LLVMBC;
void ParseConstant(const LLVMBC::BlockOrRecord &constant, const Type *&curType,
std::function<const Type *(uint64_t)> getType,
std::function<const Type *(const Type *, Type::PointerAddrSpace)> getPtrType,
std::function<const Constant *(uint64_t)> getConstant,
std::function<void(const Constant &)> addConstant)
{
if(IS_KNOWN(constant.id, ConstantsRecord::SETTYPE))
{
curType = getType(constant.ops[0]);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::CONST_NULL) ||
IS_KNOWN(constant.id, ConstantsRecord::UNDEF))
{
Constant v;
v.type = curType;
v.nullconst = IS_KNOWN(constant.id, ConstantsRecord::CONST_NULL);
v.undef = IS_KNOWN(constant.id, ConstantsRecord::UNDEF);
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::INTEGER))
{
Constant v;
v.type = curType;
v.val.s64v[0] = LLVMBC::BitReader::svbr(constant.ops[0]);
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::FLOAT))
{
Constant v;
v.type = curType;
memcpy(&v.val.f64v[0], &constant.ops[0], curType->bitWidth / 8);
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::STRING) ||
IS_KNOWN(constant.id, ConstantsRecord::CSTRING))
{
Constant v;
v.type = curType;
v.str = constant.getString(0);
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::EVAL_CAST))
{
Constant v;
v.op = DecodeCast(constant.ops[0]);
v.type = curType;
// getType(constant.ops[1]); type of the constant, which we ignore
v.inner = getConstant(constant.ops[2]);
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::EVAL_GEP))
{
Constant v;
v.op = Operation::GetElementPtr;
size_t idx = 0;
if(constant.ops.size() & 1)
v.type = getType(constant.ops[idx++]);
for(; idx < constant.ops.size(); idx += 2)
{
const Type *t = getType(constant.ops[idx]);
const Constant *a = getConstant(constant.ops[idx + 1]);
RDCASSERT(t == a->type);
v.members.push_back(*a);
}
if(!v.type)
v.type = v.members[0].type;
// walk the type list to get the return type
for(idx = 2; idx < v.members.size(); idx++)
{
if(v.type->type == Type::Vector || v.type->type == Type::Array)
{
v.type = v.type->inner;
}
else if(v.type->type == Type::Struct)
{
v.type = v.type->members[v.members[idx].val.u32v[0]];
}
else
{
RDCERR("Unexpected type %d encountered in GEP", v.type->type);
}
}
// the result is a pointer to the return type
v.type = getPtrType(v.type, curType->addrSpace);
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::AGGREGATE))
{
Constant v;
v.type = curType;
if(v.type->type == Type::Vector)
{
// inline vectors
for(size_t m = 0; m < constant.ops.size(); m++)
{
const Constant *member = getConstant(constant.ops[m]);
if(member)
{
if(v.type->bitWidth <= 32)
v.val.u32v[m] = member->val.u32v[m];
else
v.val.u64v[m] = member->val.u64v[m];
}
else
{
RDCERR("Index %llu out of bounds for constants array", constant.ops[m]);
}
}
}
else
{
for(uint64_t m : constant.ops)
{
const Constant *member = getConstant(m);
if(member && member->type)
{
v.members.push_back(*member);
}
else
{
Constant c;
c.type = NULL;
c.val.u64v[0] = m;
v.members.push_back(c);
RDCWARN("Index %llu out of bounds for constants array, possible forward reference", m);
}
}
}
addConstant(v);
}
else if(IS_KNOWN(constant.id, ConstantsRecord::DATA))
{
Constant v;
v.type = curType;
if(v.type->type == Type::Vector)
{
for(size_t m = 0; m < constant.ops.size(); m++)
{
if(v.type->bitWidth <= 32)
v.val.u32v[m] = constant.ops[m] & ((1ULL << v.type->bitWidth) - 1);
else
v.val.u64v[m] = constant.ops[m];
}
}
else
{
for(size_t m = 0; m < constant.ops.size(); m++)
{
Constant el;
el.type = v.type->inner;
if(el.type->bitWidth <= 32)
el.val.u32v[0] = constant.ops[m] & ((1ULL << el.type->bitWidth) - 1);
else
el.val.u64v[m] = constant.ops[m];
v.members.push_back(el);
}
}
addConstant(v);
}
else
{
RDCERR("Unknown record ID %u encountered in constants block", constant.id);
}
}
// helper struct for reading ops
struct OpReader
{
OpReader(Program *prog, const LLVMBC::BlockOrRecord &op)
: prog(prog), type((FunctionRecord)op.id), values(op.ops), idx(0)
{
}
FunctionRecord type;
size_t remaining() { return values.size() - idx; }
Symbol getSymbol(uint64_t val) { return prog->m_Symbols[prog->m_Symbols.size() - (size_t)val]; }
Symbol getSymbol(bool withType = true)
{
// get the value
uint64_t val = get<uint64_t>();
// if it's not a forward reference, resolve the relative-ness and return
if(val <= prog->m_Symbols.size())
{
return getSymbol(val);
}
else
{
// sometimes forward references have types, which we store here in case we need the type
// later.
if(withType)
m_LastType = getType();
// return the forward reference symbol
return Symbol(SymbolType::Unknown, prog->m_Symbols.size() - (int32_t)val);
}
}
// some symbols are referenced absolute, not relative
Symbol getSymbolAbsolute() { return prog->m_Symbols[get<size_t>()]; }
const Type *getType() { return &prog->m_Types[get<size_t>()]; }
const Type *getType(const Function &f, Symbol s)
{
if(s.type == SymbolType::Unknown)
return m_LastType;
return prog->GetSymbolType(f, s);
}
template <typename T>
T get()
{
return (T)values[idx++];
}
private:
const rdcarray<uint64_t> &values;
size_t idx;
Program *prog;
const Type *m_LastType = NULL;
};
bool Program::Valid(const byte *bytes, size_t length)
{
if(length < sizeof(ProgramHeader))
return false;
const byte *ptr = bytes;
const ProgramHeader *header = (const ProgramHeader *)ptr;
if(header->DxilMagic != MAKE_FOURCC('D', 'X', 'I', 'L'))
return false;
size_t expected = offsetof(ProgramHeader, DxilMagic) + header->BitcodeOffset + header->BitcodeSize;
if(expected != length)
return false;
return LLVMBC::BitcodeReader::Valid(
ptr + offsetof(ProgramHeader, DxilMagic) + header->BitcodeOffset, header->BitcodeSize);
}
Program::Program(const byte *bytes, size_t length)
{
const byte *ptr = bytes;
const ProgramHeader *header = (const ProgramHeader *)ptr;
RDCASSERT(header->DxilMagic == MAKE_FOURCC('D', 'X', 'I', 'L'));
m_Bytes.assign(bytes, length);
const byte *bitcode = ((const byte *)&header->DxilMagic) + header->BitcodeOffset;
RDCASSERT(bitcode + header->BitcodeSize <= ptr + length);
LLVMBC::BitcodeReader reader(bitcode, header->BitcodeSize);
LLVMBC::BlockOrRecord root = reader.ReadToplevelBlock();
// the top-level block should be MODULE_BLOCK
RDCASSERT(KnownBlock(root.id) == KnownBlock::MODULE_BLOCK);
// we should have consumed all bits, only one top-level block
RDCASSERT(reader.AtEndOfStream());
m_Type = DXBC::ShaderType(header->ProgramType);
m_Major = (header->ProgramVersion & 0xf0) >> 4;
m_Minor = header->ProgramVersion & 0xf;
m_DXILVersion = header->DxilVersion;
// Input signature and Output signature haven't changed.
// Pipeline Runtime Information we have decoded just not implemented here
rdcstr datalayout, triple;
rdcarray<size_t> functionDecls;
for(const LLVMBC::BlockOrRecord &rootchild : root.children)
{
if(rootchild.IsRecord())
{
if(IS_KNOWN(rootchild.id, ModuleRecord::VERSION))
{
if(rootchild.ops[0] != 1)
{
RDCERR("Unsupported LLVM bitcode version %u", rootchild.ops[0]);
break;
}
}
else if(IS_KNOWN(rootchild.id, ModuleRecord::TRIPLE))
{
m_Triple = rootchild.getString();
}
else if(IS_KNOWN(rootchild.id, ModuleRecord::DATALAYOUT))
{
m_Datalayout = rootchild.getString();
}
else if(IS_KNOWN(rootchild.id, ModuleRecord::GLOBALVAR))
{
// [pointer type, isconst, initid, linkage, alignment, section, visibility, threadlocal,
// unnamed_addr, externally_initialized, dllstorageclass, comdat]
GlobalVar g;
g.type = &m_Types[(size_t)rootchild.ops[0]];
if(rootchild.ops[1] & 0x1)
g.flags |= GlobalFlags::IsConst;
Type::PointerAddrSpace addrSpace = g.type->addrSpace;
if(rootchild.ops[1] & 0x2)
addrSpace = Type::PointerAddrSpace(rootchild.ops[1] >> 2);
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)
g.flags |= GlobalFlags::GlobalUnnamedAddr;
else if(rootchild.ops[8] == 2)
g.flags |= GlobalFlags::LocalUnnamedAddr;
}
if(rootchild.ops.size() > 9)
{
if(rootchild.ops[9])
g.flags |= GlobalFlags::ExternallyInitialised;
}
if(rootchild.ops.size() > 10)
{
RDCASSERTMSG("global has non-default DLL storage class", rootchild.ops[10] == 0);
}
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()});
// all global symbols are 'values' in LLVM, we don't need this but need to keep indexing the
// same
Constant v;
v.symbol = true;
v.type = GetPointerType(g.type, addrSpace);
if(v.type == g.type)
RDCERR("Expected to find pointer type for global variable");
g.type = v.type;
m_Constants.push_back(v);
m_GlobalVars.push_back(g);
}
else if(IS_KNOWN(rootchild.id, ModuleRecord::FUNCTION))
{
// [type, callingconv, isproto, linkage, paramattrs, alignment, section, visibility, gc,
// 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];
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()});
// all global symbols are 'values' in LLVM, we don't need this but need to keep indexing the
// same
Constant v;
v.symbol = true;
v.type = GetPointerType(f.funcType, Type::PointerAddrSpace::Default);
if(v.type == f.funcType)
RDCERR("Expected to find pointer type for function");
m_Constants.push_back(v);
if(!f.external)
functionDecls.push_back(m_Functions.size());
m_Functions.push_back(f);
}
else if(IS_KNOWN(rootchild.id, ModuleRecord::ALIAS))
{
// [alias value type, addrspace, aliasee val#, linkage, visibility]
Alias a;
// symbols refer into any of N types in declaration order
m_Symbols.push_back({SymbolType::Alias, m_Aliases.size()});
// all global symbols are 'values' in LLVM, we don't need this but need to keep indexing the
// same
Constant v;
v.type = &m_Types[(size_t)rootchild.ops[0]];
v.symbol = true;
m_Constants.push_back(v);
m_Aliases.push_back(a);
}
else if(IS_KNOWN(rootchild.id, ModuleRecord::SECTIONNAME))
{
m_Sections.push_back(rootchild.getString(0));
}
else
{
RDCERR("Unknown record ID %u encountered at module scope", rootchild.id);
}
}
else if(rootchild.IsBlock())
{
if(IS_KNOWN(rootchild.id, KnownBlock::BLOCKINFO))
{
// do nothing, this is internal parse data
}
else if(IS_KNOWN(rootchild.id, KnownBlock::PARAMATTR_GROUP_BLOCK))
{
for(const LLVMBC::BlockOrRecord &attrgroup : rootchild.children)
{
if(attrgroup.IsBlock())
{
RDCERR("Unexpected subblock in PARAMATTR_GROUP_BLOCK");
continue;
}
if(!IS_KNOWN(attrgroup.id, ParamAttrGroupRecord::ENTRY))
{
RDCERR("Unexpected attribute group record ID %u", attrgroup.id);
continue;
}
Attributes group;
size_t id = (size_t)attrgroup.ops[0];
group.index = attrgroup.ops[1];
group.valid = true;
for(size_t i = 2; i < attrgroup.ops.size(); i++)
{
switch(attrgroup.ops[i])
{
case 0:
{
group.params |= Attribute(1ULL << (attrgroup.ops[i + 1]));
i++;
break;
}
case 1:
{
uint64_t param = attrgroup.ops[i + 2];
Attribute attr = Attribute(1ULL << attrgroup.ops[i + 1]);
group.params |= attr;
switch(attr)
{
case Attribute::Alignment: group.align = param; break;
case Attribute::StackAlignment: group.stackAlign = param; break;
case Attribute::Dereferenceable: group.derefBytes = param; break;
case Attribute::DereferenceableOrNull: group.derefOrNullBytes = param; break;
default: RDCERR("Unexpected attribute %llu with parameter", attr);
}
i += 2;
break;
}
default:
{
rdcstr a = attrgroup.getString(i + 1);
rdcstr b = attrgroup.getString(i + 1 + a.size() + 1);
group.strs.push_back({a, b});
break;
}
}
}
m_AttributeGroups.resize_for_index(id);
m_AttributeGroups[id] = group;
}
}
else if(IS_KNOWN(rootchild.id, KnownBlock::PARAMATTR_BLOCK))
{
for(const LLVMBC::BlockOrRecord &paramattr : rootchild.children)
{
if(paramattr.IsBlock())
{
RDCERR("Unexpected subblock in PARAMATTR_BLOCK");
continue;
}
if(!IS_KNOWN(paramattr.id, ParamAttrRecord::ENTRY))
{
RDCERR("Unexpected attribute record ID %u", paramattr.id);
continue;
}
Attributes attrs;
attrs.index = m_Attributes.size();
attrs.groups = paramattr.ops;
for(uint64_t g : paramattr.ops)
{
if(g < m_AttributeGroups.size())
{
Attributes &other = m_AttributeGroups[(size_t)g];
attrs.params |= other.params;
attrs.align = RDCMAX(attrs.align, other.align);
attrs.stackAlign = RDCMAX(attrs.stackAlign, other.stackAlign);
attrs.derefBytes = RDCMAX(attrs.derefBytes, other.derefBytes);
attrs.derefOrNullBytes = RDCMAX(attrs.derefOrNullBytes, other.derefOrNullBytes);
attrs.strs.append(other.strs);
}
else
{
RDCERR("Attribute refers to out of bounds group %llu", g);
}
}
m_Attributes.push_back(attrs);
}
}
else if(IS_KNOWN(rootchild.id, KnownBlock::TYPE_BLOCK))
{
rdcstr structname;
if(!rootchild.children.empty() && !IS_KNOWN(rootchild.children[0].id, TypeRecord::NUMENTRY))
{
RDCWARN("No NUMENTRY record, resizing conservatively to number of records");
m_Types.resize(rootchild.children.size());
}
size_t typeIndex = 0;
for(const LLVMBC::BlockOrRecord &typ : rootchild.children)
{
if(typ.IsBlock())
{
RDCERR("Unexpected subblock in TYPE_BLOCK");
continue;
}
if(IS_KNOWN(typ.id, TypeRecord::NUMENTRY))
{
RDCASSERT(m_Types.size() < (size_t)typ.ops[0], m_Types.size(), typ.ops[0]);
m_Types.resize((size_t)typ.ops[0]);
}
else if(IS_KNOWN(typ.id, TypeRecord::VOID))
{
m_Types[typeIndex].type = Type::Scalar;
m_Types[typeIndex].scalarType = Type::Void;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::LABEL))
{
m_Types[typeIndex].type = Type::Label;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::METADATA))
{
m_Types[typeIndex].type = Type::Metadata;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::HALF))
{
m_Types[typeIndex].type = Type::Scalar;
m_Types[typeIndex].scalarType = Type::Float;
m_Types[typeIndex].bitWidth = 16;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::FLOAT))
{
m_Types[typeIndex].type = Type::Scalar;
m_Types[typeIndex].scalarType = Type::Float;
m_Types[typeIndex].bitWidth = 32;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::DOUBLE))
{
m_Types[typeIndex].type = Type::Scalar;
m_Types[typeIndex].scalarType = Type::Float;
m_Types[typeIndex].bitWidth = 64;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::INTEGER))
{
m_Types[typeIndex].type = Type::Scalar;
m_Types[typeIndex].scalarType = Type::Int;
m_Types[typeIndex].bitWidth = typ.ops[0] & 0xffffffff;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::VECTOR))
{
m_Types[typeIndex].type = Type::Vector;
m_Types[typeIndex].elemCount = typ.ops[0] & 0xffffffff;
m_Types[typeIndex].inner = &m_Types[(size_t)typ.ops[1]];
// copy properties out of the inner for convenience
m_Types[typeIndex].scalarType = m_Types[typeIndex].inner->scalarType;
m_Types[typeIndex].bitWidth = m_Types[typeIndex].inner->bitWidth;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::ARRAY))
{
m_Types[typeIndex].type = Type::Array;
m_Types[typeIndex].elemCount = typ.ops[0] & 0xffffffff;
m_Types[typeIndex].inner = &m_Types[(size_t)typ.ops[1]];
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::POINTER))
{
m_Types[typeIndex].type = Type::Pointer;
m_Types[typeIndex].inner = &m_Types[(size_t)typ.ops[0]];
m_Types[typeIndex].addrSpace = Type::PointerAddrSpace(typ.ops[1]);
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::OPAQUE))
{
// pretend opaque types are empty structs
m_Types[typeIndex].type = Type::Struct;
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::STRUCT_NAME))
{
structname = typ.getString(0);
}
else if(IS_KNOWN(typ.id, TypeRecord::STRUCT_ANON) ||
IS_KNOWN(typ.id, TypeRecord::STRUCT_NAMED))
{
m_Types[typeIndex].type = Type::Struct;
m_Types[typeIndex].packedStruct = (typ.ops[0] != 0);
for(size_t o = 1; o < typ.ops.size(); o++)
m_Types[typeIndex].members.push_back(&m_Types[(size_t)typ.ops[o]]);
if(IS_KNOWN(typ.id, TypeRecord::STRUCT_NAMED))
{
// may we want a reverse map name -> type? probably not, this is only relevant for
// disassembly or linking and disassembly we can do just by iterating all types
m_Types[typeIndex].name = structname;
structname.clear();
}
typeIndex++;
}
else if(IS_KNOWN(typ.id, TypeRecord::FUNCTION_OLD) ||
IS_KNOWN(typ.id, TypeRecord::FUNCTION))
{
m_Types[typeIndex].type = Type::Function;
m_Types[typeIndex].vararg = (typ.ops[0] != 0);
size_t o = 1;
// skip attrid
if(IS_KNOWN(typ.id, TypeRecord::FUNCTION_OLD))
o++;
// return type
m_Types[typeIndex].inner = &m_Types[(size_t)typ.ops[o]];
o++;
for(; o < typ.ops.size(); o++)
m_Types[typeIndex].members.push_back(&m_Types[(size_t)typ.ops[o]]);
typeIndex++;
}
else
{
RDCERR("Unknown record ID %u encountered in type block", typ.id);
}
}
}
else if(IS_KNOWN(rootchild.id, KnownBlock::CONSTANTS_BLOCK))
{
const Type *t = NULL;
m_Constants.reserve(m_Constants.size() + rootchild.children.size());
for(const LLVMBC::BlockOrRecord &constant : rootchild.children)
{
if(constant.IsBlock())
{
RDCERR("Unexpected subblock in CONSTANTS_BLOCK");
continue;
}
ParseConstant(constant, t, [this](uint64_t op) { return &m_Types[(size_t)op]; },
[this](const Type *t, Type::PointerAddrSpace addrSpace) {
return GetPointerType(t, addrSpace);
},
[this](uint64_t v) {
size_t idx = (size_t)v;
return idx < m_Constants.size() ? &m_Constants[idx] : NULL;
},
[this](const Constant &v) {
m_Symbols.push_back({SymbolType::Constant, m_Constants.size()});
m_Constants.push_back(v);
});
}
// post-patch up contants with members that are references to future constants (blech!)
for(Constant &c : m_Constants)
{
if(c.members.empty())
continue;
for(Constant &m : c.members)
{
if(m.type == NULL)
{
if(m.val.u64v[0] > 0)
{
size_t idx = (size_t)m.val.u64v[0];
if(idx < m_Constants.size())
{
m = m_Constants[idx];
}
else
{
m = Constant();
RDCERR("Couldn't resolve constant %zu", idx);
}
}
else
{
RDCERR("Unexpected member with no type but no forward-index constant value");
}
}
}
}
}
else if(IS_KNOWN(rootchild.id, KnownBlock::VALUE_SYMTAB_BLOCK))
{
for(const LLVMBC::BlockOrRecord &symtab : rootchild.children)
{
if(symtab.IsBlock())
{
RDCERR("Unexpected subblock in VALUE_SYMTAB_BLOCK");
continue;
}
if(!IS_KNOWN(symtab.id, ValueSymtabRecord::ENTRY))
{
RDCERR("Unexpected symbol table record ID %u", symtab.id);
continue;
}
size_t s = (size_t)symtab.ops[0];
if(s < m_Symbols.size())
{
size_t idx = (size_t)m_Symbols[s].idx;
switch(m_Symbols[s].type)
{
case SymbolType::Unknown:
case SymbolType::Constant:
case SymbolType::Argument:
case SymbolType::Instruction:
case SymbolType::Metadata:
case SymbolType::Literal:
case SymbolType::BasicBlock:
RDCERR("Unexpected global symbol referring to %d", m_Symbols[s].type);
break;
case SymbolType::GlobalVar:
m_Constants[s].str = m_GlobalVars[idx].name = symtab.getString(1);
break;
case SymbolType::Function:
m_Constants[s].str = m_Functions[idx].name = symtab.getString(1);
break;
case SymbolType::Alias:
m_Constants[s].str = m_Aliases[idx].name = symtab.getString(1);
break;
}
}
else
{
RDCERR("Symbol %llu referenced out of bounds", s);
}
}
}
else if(IS_KNOWN(rootchild.id, KnownBlock::METADATA_BLOCK))
{
m_Metadata.reserve(rootchild.children.size());
for(size_t i = 0; i < rootchild.children.size(); i++)
{
const LLVMBC::BlockOrRecord &metaRecord = rootchild.children[i];
if(metaRecord.IsBlock())
{
RDCERR("Unexpected subblock in METADATA_BLOCK");
continue;
}
if(IS_KNOWN(metaRecord.id, MetaDataRecord::NAME))
{
NamedMetadata meta;
meta.name = metaRecord.getString();
i++;
const LLVMBC::BlockOrRecord &namedNode = rootchild.children[i];
RDCASSERT(IS_KNOWN(namedNode.id, MetaDataRecord::NAMED_NODE));
for(uint64_t op : namedNode.ops)
meta.children.push_back(&m_Metadata[(size_t)op]);
m_NamedMeta.push_back(meta);
}
else if(IS_KNOWN(metaRecord.id, MetaDataRecord::KIND))
{
size_t kind = (size_t)metaRecord.ops[0];
m_Kinds.resize(RDCMAX(m_Kinds.size(), kind + 1));
m_Kinds[kind] = metaRecord.getString(1);
continue;
}
else
{
m_Metadata.resize_for_index(i);
Metadata &meta = m_Metadata[i];
auto getMetaOrNull = [this](uint64_t id) {
return id ? &m_Metadata[size_t(id - 1)] : NULL;
};
auto getMetaStringOrNull = [this](uint64_t id) {
return id ? &m_Metadata[size_t(id - 1)].str : NULL;
};
if(IS_KNOWN(metaRecord.id, MetaDataRecord::STRING_OLD))
{
meta.isConstant = true;
meta.str = metaRecord.getString();
}
else if(IS_KNOWN(metaRecord.id, MetaDataRecord::VALUE))
{
meta.isConstant = true;
meta.constant = &m_Constants[(size_t)metaRecord.ops[1]];
meta.type = &m_Types[(size_t)metaRecord.ops[0]];
}
else if(IS_KNOWN(metaRecord.id, MetaDataRecord::NODE) ||
IS_KNOWN(metaRecord.id, MetaDataRecord::DISTINCT_NODE))
{
if(IS_KNOWN(metaRecord.id, MetaDataRecord::DISTINCT_NODE))
meta.isDistinct = true;
for(uint64_t op : metaRecord.ops)
meta.children.push_back(getMetaOrNull(op));
}
else
{
bool parsed = ParseDebugMetaRecord(metaRecord, meta);
if(!parsed)
{
RDCERR("unhandled metadata type %u", metaRecord.id);
}
}
}
}
}
else if(IS_KNOWN(rootchild.id, KnownBlock::FUNCTION_BLOCK))
{
Function &f = m_Functions[functionDecls[0]];
functionDecls.erase(0);
auto getConstant = [this, &f](uint64_t v) { return GetFunctionConstant(f, v); };
auto getMeta = [this, &f](uint64_t v) {
size_t idx = (size_t)v;
return idx - 1 < m_Metadata.size() ? &m_Metadata[idx] : &f.metadata[idx];
};
auto getMetaOrNull = [this, &f](uint64_t v) {
size_t idx = (size_t)v;
return idx == 0 ? NULL : (idx - 1 < m_Metadata.size() ? &m_Metadata[idx - 1]
: &f.metadata[idx - 1]);
};
size_t prevNumSymbols = m_Symbols.size();
size_t instrSymbolStart = 0;
for(size_t i = 0; i < f.funcType->members.size(); i++)
{
Instruction arg;
arg.type = f.funcType->members[i];
arg.name = StringFormat::Fmt("arg%zu", i);
f.args.push_back(arg);
m_Symbols.push_back({SymbolType::Argument, i});
}
size_t curBlock = 0;
int32_t debugLocIndex = -1;
for(const LLVMBC::BlockOrRecord &funcChild : rootchild.children)
{
if(funcChild.IsBlock())
{
if(IS_KNOWN(funcChild.id, KnownBlock::CONSTANTS_BLOCK))
{
f.constants.reserve(funcChild.children.size());
const Type *t = NULL;
for(const LLVMBC::BlockOrRecord &constant : funcChild.children)
{
if(constant.IsBlock())
{
RDCERR("Unexpected subblock in CONSTANTS_BLOCK");
continue;
}
ParseConstant(constant, t, [this](uint64_t op) { return &m_Types[(size_t)op]; },
[this](const Type *t, Type::PointerAddrSpace addrSpace) {
return GetPointerType(t, addrSpace);
},
getConstant,
[this, &f](const Constant &v) {
m_Symbols.push_back({SymbolType::Constant,
m_Constants.size() + f.constants.size()});
f.constants.push_back(v);
});
}
// post-patch up contants with members that are references to future constants
// (blech!)
for(Constant &c : f.constants)
{
if(c.members.empty())
continue;
for(Constant &m : c.members)
{
if(m.type == NULL)
{
if(m.val.u64v[0] > 0)
{
m = *getConstant(m.val.u64v[0]);
}
else
{
RDCERR("Unexpected member with no type but no forward-index constant value");
}
}
}
}
instrSymbolStart = m_Symbols.size();
}
else if(IS_KNOWN(funcChild.id, KnownBlock::METADATA_BLOCK))
{
f.metadata.resize(funcChild.children.size());
size_t m = 0;
for(const LLVMBC::BlockOrRecord &metaRecord : funcChild.children)
{
if(metaRecord.IsBlock())
{
RDCERR("Unexpected subblock in function METADATA_BLOCK");
continue;
}
Metadata &meta = f.metadata[m];
if(IS_KNOWN(metaRecord.id, MetaDataRecord::VALUE))
{
meta.isConstant = true;
size_t idx = (size_t)metaRecord.ops[1];
if(idx < m_Constants.size())
{
// global constant reference
meta.constant = &m_Constants[idx];
}
else
{
idx -= m_Constants.size();
if(idx < f.constants.size())
{
// function-local constant reference
meta.constant = &f.constants[idx];
}
else
{
// forward reference to instruction
meta.func = &f;
meta.instruction = idx - f.constants.size();
}
}
meta.type = &m_Types[(size_t)metaRecord.ops[0]];
}
else
{
RDCERR("Unexpected record %u in function METADATA_BLOCK", metaRecord.id);
}
m++;
}
}
else if(IS_KNOWN(funcChild.id, KnownBlock::VALUE_SYMTAB_BLOCK))
{
for(const LLVMBC::BlockOrRecord &symtab : funcChild.children)
{
if(symtab.IsBlock())
{
RDCERR("Unexpected subblock in function VALUE_SYMTAB_BLOCK");
continue;
}
if(IS_KNOWN(symtab.id, ValueSymtabRecord::ENTRY))
{
size_t idx = (size_t)symtab.ops[0];
if(idx >= m_Symbols.size())
{
RDCERR("Out of bounds symbol index %zu (%s) in function symbol table", idx,
symtab.getString(1).c_str());
continue;
}
Symbol s = m_Symbols[idx];
switch(s.type)
{
case SymbolType::Unknown:
case SymbolType::Constant:
if(s.idx < m_Constants.size())
RDCERR("Unexpected local symbol referring to global value");
else
f.constants[(size_t)s.idx - m_Constants.size()].str = symtab.getString(1);
break;
case SymbolType::Argument:
f.args[(size_t)s.idx].name = symtab.getString(1);
break;
case SymbolType::Instruction:
f.instructions[(size_t)s.idx].name = symtab.getString(1);
break;
case SymbolType::BasicBlock:
f.blocks[(size_t)s.idx].name = symtab.getString(1);
break;
case SymbolType::GlobalVar:
case SymbolType::Function:
case SymbolType::Alias:
case SymbolType::Metadata:
case SymbolType::Literal:
RDCERR("Unexpected local symbol referring to %d", s.type);
break;
}
}
else if(IS_KNOWN(symtab.id, ValueSymtabRecord::BBENTRY))
{
f.blocks[(size_t)symtab.ops[0]].name = symtab.getString(1);
}
else
{
RDCERR("Unexpected function symbol table record ID %u", symtab.id);
continue;
}
}
}
else if(IS_KNOWN(funcChild.id, KnownBlock::METADATA_ATTACHMENT))
{
for(const LLVMBC::BlockOrRecord &meta : funcChild.children)
{
if(meta.IsBlock())
{
RDCERR("Unexpected subblock in METADATA_ATTACHMENT");
continue;
}
if(!IS_KNOWN(meta.id, MetaDataRecord::ATTACHMENT))
{
RDCERR("Unexpected record %u in METADATA_ATTACHMENT", meta.id);
continue;
}
size_t idx = 0;
rdcarray<rdcpair<uint64_t, Metadata *>> attach;
if(meta.ops.size() % 2 != 0)
idx++;
for(; idx < meta.ops.size(); idx += 2)
attach.push_back(make_rdcpair(meta.ops[idx], getMeta(meta.ops[idx + 1])));
if(meta.ops.size() % 2 == 0)
f.attachedMeta.swap(attach);
else
f.instructions[(size_t)meta.ops[0]].attachedMeta.swap(attach);
}
}
else if(IS_KNOWN(funcChild.id, KnownBlock::USELIST_BLOCK))
{
RDCDEBUG("Ignoring uselist block");
}
else
{
RDCERR("Unexpected subblock %u in FUNCTION_BLOCK", funcChild.id);
continue;
}
}
else
{
OpReader op(this, funcChild);
if(op.type == FunctionRecord::DECLAREBLOCKS)
{
f.blocks.resize(op.get<size_t>());
curBlock = 0;
}
else if(op.type == FunctionRecord::DEBUG_LOC)
{
DebugLocation debugLoc;
debugLoc.line = op.get<uint64_t>();
debugLoc.col = op.get<uint64_t>();
debugLoc.scope = getMetaOrNull(op.get<uint64_t>());
debugLoc.inlinedAt = getMetaOrNull(op.get<uint64_t>());
debugLocIndex = m_DebugLocations.indexOf(debugLoc);
if(debugLocIndex < 0)
{
m_DebugLocations.push_back(debugLoc);
debugLocIndex = int32_t(m_DebugLocations.size() - 1);
}
f.instructions.back().debugLoc = (uint32_t)debugLocIndex;
}
else if(op.type == FunctionRecord::DEBUG_LOC_AGAIN)
{
f.instructions.back().debugLoc = (uint32_t)debugLocIndex;
}
else if(op.type == FunctionRecord::INST_CALL)
{
Instruction inst;
inst.op = Operation::Call;
size_t attr = op.get<size_t>();
if(attr > 0)
inst.paramAttrs = &m_Attributes[attr - 1];
uint64_t callingFlags = op.get<uint64_t>();
if(callingFlags & (1ULL << 17))
inst.opFlags = op.get<InstructionFlags>();
if(callingFlags & (1ULL << 15))
op.get<uint64_t>(); // funcCallType
Symbol s = op.getSymbol();
if(s.type != SymbolType::Function)
{
RDCERR("Unexpected symbol type %d called in INST_CALL", s.type);
continue;
}
inst.funcCall = &m_Functions[(size_t)s.idx];
inst.type = inst.funcCall->funcType->inner;
for(size_t i = 0; op.remaining() > 0; i++)
{
if(inst.funcCall->funcType->members[i]->type == Type::Metadata)
{
s.type = SymbolType::Metadata;
s.idx = uint32_t((uint64_t)m_Symbols.size() - op.get<uint64_t>());
}
else
{
s = op.getSymbol(false);
}
inst.args.push_back(s);
}
RDCASSERTEQUAL(inst.args.size(), inst.funcCall->funcType->members.size());
if(!inst.type->isVoid())
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_CAST)
{
Instruction inst;
inst.args.push_back(op.getSymbol());
inst.type = op.getType();
uint64_t opcode = op.get<uint64_t>();
inst.op = DecodeCast(opcode);
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_EXTRACTVAL)
{
Instruction inst;
inst.op = Operation::ExtractVal;
inst.args.push_back(op.getSymbol());
inst.type = op.getType(f, inst.args.back());
while(op.remaining() > 0)
{
uint64_t val = op.get<uint64_t>();
if(inst.type->type == Type::Array)
inst.type = inst.type->inner;
else
inst.type = inst.type->members[(size_t)val];
inst.args.push_back({SymbolType::Literal, val});
}
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_RET)
{
Instruction inst;
inst.op = Operation::Ret;
if(op.remaining() == 0)
{
inst.type = GetVoidType();
RDCASSERT(inst.type);
}
else
{
inst.args.push_back(op.getSymbol());
inst.type = op.getType(f, inst.args.back());
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
}
curBlock++;
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_BINOP)
{
Instruction inst;
inst.args.push_back(op.getSymbol());
inst.type = op.getType(f, inst.args.back());
inst.args.push_back(op.getSymbol(false));
bool isFloatOp = (inst.type->scalarType == Type::Float);
uint64_t opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.op = isFloatOp ? Operation::FAdd : Operation::Add; break;
case 1: inst.op = isFloatOp ? Operation::FSub : Operation::Sub; break;
case 2: inst.op = isFloatOp ? Operation::FMul : Operation::Mul; break;
case 3: inst.op = Operation::UDiv; break;
case 4: inst.op = isFloatOp ? Operation::FDiv : Operation::SDiv; break;
case 5: inst.op = Operation::URem; break;
case 6: inst.op = isFloatOp ? Operation::FRem : Operation::SRem; break;
case 7: inst.op = Operation::ShiftLeft; break;
case 8: inst.op = Operation::LogicalShiftRight; break;
case 9: inst.op = Operation::ArithShiftRight; break;
case 10: inst.op = Operation::And; break;
case 11: inst.op = Operation::Or; break;
case 12: inst.op = Operation::Xor; break;
default:
inst.op = Operation::And;
RDCERR("Unhandled binop type %llu", opcode);
break;
}
if(op.remaining() > 0)
{
uint64_t flags = op.get<uint64_t>();
if(inst.op == Operation::Add || inst.op == Operation::Sub ||
inst.op == Operation::Mul || inst.op == Operation::ShiftLeft)
{
if(flags & 0x2)
inst.opFlags |= InstructionFlags::NoSignedWrap;
if(flags & 0x1)
inst.opFlags |= InstructionFlags::NoUnsignedWrap;
}
else if(inst.op == Operation::SDiv || inst.op == Operation::UDiv ||
inst.op == Operation::LogicalShiftRight ||
inst.op == Operation::ArithShiftRight)
{
if(flags & 0x1)
inst.opFlags |= InstructionFlags::Exact;
}
else if(isFloatOp)
{
// fast math flags overlap
inst.opFlags = InstructionFlags(flags);
}
}
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_UNREACHABLE)
{
Instruction inst;
inst.op = Operation::Unreachable;
curBlock++;
}
else if(op.type == FunctionRecord::INST_ALLOCA)
{
Instruction inst;
inst.op = Operation::Alloca;
inst.type = op.getType();
// we now have the inner type, but this instruction returns a pointer to that type so
// adjust
inst.type = GetPointerType(inst.type, Type::PointerAddrSpace::Default);
RDCASSERT(inst.type->type == Type::Pointer);
// type of the size - ignored
(void)op.getType();
// size
inst.args.push_back(op.getSymbolAbsolute());
uint64_t align = op.get<uint64_t>();
if(align & 0x20)
{
// argument alloca
}
if((align & 0x40) == 0)
{
RDCASSERT(inst.type->type == Type::Pointer);
inst.type = inst.type->inner;
}
align &= ~0xE0;
inst.align = (1U << align) >> 1;
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_INBOUNDS_GEP_OLD ||
op.type == FunctionRecord::INST_GEP_OLD || op.type == FunctionRecord::INST_GEP)
{
Instruction inst;
inst.op = Operation::GetElementPtr;
if(op.type == FunctionRecord::INST_INBOUNDS_GEP_OLD)
inst.opFlags |= InstructionFlags::InBounds;
if(op.type == FunctionRecord::INST_GEP)
{
if(op.get<uint64_t>())
inst.opFlags |= InstructionFlags::InBounds;
inst.type = op.getType();
}
while(op.remaining() > 0)
{
inst.args.push_back(op.getSymbol());
if(inst.type == NULL && inst.args.size() == 1)
inst.type = op.getType(f, inst.args.back());
}
// walk the type list to get the return type
for(size_t idx = 2; idx < inst.args.size(); idx++)
{
if(inst.type->type == Type::Vector || inst.type->type == Type::Array)
{
inst.type = inst.type->inner;
}
else if(inst.type->type == Type::Struct)
{
Symbol s = inst.args[idx];
// if it's a struct the index must be constant
RDCASSERT(s.type == SymbolType::Constant);
inst.type = inst.type->members[GetFunctionConstant(f, s.idx)->val.u32v[0]];
}
else
{
RDCERR("Unexpected type %d encountered in GEP", inst.type->type);
}
}
// get the pointer type
inst.type = GetPointerType(inst.type, op.getType(f, inst.args[0])->addrSpace);
RDCASSERT(inst.type->type == Type::Pointer);
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_LOAD)
{
Instruction inst;
inst.op = Operation::Load;
inst.args.push_back(op.getSymbol());
if(op.remaining() == 3)
{
inst.type = op.getType();
}
else
{
inst.type = op.getType(f, inst.args.back());
RDCASSERT(inst.type->type == Type::Pointer);
inst.type = inst.type->inner;
}
inst.align = (1U << op.get<uint64_t>()) >> 1;
inst.opFlags |= (op.get<uint64_t>() != 0) ? InstructionFlags::Volatile
: InstructionFlags::NoFlags;
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_STORE_OLD || op.type == FunctionRecord::INST_STORE)
{
Instruction inst;
inst.op = Operation::Store;
inst.type = GetVoidType();
inst.args.push_back(op.getSymbol());
if(op.type == FunctionRecord::INST_STORE_OLD)
inst.args.push_back(op.getSymbol(false));
else
inst.args.push_back(op.getSymbol());
inst.align = (1U << op.get<uint64_t>()) >> 1;
inst.opFlags |= (op.get<uint64_t>() != 0) ? InstructionFlags::Volatile
: InstructionFlags::NoFlags;
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_CMP ||
IS_KNOWN(op.type, FunctionRecord::INST_CMP2))
{
Instruction inst;
// a
inst.args.push_back(op.getSymbol());
const Type *argType = op.getType(f, inst.args.back());
// b
inst.args.push_back(op.getSymbol(false));
uint64_t opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.op = Operation::FOrdFalse; break;
case 1: inst.op = Operation::FOrdEqual; break;
case 2: inst.op = Operation::FOrdGreater; break;
case 3: inst.op = Operation::FOrdGreaterEqual; break;
case 4: inst.op = Operation::FOrdLess; break;
case 5: inst.op = Operation::FOrdLessEqual; break;
case 6: inst.op = Operation::FOrdNotEqual; break;
case 7: inst.op = Operation::FOrd; break;
case 8: inst.op = Operation::FUnord; break;
case 9: inst.op = Operation::FUnordEqual; break;
case 10: inst.op = Operation::FUnordGreater; break;
case 11: inst.op = Operation::FUnordGreaterEqual; break;
case 12: inst.op = Operation::FUnordLess; break;
case 13: inst.op = Operation::FUnordLessEqual; break;
case 14: inst.op = Operation::FUnordNotEqual; break;
case 15: inst.op = Operation::FOrdTrue; break;
case 32: inst.op = Operation::IEqual; break;
case 33: inst.op = Operation::INotEqual; break;
case 34: inst.op = Operation::UGreater; break;
case 35: inst.op = Operation::UGreaterEqual; break;
case 36: inst.op = Operation::ULess; break;
case 37: inst.op = Operation::ULessEqual; break;
case 38: inst.op = Operation::SGreater; break;
case 39: inst.op = Operation::SGreaterEqual; break;
case 40: inst.op = Operation::SLess; break;
case 41: inst.op = Operation::SLessEqual; break;
default:
inst.op = Operation::FOrdFalse;
RDCERR("Unexpected comparison %llu", opcode);
break;
}
// fast math flags
if(op.remaining() > 0)
inst.opFlags = op.get<InstructionFlags>();
inst.type = GetBoolType();
// if we're comparing vectors, the return type is an equal sized bool vector
if(argType->type == Type::Vector)
{
for(const Type &t : m_Types)
{
if(t.type == Type::Vector && t.inner == inst.type &&
t.elemCount == argType->elemCount)
{
inst.type = &t;
break;
}
}
}
RDCASSERT(inst.type->type == argType->type &&
inst.type->elemCount == argType->elemCount);
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_SELECT || op.type == FunctionRecord::INST_VSELECT)
{
Instruction inst;
inst.op = Operation::Select;
// if true
inst.args.push_back(op.getSymbol());
inst.type = op.getType(f, inst.args.back());
// if false
inst.args.push_back(op.getSymbol(false));
// selector
if(op.type == FunctionRecord::INST_SELECT)
inst.args.push_back(op.getSymbol(false));
else
inst.args.push_back(op.getSymbol());
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_BR)
{
Instruction inst;
inst.op = Operation::Branch;
inst.type = GetVoidType();
// true destination
uint64_t trueDest = op.get<uint64_t>();
inst.args.push_back(Symbol(SymbolType::BasicBlock, trueDest));
f.blocks[(size_t)trueDest].preds.insert(0, &f.blocks[curBlock]);
if(op.remaining() > 0)
{
// false destination
uint64_t falseDest = op.get<uint64_t>();
inst.args.push_back(Symbol(SymbolType::BasicBlock, falseDest));
f.blocks[(size_t)falseDest].preds.insert(0, &f.blocks[curBlock]);
// predicate
inst.args.push_back(op.getSymbol(false));
}
curBlock++;
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_SWITCH)
{
Instruction inst;
inst.op = Operation::Switch;
inst.type = GetVoidType();
uint64_t typeIdx = op.get<uint64_t>();
static const uint64_t SWITCH_INST_MAGIC = 0x4B5;
if((typeIdx >> 16) == SWITCH_INST_MAGIC)
{
// type of condition
const Type *condType = op.getType();
RDCASSERT(condType->bitWidth <= 64);
// condition
inst.args.push_back(op.getSymbol(false));
// default block
uint64_t defaultDest = op.get<uint64_t>();
inst.args.push_back(Symbol(SymbolType::BasicBlock, defaultDest));
f.blocks[(size_t)defaultDest].preds.insert(0, &f.blocks[curBlock]);
RDCERR("Unsupported switch instruction version");
}
else
{
// condition
inst.args.push_back(op.getSymbol(false));
// default block
uint64_t defaultDest = op.get<uint64_t>();
inst.args.push_back(Symbol(SymbolType::BasicBlock, defaultDest));
f.blocks[(size_t)defaultDest].preds.insert(0, &f.blocks[curBlock]);
uint64_t numCases = op.remaining() / 2;
for(uint64_t c = 0; c < numCases; c++)
{
// case value, absolute not relative
inst.args.push_back(op.getSymbolAbsolute());
// case block
uint64_t caseDest = op.get<uint64_t>();
inst.args.push_back(Symbol(SymbolType::BasicBlock, caseDest));
f.blocks[(size_t)caseDest].preds.insert(0, &f.blocks[curBlock]);
}
}
curBlock++;
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_PHI)
{
Instruction inst;
inst.op = Operation::Phi;
inst.type = op.getType();
while(op.remaining() > 0)
{
int64_t valSrc = LLVMBC::BitReader::svbr(op.get<uint64_t>());
uint64_t blockSrc = op.get<uint64_t>();
if(valSrc <= 0)
{
inst.args.push_back(Symbol(SymbolType::Unknown, m_Symbols.size() - valSrc));
}
else
{
inst.args.push_back(op.getSymbol((uint64_t)valSrc));
}
inst.args.push_back(Symbol(SymbolType::BasicBlock, blockSrc));
}
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_LOADATOMIC)
{
Instruction inst;
inst.op = Operation::LoadAtomic;
inst.args.push_back(op.getSymbol());
if(op.remaining() == 5)
{
inst.type = op.getType();
}
else
{
inst.type = op.getType(f, inst.args.back());
RDCASSERT(inst.type->type == Type::Pointer);
inst.type = inst.type->inner;
}
inst.align = (1U << op.get<uint64_t>()) >> 1;
inst.opFlags |= (op.get<uint64_t>() != 0) ? InstructionFlags::Volatile
: InstructionFlags::NoFlags;
// success ordering
uint64_t opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: break;
case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break;
case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break;
case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break;
case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break;
case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break;
case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break;
default:
RDCERR("Unexpected success ordering %llu", opcode);
inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent;
break;
}
// synchronisation scope
opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.opFlags |= InstructionFlags::SingleThread; break;
case 1: break;
default: RDCERR("Unexpected synchronisation scope %llu", opcode); break;
}
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_STOREATOMIC_OLD ||
op.type == FunctionRecord::INST_STOREATOMIC)
{
Instruction inst;
inst.op = Operation::StoreAtomic;
inst.type = GetVoidType();
inst.args.push_back(op.getSymbol());
if(op.type == FunctionRecord::INST_STOREATOMIC_OLD)
inst.args.push_back(op.getSymbol(false));
else
inst.args.push_back(op.getSymbol());
inst.align = (1U << op.get<uint64_t>()) >> 1;
inst.opFlags |= (op.get<uint64_t>() != 0) ? InstructionFlags::Volatile
: InstructionFlags::NoFlags;
// success ordering
uint64_t opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: break;
case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break;
case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break;
case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break;
case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break;
case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break;
case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break;
default:
RDCERR("Unexpected success ordering %llu", opcode);
inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent;
break;
}
// synchronisation scope
opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.opFlags |= InstructionFlags::SingleThread; break;
case 1: break;
default: RDCERR("Unexpected synchronisation scope %llu", opcode); break;
}
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_ATOMICRMW)
{
Instruction inst;
// pointer to atomically modify
inst.args.push_back(op.getSymbol());
// type is the pointee of the first argument
inst.type = op.getType(f, inst.args.back());
RDCASSERT(inst.type->type == Type::Pointer);
inst.type = inst.type->inner;
// parameter value
inst.args.push_back(op.getSymbol(false));
uint64_t opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.op = Operation::AtomicExchange; break;
case 1: inst.op = Operation::AtomicAdd; break;
case 2: inst.op = Operation::AtomicSub; break;
case 3: inst.op = Operation::AtomicAnd; break;
case 4: inst.op = Operation::AtomicNand; break;
case 5: inst.op = Operation::AtomicOr; break;
case 6: inst.op = Operation::AtomicXor; break;
case 7: inst.op = Operation::AtomicMax; break;
case 8: inst.op = Operation::AtomicMin; break;
case 9: inst.op = Operation::AtomicUMax; break;
case 10: inst.op = Operation::AtomicUMin; break;
default:
RDCERR("Unhandled atomicrmw op %llu", opcode);
inst.op = Operation::AtomicExchange;
break;
}
if(op.get<uint64_t>())
inst.opFlags |= InstructionFlags::Volatile;
// success ordering
opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: break;
case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break;
case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break;
case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break;
case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break;
case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break;
case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break;
default:
RDCERR("Unexpected success ordering %llu", opcode);
inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent;
break;
}
// synchronisation scope
opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.opFlags |= InstructionFlags::SingleThread; break;
case 1: break;
default: RDCERR("Unexpected synchronisation scope %llu", opcode); break;
}
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_CMPXCHG ||
op.type == FunctionRecord::INST_CMPXCHG_OLD)
{
Instruction inst;
inst.op = Operation::CompareExchange;
// pointer to atomically modify
inst.args.push_back(op.getSymbol());
// type is the pointee of the first argument
inst.type = op.getType(f, inst.args.back());
RDCASSERT(inst.type->type == Type::Pointer);
inst.type = inst.type->inner;
// combined with a bool, search for a struct like that
const Type *boolType = GetBoolType();
for(const Type &t : m_Types)
{
if(t.type == Type::Struct && t.members.size() == 2 && t.members[0] == inst.type &&
t.members[1] == boolType)
{
inst.type = &t;
break;
}
}
RDCASSERT(inst.type->type == Type::Struct);
// expect modern encoding with weak parameters.
RDCASSERT(funcChild.ops.size() >= 8);
// compare value
if(op.type == FunctionRecord::INST_CMPXCHG_OLD)
inst.args.push_back(op.getSymbol(false));
else
inst.args.push_back(op.getSymbol());
// new replacement value
inst.args.push_back(op.getSymbol(false));
if(op.get<uint64_t>())
inst.opFlags |= InstructionFlags::Volatile;
// success ordering
uint64_t opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: break;
case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break;
case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break;
case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break;
case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break;
case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break;
case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break;
default:
RDCERR("Unexpected success ordering %llu", opcode);
inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent;
break;
}
// synchronisation scope
opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.opFlags |= InstructionFlags::SingleThread; break;
case 1: break;
default: RDCERR("Unexpected synchronisation scope %llu", opcode); break;
}
// failure ordering
opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: break;
case 1: inst.opFlags |= InstructionFlags::FailureUnordered; break;
case 2: inst.opFlags |= InstructionFlags::FailureMonotonic; break;
case 3: inst.opFlags |= InstructionFlags::FailureAcquire; break;
case 4: inst.opFlags |= InstructionFlags::FailureRelease; break;
case 5: inst.opFlags |= InstructionFlags::FailureAcquireRelease; break;
case 6: inst.opFlags |= InstructionFlags::FailureSequentiallyConsistent; break;
default:
RDCERR("Unexpected failure ordering %llu", opcode);
inst.opFlags |= InstructionFlags::FailureSequentiallyConsistent;
break;
}
if(op.get<uint64_t>())
inst.opFlags |= InstructionFlags::Weak;
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_FENCE)
{
Instruction inst;
inst.op = Operation::Fence;
inst.type = GetVoidType();
// success ordering
uint64_t opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: break;
case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break;
case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break;
case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break;
case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break;
case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break;
case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break;
default:
RDCERR("Unexpected success ordering %llu", opcode);
inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent;
break;
}
// synchronisation scope
opcode = op.get<uint64_t>();
switch(opcode)
{
case 0: inst.opFlags |= InstructionFlags::SingleThread; break;
case 1: break;
default: RDCERR("Unexpected synchronisation scope %llu", opcode); break;
}
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_EXTRACTELT)
{
// DXIL claims to be scalarised so should this appear?
RDCWARN("Unexpected vector instruction extractelement in DXIL");
Instruction inst;
inst.op = Operation::ExtractElement;
// vector
inst.args.push_back(op.getSymbol());
// result is the scalar type within the vector
inst.type = op.getType(f, inst.args.back())->inner;
// index
inst.args.push_back(op.getSymbol());
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_INSERTELT)
{
// DXIL claims to be scalarised so should this appear?
RDCWARN("Unexpected vector instruction insertelement in DXIL");
Instruction inst;
inst.op = Operation::InsertElement;
// vector
inst.args.push_back(op.getSymbol());
// result is the vector type
inst.type = op.getType(f, inst.args.back());
// replacement element
inst.args.push_back(op.getSymbol(false));
// index
inst.args.push_back(op.getSymbol());
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_SHUFFLEVEC)
{
// DXIL claims to be scalarised so should this appear?
RDCWARN("Unexpected vector instruction shufflevector in DXIL");
Instruction inst;
inst.op = Operation::ShuffleVector;
// vector 1
inst.args.push_back(op.getSymbol());
const Type *vecType = op.getType(f, inst.args.back());
// vector 2
inst.args.push_back(op.getSymbol(false));
// indexes
inst.args.push_back(op.getSymbol());
// result is a vector with the inner type of the first two vectors and the element
// count of the last vector
const Type *maskType = op.getType(f, inst.args.back());
for(const Type &t : m_Types)
{
if(t.type == Type::Vector && t.inner == vecType->inner &&
t.elemCount == maskType->elemCount)
{
inst.type = &t;
break;
}
}
RDCASSERT(inst.type);
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_INSERTVAL)
{
// DXIL claims to be scalarised so should this appear?
RDCWARN("Unexpected aggregate instruction insertvalue in DXIL");
Instruction inst;
inst.op = Operation::InsertValue;
// aggregate
inst.args.push_back(op.getSymbol());
// result is the aggregate type
inst.type = op.getType(f, inst.args.back());
// replacement element
inst.args.push_back(op.getSymbol());
// indices as literals
while(op.remaining() > 0)
inst.args.push_back(Symbol(SymbolType::Literal, op.get<uint64_t>()));
m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()});
f.instructions.push_back(inst);
}
else if(op.type == FunctionRecord::INST_VAARG)
{
// don't expect vararg instructions
RDCERR("Unexpected vararg instruction %u in DXIL", op.type);
}
else if(op.type == FunctionRecord::INST_LANDINGPAD ||
op.type == FunctionRecord::INST_LANDINGPAD_OLD ||
op.type == FunctionRecord::INST_INVOKE || op.type == FunctionRecord::INST_RESUME)
{
// don't expect exception handling instructions
RDCERR("Unexpected exception handling instruction %u in DXIL", op.type);
}
else
{
RDCERR("Unexpected record in FUNCTION_BLOCK");
continue;
}
}
}
RDCASSERT(curBlock == f.blocks.size());
size_t resultID = 0;
if(f.blocks[0].name.empty())
f.blocks[0].resultID = (uint32_t)resultID++;
curBlock = 0;
for(size_t i = 0; i < f.instructions.size(); i++)
{
// fix up forward references here, we couldn't write them up front because we didn't know
// how many actual symbols (non-void instructions) existed after the given instruction
for(Symbol &s : f.instructions[i].args)
{
if(s.type == SymbolType::Unknown)
{
s = m_Symbols[(size_t)s.idx];
RDCASSERT(s.type == SymbolType::Instruction);
}
}
if(f.instructions[i].op == Operation::Branch ||
f.instructions[i].op == Operation::Unreachable ||
f.instructions[i].op == Operation::Switch || f.instructions[i].op == Operation::Ret)
{
curBlock++;
if(i == f.instructions.size() - 1)
break;
if(f.blocks[curBlock].name.empty())
f.blocks[curBlock].resultID = (uint32_t)resultID++;
continue;
}
if(f.instructions[i].type->isVoid())
continue;
if(!f.instructions[i].name.empty())
continue;
f.instructions[i].resultID = (uint32_t)resultID++;
}
// rebase metadata, we get indices that skip void results, so look up the Symbols directory
// to get to a normal instruction index
for(Metadata &m : f.metadata)
if(m.func)
m.instruction = (size_t)m_Symbols[instrSymbolStart + m.instruction].idx;
m_Symbols.resize(prevNumSymbols);
}
else
{
RDCERR("Unknown block ID %u encountered at module scope", rootchild.id);
}
}
}
RDCASSERT(functionDecls.empty());
}
uint32_t Program::GetOrAssignMetaID(Metadata *m)
{
if(m->id != ~0U)
return m->id;
m->id = m_NextMetaID++;
m_NumberedMeta.push_back(m);
// assign meta IDs to the children now
for(Metadata *c : m->children)
{
if(!c || c->isConstant)
continue;
GetOrAssignMetaID(c);
}
return m->id;
}
uint32_t Program::GetOrAssignMetaID(DebugLocation &l)
{
if(l.id != ~0U)
return l.id;
l.id = m_NextMetaID++;
if(l.scope)
GetOrAssignMetaID(l.scope);
if(l.inlinedAt)
GetOrAssignMetaID(l.inlinedAt);
return l.id;
}
const Type *Program::GetSymbolType(const Function &f, Symbol s)
{
const Type *ret = NULL;
switch(s.type)
{
case SymbolType::Constant:
if(s.idx < m_Constants.size())
ret = m_Constants[(size_t)s.idx].type;
else
ret = f.constants[(size_t)s.idx - m_Constants.size()].type;
break;
case SymbolType::Argument: ret = f.funcType->members[(size_t)s.idx]; break;
case SymbolType::Instruction: ret = f.instructions[(size_t)s.idx].type; break;
case SymbolType::GlobalVar: ret = m_GlobalVars[(size_t)s.idx].type; break;
case SymbolType::Function: ret = m_Functions[(size_t)s.idx].funcType; break;
case SymbolType::Metadata:
if(s.idx < m_Metadata.size())
ret = m_Metadata[(size_t)s.idx].type;
else
ret = f.metadata[(size_t)s.idx - m_Metadata.size()].type;
break;
case SymbolType::Unknown:
case SymbolType::Alias:
case SymbolType::BasicBlock:
case SymbolType::Literal: RDCERR("Unexpected symbol to get type for %d", s.type); break;
}
return ret;
}
const Constant *Program::GetFunctionConstant(const Function &f, uint64_t v)
{
size_t idx = (size_t)v;
return idx < m_Constants.size() ? &m_Constants[idx] : &f.constants[idx - m_Constants.size()];
}
const Metadata *Program::GetFunctionMetadata(const Function &f, uint64_t v)
{
size_t idx = (size_t)v;
return idx < m_Metadata.size() ? &m_Metadata[idx] : &f.metadata[idx - m_Metadata.size()];
}
const DXIL::Type *Program::GetVoidType()
{
if(m_VoidType)
return m_VoidType;
for(size_t i = 0; i < m_Types.size(); i++)
{
if(m_Types[i].isVoid())
{
m_VoidType = &m_Types[i];
break;
}
}
if(!m_VoidType)
RDCERR("Couldn't find void type");
return m_VoidType;
}
const DXIL::Type *Program::GetBoolType()
{
if(m_BoolType)
return m_BoolType;
for(size_t i = 0; i < m_Types.size(); i++)
{
if(m_Types[i].type == Type::Scalar && m_Types[i].scalarType == Type::Int &&
m_Types[i].bitWidth == 1)
{
m_BoolType = &m_Types[i];
break;
}
}
if(!m_BoolType)
RDCERR("Couldn't find void type");
return m_BoolType;
}
const Type *Program::GetPointerType(const Type *type, Type::PointerAddrSpace addrSpace) const
{
for(const Type &t : m_Types)
{
if(t.type == Type::Pointer && t.inner == type && t.addrSpace == addrSpace)
{
return &t;
}
}
RDCERR("Couldn't find pointer type");
return type;
}
Metadata::~Metadata()
{
SAFE_DELETE(dwarf);
SAFE_DELETE(debugLoc);
}
}; // namespace DXIL