/****************************************************************************** * 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 #include #include #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 getType, std::function getPtrType, std::function getConstant, std::function 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(); // 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()]; } const Type *getType() { return &prog->m_Types[get()]; } const Type *getType(const Function &f, Symbol s) { if(s.type == SymbolType::Unknown) return m_LastType; return prog->GetSymbolType(f, s); } template T get() { return (T)values[idx++]; } private: const rdcarray &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 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 ¶mattr : 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> 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()); curBlock = 0; } else if(op.type == FunctionRecord::DEBUG_LOC) { DebugLocation debugLoc; debugLoc.line = op.get(); debugLoc.col = op.get(); debugLoc.scope = getMetaOrNull(op.get()); debugLoc.inlinedAt = getMetaOrNull(op.get()); 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(); if(attr > 0) inst.paramAttrs = &m_Attributes[attr - 1]; uint64_t callingFlags = op.get(); if(callingFlags & (1ULL << 17)) inst.opFlags = op.get(); if(callingFlags & (1ULL << 15)) op.get(); // 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()); } 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(); 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(); 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(); 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(); 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(); 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()) 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()) >> 1; inst.opFlags |= (op.get() != 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()) >> 1; inst.opFlags |= (op.get() != 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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 blockSrc = op.get(); 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()) >> 1; inst.opFlags |= (op.get() != 0) ? InstructionFlags::Volatile : InstructionFlags::NoFlags; // success ordering uint64_t opcode = op.get(); 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(); 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()) >> 1; inst.opFlags |= (op.get() != 0) ? InstructionFlags::Volatile : InstructionFlags::NoFlags; // success ordering uint64_t opcode = op.get(); 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(); 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(); 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()) inst.opFlags |= InstructionFlags::Volatile; // success ordering opcode = op.get(); 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(); 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()) inst.opFlags |= InstructionFlags::Volatile; // success ordering uint64_t opcode = op.get(); 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(); 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(); 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()) 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(); 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(); 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())); 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