Files
dxil-spirv/dxil_parser.cpp

550 lines
14 KiB
C++

/* Copyright (c) 2019-2022 Hans-Kristian Arntzen for Valve Corporation
*
* SPDX-License-Identifier: MIT
*
* 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_parser.hpp"
#include "dxil.hpp"
#include "memory_stream.hpp"
#include "logging.hpp"
#include <stdio.h>
#include <string.h>
#include <vector>
namespace dxil_spv
{
bool is_mangled_entry_point(const char *user)
{
// The mangling algorithm is intentionally left undefined in spec.
// However, the mangling scheme clearly follows MSVC here.
// The format we're looking for is:
// <blah>?<identifier>@<suffix>.
// http://www.agner.org/optimize/calling_conventions.pdf (section 8.1).
// DXC also seems to start with '\01', but we can ignore that.
const char *mangle_begin = strchr(user, '?');
if (!mangle_begin)
return false;
const char *mangle_end = strchr(mangle_begin + 1, '@');
return mangle_end != nullptr;
}
String demangle_entry_point(const String &entry)
{
auto start_idx = entry.find_first_of('?');
if (start_idx == std::string::npos)
return entry;
start_idx++;
auto end_idx = entry.find_first_of('@', start_idx);
if (end_idx == std::string::npos)
return entry;
return entry.substr(start_idx, end_idx - start_idx);
}
Vector<uint8_t> &DXILContainerParser::get_blob()
{
return dxil_blob;
}
Vector<RDATSubobject> &DXILContainerParser::get_rdat_subobjects()
{
return rdat_subobjects;
}
bool DXILContainerParser::parse_dxil(MemoryStream &stream)
{
DXIL::ProgramHeader program_header;
if (!stream.read(program_header))
return false;
if (static_cast<DXIL::FourCC>(program_header.dxil_magic) != DXIL::FourCC::DXIL)
return false;
constexpr uint32_t DxilMagicPad = sizeof(DXIL::ProgramHeader) - offsetof(DXIL::ProgramHeader, dxil_magic);
if (program_header.bitcode_offset < DxilMagicPad)
return false;
auto substream = stream.create_substream_bitcode_size(
stream.get_offset() + program_header.bitcode_offset - DxilMagicPad,
program_header.bitcode_size);
dxil_blob.resize(substream.get_size());
if (!substream.read(dxil_blob.data(), substream.get_size()))
return false;
return true;
}
bool DXILContainerParser::parse_iosg1(MemoryStream &stream, Vector<DXIL::IOElement> &elements)
{
uint32_t element_count;
if (!stream.read(element_count))
return false;
if (!stream.skip(sizeof(uint32_t)))
return false;
elements.resize(element_count);
for (uint32_t i = 0; i < element_count; i++)
{
if (!stream.read(elements[i].stream_index))
return false;
uint32_t string_offset;
if (!stream.read(string_offset))
return false;
if (!stream.read(elements[i].semantic_index))
return false;
if (!stream.read(elements[i].system_value_semantic))
return false;
if (!stream.read(elements[i].component_type))
return false;
if (!stream.read(elements[i].register_index))
return false;
if (!stream.read(elements[i].mask))
return false;
if (!stream.read(elements[i].min_precision))
return false;
size_t offset = stream.get_offset();
if (!stream.seek(string_offset))
return false;
const char *semantic_name;
if (!stream.map_string_iterate(semantic_name))
return false;
elements[i].semantic_name = semantic_name;
if (!stream.seek(offset))
return false;
}
return true;
}
bool DXILContainerParser::parse_rdat(MemoryStream &stream)
{
uint32_t version, part_count;
if (!stream.read(version))
return false;
if (!stream.read(part_count))
return false;
constexpr uint32_t RDAT_Version = 0x10;
if (version != RDAT_Version)
return false;
Vector<uint32_t> offsets(part_count);
for (uint32_t i = 0; i < part_count; i++)
if (!stream.read(offsets[i]))
return false;
MemoryStream string_buffer;
MemoryStream index_buffer;
MemoryStream raw_bytes;
for (uint32_t i = 0; i < part_count; i++)
{
if (offsets[i] + 2 * sizeof(uint32_t) > stream.get_size())
return false;
uint32_t part_size = i + 1 < part_count ?
(offsets[i + 1] - offsets[i]) :
uint32_t(stream.get_size() - offsets[i]);
auto substream = stream.create_substream(offsets[i], part_size);
DXIL::RuntimeDataPartType type;
if (!substream.read(type))
return false;
uint32_t subpart_length;
if (!substream.read(subpart_length))
return false;
if (subpart_length + 2 * sizeof(uint32_t) > substream.get_size())
return false;
switch (type)
{
case DXIL::RuntimeDataPartType::StringBuffer:
{
string_buffer = substream.create_substream(substream.get_offset(), subpart_length);
break;
}
case DXIL::RuntimeDataPartType::IndexArrays:
{
index_buffer = substream.create_substream(substream.get_offset(), subpart_length);
break;
}
case DXIL::RuntimeDataPartType::RawBytes:
{
raw_bytes = substream.create_substream(substream.get_offset(), subpart_length);
break;
}
case DXIL::RuntimeDataPartType::SubobjectTable:
{
uint32_t record_count;
uint32_t record_stride;
if (!substream.read(record_count))
return false;
if (!substream.read(record_stride))
return false;
for (unsigned record = 0; record < record_count; record++)
{
auto record_stream =
substream.create_substream(substream.get_offset() + record * record_stride, record_stride);
DXIL::SubobjectKind kind;
if (!record_stream.read(kind))
return false;
switch (kind)
{
case DXIL::SubobjectKind::StateObjectConfig:
{
uint32_t name_offset;
if (!record_stream.read(name_offset))
return false;
const char *str = nullptr;
if (!string_buffer.map_string_absolute(str, name_offset))
return false;
uint32_t flag;
if (!record_stream.read(flag))
return false;
RDATSubobject elem = {};
elem.kind = kind;
elem.subobject_name = str;
elem.args[0] = flag;
rdat_subobjects.push_back(std::move(elem));
break;
}
case DXIL::SubobjectKind::RaytracingShaderConfig:
{
uint32_t name_offset;
if (!record_stream.read(name_offset))
return false;
const char *str;
if (!string_buffer.map_string_absolute(str, name_offset))
return false;
uint32_t max_payload_size, max_attribute_size;
if (!record_stream.read(max_payload_size))
return false;
if (!record_stream.read(max_attribute_size))
return false;
RDATSubobject elem = {};
elem.kind = kind;
elem.subobject_name = str;
elem.args[0] = max_payload_size;
elem.args[1] = max_attribute_size;
rdat_subobjects.push_back(std::move(elem));
break;
}
case DXIL::SubobjectKind::RaytracingPipelineConfig:
case DXIL::SubobjectKind::RaytracingPipelineConfig1:
{
uint32_t name_offset;
if (!record_stream.read(name_offset))
return false;
const char *str;
if (!string_buffer.map_string_absolute(str, name_offset))
return false;
uint32_t max_recursion_depth;
uint32_t flags = 0;
if (!record_stream.read(max_recursion_depth))
return false;
if (kind == DXIL::SubobjectKind::RaytracingPipelineConfig1)
if (!record_stream.read(flags))
return false;
RDATSubobject elem = {};
elem.kind = kind;
elem.subobject_name = str;
elem.args[0] = max_recursion_depth;
elem.args[1] = flags;
rdat_subobjects.push_back(std::move(elem));
break;
}
case DXIL::SubobjectKind::HitGroup:
{
uint32_t name_offset;
if (!record_stream.read(name_offset))
return false;
const char *hg_name;
if (!string_buffer.map_string_absolute(hg_name, name_offset))
return false;
DXIL::HitGroupType hit_group_type;
if (!record_stream.read(hit_group_type))
return false;
uint32_t ahit_name_offset, chit_name_offset, intersection_name_offset;
if (!record_stream.read(ahit_name_offset))
return false;
if (!record_stream.read(chit_name_offset))
return false;
if (!record_stream.read(intersection_name_offset))
return false;
const char *ahit, *chit, *intersection;
if (!string_buffer.map_string_absolute(ahit, ahit_name_offset))
return false;
if (!string_buffer.map_string_absolute(chit, chit_name_offset))
return false;
if (!string_buffer.map_string_absolute(intersection, intersection_name_offset))
return false;
RDATSubobject elem = {};
elem.kind = kind;
elem.subobject_name = hg_name;
elem.hit_group_type = hit_group_type;
elem.exports = { ahit, chit, intersection };
rdat_subobjects.push_back(std::move(elem));
break;
}
case DXIL::SubobjectKind::SubobjectToExportsAssociation:
{
RDATSubobject elem = {};
elem.kind = kind;
uint32_t name_offset;
if (!record_stream.read(name_offset))
return false;
const char *name;
if (!string_buffer.map_string_absolute(name, name_offset))
return false;
elem.subobject_name = name;
if (!record_stream.read(name_offset))
return false;
const char *object_name;
if (!string_buffer.map_string_absolute(object_name, name_offset))
return false;
elem.exports.push_back(object_name);
uint32_t index_offset;
if (!record_stream.read(index_offset))
return false;
auto index_substream = index_buffer.create_substream(sizeof(uint32_t) * index_offset);
uint32_t count;
if (!index_substream.read(count))
return false;
for (uint32_t export_index = 0; export_index < count; export_index++)
{
if (!index_substream.read(name_offset))
return false;
if (!string_buffer.map_string_absolute(object_name, name_offset))
return false;
elem.exports.push_back(object_name);
}
rdat_subobjects.push_back(std::move(elem));
break;
}
case DXIL::SubobjectKind::GlobalRootSignature:
case DXIL::SubobjectKind::LocalRootSignature:
{
uint32_t name_offset;
if (!record_stream.read(name_offset))
return false;
const char *name;
if (!string_buffer.map_string_absolute(name, name_offset))
return false;
uint32_t byte_offset;
uint32_t byte_size;
if (!record_stream.read(byte_offset))
return false;
if (!record_stream.read(byte_size))
return false;
auto name_substream = raw_bytes.create_substream(byte_offset, byte_size);
auto *data = name_substream.map_read<uint8_t>(byte_size);
RDATSubobject elem = {};
elem.kind = kind;
elem.subobject_name = name;
elem.payload = data;
elem.payload_size = byte_size;
rdat_subobjects.push_back(std::move(elem));
break;
}
default:
break;
}
}
break;
}
default:
break;
}
}
return true;
}
bool DXILContainerParser::parse_container(const void *data, size_t size, bool reflection)
{
MemoryStream stream(data, size);
DXIL::ContainerHeader container_header;
if (!stream.read(container_header))
return false;
if (static_cast<DXIL::FourCC>(container_header.header_fourcc) != DXIL::FourCC::Container)
return false;
if (container_header.container_size_in_bytes > size)
return false;
Vector<uint32_t> parts(container_header.part_count);
for (uint32_t i = 0; i < container_header.part_count; i++)
{
if (!stream.read(parts[i]))
return false;
}
for (auto &part_offset : parts)
{
if (!stream.seek(part_offset))
return false;
DXIL::PartHeader part_header;
if (!stream.read(part_header))
return false;
auto fourcc = static_cast<DXIL::FourCC>(part_header.part_fourcc);
if (fourcc == DXIL::FourCC::SHDR || fourcc == DXIL::FourCC::SHEX)
dxbc_binary = true;
}
for (auto &part_offset : parts)
{
if (!stream.seek(part_offset))
return false;
DXIL::PartHeader part_header;
if (!stream.read(part_header))
return false;
auto fourcc = static_cast<DXIL::FourCC>(part_header.part_fourcc);
switch (fourcc)
{
case DXIL::FourCC::DXIL:
case DXIL::FourCC::ShaderStatistics:
{
DXIL::FourCC expected = reflection ? DXIL::FourCC::ShaderStatistics : DXIL::FourCC::DXIL;
if (expected != fourcc || dxbc_binary)
break;
// The STAT block includes a DXIL blob that is literally the same DXIL IR
// minus code + string names in the metadata chunks ... <__________________________________<
auto substream = stream.create_substream(stream.get_offset(), part_header.part_size);
if (!parse_dxil(substream))
return false;
break;
}
case DXIL::FourCC::FeatureInfo:
break;
case DXIL::FourCC::InputSignature:
{
auto substream = stream.create_substream(stream.get_offset(), part_header.part_size);
if (!parse_iosg1(substream, input_elements))
return false;
break;
}
case DXIL::FourCC::OutputSignature:
{
auto substream = stream.create_substream(stream.get_offset(), part_header.part_size);
if (!parse_iosg1(substream, output_elements))
return false;
break;
}
case DXIL::FourCC::PatchConstantSignature:
break;
case DXIL::FourCC::PrivateData:
break;
case DXIL::FourCC::RootSignature:
break;
case DXIL::FourCC::PipelineStateValidation:
break;
case DXIL::FourCC::ResourceDef:
break;
case DXIL::FourCC::ShaderHash:
break;
case DXIL::FourCC::RuntimeData:
{
auto substream = stream.create_substream(stream.get_offset(), part_header.part_size);
if (!parse_rdat(substream))
return false;
break;
}
default:
break;
}
}
return true;
}
} // namespace dxil_spv