blob: b3ae4541cb2a8c5f4cb2e8726d35178dc7e48424 [file] [log] [blame] [edit]
///////////////////////////////////////////////////////////////////////////////
// //
// DxcBindingTable.cpp //
// Copyright (C) Microsoft Corporation. All rights reserved. //
// This file is distributed under the University of Illinois Open Source //
// License. See LICENSE.TXT for details. //
// //
///////////////////////////////////////////////////////////////////////////////
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Twine.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Metadata.h"
#include "llvm/IR/Module.h"
#include "dxc/DXIL/DxilMetadataHelper.h"
#include "dxc/DXIL/DxilModule.h"
#include "dxc/DXIL/DxilResourceBase.h"
#include "dxc/DxcBindingTable/DxcBindingTable.h"
#include <ctype.h>
#include <set>
using namespace llvm;
using namespace hlsl;
namespace {
enum IntegerConversionStatus {
Success,
OutOfBounds,
Invalid,
Empty,
};
static IntegerConversionStatus ToUnsigned32(StringRef str,
uint32_t *outInteger) {
*outInteger = 0;
if (str.empty())
return IntegerConversionStatus::Empty;
llvm::APInt integer;
if (llvm::StringRef(str).getAsInteger(0, integer)) {
return IntegerConversionStatus::Invalid;
}
if (integer != 0 && integer.getBitWidth() > 32) {
return IntegerConversionStatus::OutOfBounds;
}
*outInteger = (uint32_t)integer.getLimitedValue();
return IntegerConversionStatus::Success;
}
} // namespace
bool hlsl::ParseBindingTable(llvm::StringRef fileName, llvm::StringRef content,
llvm::raw_ostream &errors,
DxcBindingTable *outTable) {
struct Parser {
StringRef fileName;
const char *curr = nullptr;
const char *end = nullptr;
int line = 1;
int col = 1;
llvm::raw_ostream &errors;
bool WasEndOfLine = false;
struct Location {
int line = 0;
int col = 0;
};
inline static bool IsDelimiter(char c) { return c == ','; }
inline static bool IsNewline(char c) { return c == '\r' || c == '\n'; }
inline static bool IsEndOfLine(char c) {
return IsNewline(c) || c == ';' || c == '\0';
}
inline static bool IsWhitespace(char c) { return c == ' ' || c == '\t'; }
inline Parser(StringRef fileName, StringRef content,
llvm::raw_ostream &errors)
: fileName(fileName), curr(content.data()),
end(content.data() + content.size()), errors(errors) {
EatWhiteSpaceAndNewlines();
}
inline bool WasJustEndOfLine() const { return WasEndOfLine; }
inline void EatWhitespace() {
for (;;) {
if (IsWhitespace(Peek()))
Advance();
else
break;
}
}
inline void EatWhiteSpaceAndNewlines() {
for (;;) {
if (IsWhitespace(Peek()) || IsNewline(Peek()))
Advance();
else
break;
}
}
inline Location GetLoc() const {
Location loc;
loc.line = line;
loc.col = col;
return loc;
}
void Advance() {
if (ReachedEnd())
return;
if (*curr == '\n') {
line++;
col = 1;
} else if (*curr != '\r') {
col++;
}
curr++;
}
inline bool ReachedEnd() const { return curr >= end || *curr == '\0'; }
inline void Warn(Location loc, const Twine &err) { (void)Error(loc, err); }
inline bool Error(Location loc, const Twine &err) {
errors << (Twine(fileName) + ":" + Twine(loc.line) + ":" +
Twine(loc.col) + ": " + err + "\n")
.str();
return false;
}
inline bool Error(const Twine &err) {
Error(GetLoc(), err);
return false;
}
inline char Peek() const {
if (ReachedEnd())
return '\0';
return *curr;
}
bool ParseCell(SmallVectorImpl<char> *str) {
EatWhitespace();
if (ReachedEnd()) {
return Error("Unexpected EOF when parsing cell.");
}
bool hasQuote = false;
if (Peek() == '"') {
hasQuote = true;
Advance();
}
while (!ReachedEnd()) {
if (IsEndOfLine(Peek()) || (!hasQuote && IsDelimiter(Peek()))) {
if (hasQuote && IsNewline(Peek()))
return Error("Unexpected newline inside quotation.");
// Trim the white space at the end of the string
if (str) {
while (str->size() && IsWhitespace(str->back())) {
str->pop_back();
}
}
break;
}
// Double quotes
if (Peek() == '"') {
Advance();
if (!hasQuote)
return Error("'\"' not allowed in non-quoted cell.");
EatWhitespace();
if (!IsDelimiter(Peek()) && !IsEndOfLine(Peek())) {
return Error("Unexpected character after quote.");
}
break;
}
if (str) {
str->push_back(Peek());
}
Advance();
}
// Handle delimiter
{
// If this delimiter is not a newline, set our newline flag to false.
if (!IsEndOfLine(Peek())) {
WasEndOfLine = false;
Advance();
// Eat white spaces so we can detect the next newline if this
// is a trailing comma.
EatWhitespace();
}
if (IsEndOfLine(Peek())) {
Advance(); // Skip this character, which could be ';'
WasEndOfLine = true;
EatWhiteSpaceAndNewlines();
}
}
return true;
}
bool ParseResourceIndex(hlsl::DXIL::ResourceClass *outClass,
unsigned *outIndex) {
*outClass = hlsl::DXIL::ResourceClass::Invalid;
*outIndex = UINT_MAX;
auto loc = GetLoc();
SmallString<32> str;
if (!ParseCell(&str))
return false;
if (str.empty()) {
return Error(loc, "Resource binding cannot be empty.");
}
switch (str[0]) {
case 'b':
*outClass = hlsl::DXIL::ResourceClass::CBuffer;
break;
case 's':
*outClass = hlsl::DXIL::ResourceClass::Sampler;
break;
case 't':
*outClass = hlsl::DXIL::ResourceClass::SRV;
break;
case 'u':
*outClass = hlsl::DXIL::ResourceClass::UAV;
break;
default:
return Error(loc, "Invalid resource class. Needs to be one of 'b', "
"'s', 't', or 'u'.");
break;
}
StringRef integerStr;
if (str.size() > 1) {
integerStr = StringRef(&str[1], str.size() - 1);
}
if (auto result = ToUnsigned32(integerStr, outIndex)) {
switch (result) {
case IntegerConversionStatus::OutOfBounds:
return Error(loc,
Twine() + "'" + integerStr +
"' is out of range of an 32-bit unsigned integer.");
default:
return Error(loc, Twine() + "'" + str +
"' is not a valid resource binding.");
}
}
return true;
}
inline bool ParseReourceSpace(unsigned *outResult) {
auto loc = GetLoc();
SmallString<32> str;
if (!ParseCell(&str))
return false;
if (str.empty()) {
return Error(loc, "Expected unsigned 32-bit integer for resource "
"space, but got empty cell.");
}
if (auto result = ToUnsigned32(str, outResult)) {
switch (result) {
case IntegerConversionStatus::OutOfBounds:
return Error(loc,
Twine() + "'" + str +
"' is out of range of an 32-bit unsigned integer.");
default:
return Error(loc, Twine() + "'" + str +
"' is not a valid 32-bit unsigned integer.");
}
}
return true;
}
};
Parser P(fileName, content, errors);
enum class ColumnType {
Name,
Index,
Space,
Unknown,
};
llvm::SmallVector<ColumnType, 5> columns;
std::set<ColumnType> columnsSet;
for (;;) {
llvm::SmallString<32> column;
if (!P.ParseCell(&column)) {
return false;
}
for (char &c : column)
c = tolower(c);
auto loc = P.GetLoc();
if (column == "resourcename") {
if (!columnsSet.insert(ColumnType::Name).second) {
return P.Error(loc, "Column 'ResourceName' already specified.");
}
columns.push_back(ColumnType::Name);
} else if (column == "binding") {
if (!columnsSet.insert(ColumnType::Index).second) {
return P.Error(loc, "Column 'Binding' already specified.");
}
columns.push_back(ColumnType::Index);
} else if (column == "space") {
if (!columnsSet.insert(ColumnType::Space).second) {
return P.Error(loc, "Column 'Space' already specified.");
}
columns.push_back(ColumnType::Space);
} else {
P.Warn(loc, Twine() + "Unknown column '" + column + "'");
columns.push_back(ColumnType::Unknown);
}
if (P.WasJustEndOfLine())
break;
}
if (!columnsSet.count(ColumnType::Name) ||
!columnsSet.count(ColumnType::Index) ||
!columnsSet.count(ColumnType::Space)) {
return P.Error(
Twine() +
"Input format is csv with headings: ResourceName, Binding, Space.");
}
while (!P.ReachedEnd()) {
SmallString<32> name;
hlsl::DXIL::ResourceClass cls = hlsl::DXIL::ResourceClass::Invalid;
unsigned index = 0;
unsigned space = 0;
for (unsigned i = 0; i < columns.size(); i++) {
ColumnType column = columns[i];
switch (column) {
case ColumnType::Name: {
if (!P.ParseCell(&name))
return false;
} break;
case ColumnType::Index: {
if (!P.ParseResourceIndex(&cls, &index))
return false;
} break;
case ColumnType::Space: {
if (!P.ParseReourceSpace(&space))
return false;
} break;
default: {
if (!P.ParseCell(nullptr))
return false;
} break;
}
if (P.WasJustEndOfLine() && i + 1 != columns.size()) {
return P.Error("Row ended after just " + Twine(i + 1) +
" columns. Expected " + Twine(columns.size()) + ".");
}
}
DxcBindingTable::Entry entry;
entry.space = space;
entry.index = index;
outTable->entries[DxcBindingTable::Key(name.c_str(), cls)] = entry;
if (!P.WasJustEndOfLine()) {
return P.Error(
"Unexpected cell at the end of row. There should only be " +
Twine(columns.size()) + " columns");
}
}
return true;
}
typedef std::pair<std::string, hlsl::DXIL::ResourceClass> ResourceKey;
typedef std::map<ResourceKey, DxilResourceBase *> ResourceMap;
template <typename T>
static inline void GatherResources(const std::vector<std::unique_ptr<T>> &List,
ResourceMap *Map) {
for (const std::unique_ptr<T> &ptr : List) {
(*Map)[ResourceKey(ptr->GetGlobalName(), ptr->GetClass())] = ptr.get();
}
}
void hlsl::WriteBindingTableToMetadata(llvm::Module &M,
const hlsl::DxcBindingTable &table) {
if (table.entries.empty())
return;
llvm::NamedMDNode *bindingsMD = M.getOrInsertNamedMetadata(
hlsl::DxilMDHelper::kDxilDxcBindingTableMDName);
LLVMContext &LLVMCtx = M.getContext();
// Don't add operands repeatedly
if (bindingsMD->getNumOperands()) {
return;
}
for (const std::pair<const DxcBindingTable::Key, DxcBindingTable::Entry>
&binding : table.entries) {
auto GetInt32MD = [&LLVMCtx](uint32_t val) -> llvm::ValueAsMetadata * {
return llvm::ValueAsMetadata::get(
llvm::ConstantInt::get(llvm::Type::getInt32Ty(LLVMCtx), val));
};
llvm::Metadata *operands[4] = {};
operands[hlsl::DxilMDHelper::kDxilDxcBindingTableResourceName] =
llvm::MDString::get(LLVMCtx, binding.first.first);
operands[hlsl::DxilMDHelper::kDxilDxcBindingTableResourceClass] =
GetInt32MD((unsigned)binding.first.second);
operands[hlsl::DxilMDHelper::kDxilDxcBindingTableResourceIndex] =
GetInt32MD(binding.second.index);
operands[hlsl::DxilMDHelper::kDxilDxcBindingTableResourceSpace] =
GetInt32MD(binding.second.space);
llvm::MDTuple *entry = llvm::MDNode::get(LLVMCtx, operands);
bindingsMD->addOperand(entry);
}
}
void hlsl::ApplyBindingTableFromMetadata(DxilModule &DM) {
Module &M = *DM.GetModule();
NamedMDNode *bindings =
M.getNamedMetadata(hlsl::DxilMDHelper::kDxilDxcBindingTableMDName);
if (!bindings)
return;
ResourceMap resourceMap;
GatherResources(DM.GetCBuffers(), &resourceMap);
GatherResources(DM.GetSRVs(), &resourceMap);
GatherResources(DM.GetUAVs(), &resourceMap);
GatherResources(DM.GetSamplers(), &resourceMap);
for (MDNode *mdEntry : bindings->operands()) {
Metadata *nameMD =
mdEntry->getOperand(DxilMDHelper::kDxilDxcBindingTableResourceName);
Metadata *classMD =
mdEntry->getOperand(DxilMDHelper::kDxilDxcBindingTableResourceClass);
Metadata *indexMD =
mdEntry->getOperand(DxilMDHelper::kDxilDxcBindingTableResourceIndex);
Metadata *spaceMD =
mdEntry->getOperand(DxilMDHelper::kDxilDxcBindingTableResourceSpace);
StringRef name = cast<MDString>(nameMD)->getString();
hlsl::DXIL::ResourceClass cls =
(hlsl::DXIL::ResourceClass)cast<ConstantInt>(
cast<ValueAsMetadata>(classMD)->getValue())
->getLimitedValue();
unsigned index =
cast<ConstantInt>(cast<ValueAsMetadata>(indexMD)->getValue())
->getLimitedValue();
unsigned space =
cast<ConstantInt>(cast<ValueAsMetadata>(spaceMD)->getValue())
->getLimitedValue();
auto it = resourceMap.find(ResourceKey(name, cls));
if (it != resourceMap.end()) {
DxilResourceBase *resource = it->second;
if (!resource->IsAllocated()) {
resource->SetLowerBound(index);
resource->SetSpaceID(space);
}
}
}
}