Skip to content

Commit

Permalink
[NFC] Split of SPT and SPIR-V in header parsing (#2316)
Browse files Browse the repository at this point in the history
Translator accepting two formats currently - SPIR-V and SPT.
SPT is a textual format used internally for tests without any formal specification.
Parsing a textual format is slower than binary format and SPT also is less strict
in comparison to SPIRV. Because parsing in Translator is organized around
iostream interfaces and implementations are mostly shared, the binary
format is parsed much slower than it can be.
The commit starts an effort of splitting implementations to
make SPIR-V hot path faster and easier to read.
In addition to that from both implementations the Decoder class
is removed - it doesn't bring any important functionality which
couldn't be solved by few lines of explicit simple code.
  • Loading branch information
bwlodarcz authored Mar 11, 2024
1 parent 63fd351 commit d54f77c
Showing 1 changed file with 233 additions and 28 deletions.
261 changes: 233 additions & 28 deletions lib/SPIRV/libSPIRV/SPIRVModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,8 @@ class SPIRVModuleImpl : public SPIRVModule {
SPIRVAliasInstMDMap AliasInstMDMap;

void layoutEntry(SPIRVEntry *Entry);
std::istream &parseSPT(std::istream &I);
std::istream &parseSPIRV(std::istream &I);
};

SPIRVModuleImpl::~SPIRVModuleImpl() {
Expand Down Expand Up @@ -2123,70 +2125,273 @@ void SPIRVModuleImpl::addUnknownStructField(SPIRVTypeStruct *Struct, unsigned I,
UnknownStructFieldMap[Struct].push_back(std::make_pair(I, ID));
}

std::istream &operator>>(std::istream &I, SPIRVModule &M) {
SPIRVDecoder Decoder(I, M);
SPIRVModuleImpl &MI = *static_cast<SPIRVModuleImpl *>(&M);
// Disable automatic capability filling.
namespace {
SPIRVEntry *parseAndCreateSPIRVEntry(SPIRVWord &WordCount, Op &OpCode,
SPIRVEntry *Scope, SPIRVModuleImpl &M,
std::istream &IS) {
if (WordCount == 0 || OpCode == OpNop) {
return nullptr;
}
SPIRVEntry *Entry = SPIRVEntry::create(OpCode);
assert(Entry);
Entry->setModule(&M);
if (Scope && !isModuleScopeAllowedOpCode(OpCode)) {
Entry->setScope(Scope);
}
Entry->setWordCount(WordCount);
if (OpCode != OpLine)
Entry->setLine(M.getCurrentLine());
if (!Entry->isExtInst(SPIRVEIS_NonSemantic_Shader_DebugInfo_100,
SPIRVDebug::DebugLine) &&
!Entry->isExtInst(SPIRVEIS_NonSemantic_Shader_DebugInfo_200,
SPIRVDebug::DebugLine)) {
Entry->setDebugLine(M.getCurrentDebugLine());
}
IS >> *Entry;
if (Entry->isEndOfBlock() || OpCode == OpNoLine) {
M.setCurrentLine(nullptr);
}
if (Entry->isEndOfBlock() ||
Entry->isExtInst(SPIRVEIS_NonSemantic_Shader_DebugInfo_100,
SPIRVDebug::DebugNoLine) ||
Entry->isExtInst(SPIRVEIS_NonSemantic_Shader_DebugInfo_200,
SPIRVDebug::DebugNoLine)) {
M.setCurrentDebugLine(nullptr);
}

if (OpExtension == OpCode) {
auto *OpExt = static_cast<SPIRVExtension *>(Entry);
ExtensionID ExtID = {};
bool ExtIsKnown = SPIRVMap<ExtensionID, std::string>::rfind(
OpExt->getExtensionName(), &ExtID);
if (!M.getErrorLog().checkError(
ExtIsKnown, SPIRVEC_InvalidModule,
"input SPIR-V module uses unknown extension '" +
OpExt->getExtensionName() + "'")) {
M.setInvalid();
}

if (!M.getErrorLog().checkError(
M.isAllowedToUseExtension(ExtID), SPIRVEC_InvalidModule,
"input SPIR-V module uses extension '" + OpExt->getExtensionName() +
"' which were disabled by --spirv-ext option")) {
M.setInvalid();
}
}

if (!M.getErrorLog().checkError(Entry->isImplemented(),
SPIRVEC_UnimplementedOpCode,
std::to_string(Entry->getOpCode()))) {
M.setInvalid();
}

assert(!IS.bad() && !IS.fail() && "SPIRV stream fails");
return Entry;
}
} // namespace

std::istream &SPIRVModuleImpl::parseSPT(std::istream &I) {
SPIRVModuleImpl &MI = *this;
MI.setAutoAddCapability(false);
MI.setAutoAddExtensions(false);
auto ReadSPIRVWord = [](std::istream &I) {
uint32_t W;
I >> skipcomment >> W;
SPIRVDBG(spvdbgs() << "Read word: W = " << W << " V = 0\n");
return W;
};
SPIRVErrorLog ErrorLog = MI.getErrorLog();
SPIRVWord Magic = ReadSPIRVWord(I);

if (!ErrorLog.checkError(!I.eof(), SPIRVEC_InvalidModule,
"input file is empty") ||
!ErrorLog.checkError(!I.fail(), SPIRVEC_InvalidModule,
"header parsing error")) {
MI.setInvalid();
return I;
}

SPIRVWord Magic;
Decoder >> Magic;
if (!M.getErrorLog().checkError(Magic == MagicNumber, SPIRVEC_InvalidModule,
"invalid magic number")) {
M.setInvalid();
if (!ErrorLog.checkError(Magic == MagicNumber, SPIRVEC_InvalidModule,
"invalid magic number")) {
MI.setInvalid();
return I;
}

MI.SPIRVVersion = ReadSPIRVWord(I);
if (!ErrorLog.checkError(!I.fail(), SPIRVEC_InvalidModule,
"header parsing error")) {
MI.setInvalid();
return I;
}

Decoder >> MI.SPIRVVersion;
bool SPIRVVersionIsKnown = isSPIRVVersionKnown(MI.SPIRVVersion);
if (!M.getErrorLog().checkError(
if (!ErrorLog.checkError(
SPIRVVersionIsKnown, SPIRVEC_InvalidModule,
"unsupported SPIR-V version number '" + to_string(MI.SPIRVVersion) +
"'. Range of supported/known SPIR-V "
"versions is " +
to_string(VersionNumber::MinimumVersion) + " - " +
to_string(VersionNumber::MaximumVersion))) {
M.setInvalid();
MI.setInvalid();
return I;
}

bool SPIRVVersionIsAllowed = M.isAllowedToUseVersion(MI.SPIRVVersion);
if (!M.getErrorLog().checkError(
bool SPIRVVersionIsAllowed = MI.isAllowedToUseVersion(MI.SPIRVVersion);
if (!ErrorLog.checkError(
SPIRVVersionIsAllowed, SPIRVEC_InvalidModule,
"incorrect SPIR-V version number " + to_string(MI.SPIRVVersion) +
" - it conflicts with maximum allowed version which is set to " +
to_string(M.getMaximumAllowedSPIRVVersion()))) {
M.setInvalid();
to_string(MI.getMaximumAllowedSPIRVVersion()))) {
MI.setInvalid();
return I;
}

SPIRVWord Generator = ReadSPIRVWord(I);
if (!ErrorLog.checkError(!I.fail(), SPIRVEC_InvalidModule,
"header parsing error")) {
MI.setInvalid();
return I;
}

SPIRVWord Generator = 0;
Decoder >> Generator;
MI.GeneratorId = Generator >> 16;
MI.GeneratorVer = Generator & 0xFFFF;

// Bound for Id
Decoder >> MI.NextId;
MI.NextId = ReadSPIRVWord(I);
if (!ErrorLog.checkError(!I.fail(), SPIRVEC_InvalidModule,
"header parsing error")) {
MI.setInvalid();
return I;
}

Decoder >> MI.InstSchema;
if (!M.getErrorLog().checkError(MI.InstSchema == SPIRVISCH_Default,
SPIRVEC_InvalidModule,
"unsupported instruction schema")) {
M.setInvalid();
MI.InstSchema = static_cast<SPIRVInstructionSchemaKind>(ReadSPIRVWord(I));
if (!ErrorLog.checkError(MI.InstSchema == SPIRVISCH_Default,
SPIRVEC_InvalidModule,
"unsupported instruction schema")) {
MI.setInvalid();
return I;
}

while (Decoder.getWordCountAndOpCode() && M.isModuleValid()) {
SPIRVEntry *Entry = Decoder.getEntry();
if (Entry != nullptr)
M.add(Entry);
SPIRVWord WordCount = 0;
Op OpCode = OpNop;
SPIRVEntry *Scope = nullptr;
while (true) {
WordCount = ReadSPIRVWord(I);
if (I.fail()) {
SPIRVDBG(spvdbgs() << "getWordCountAndOpCode FAIL 0 0\n");
break;
}
std::string RawOp;
I >> RawOp;
OpCode = getNameMap(OpCode).rmap(RawOp);
SPIRVDBG(spvdbgs() << "Read word: W = " << RawOp << " V = " << OpCode
<< '\n');
if (I.fail()) {
SPIRVDBG(spvdbgs() << "getWordCountAndOpCode FAIL 0 0\n");
break;
}
SPIRVDBG(spvdbgs() << "getWordCountAndOpCode " << WordCount << " "
<< OpCodeNameMap::map(OpCode) << '\n');
if (!MI.isModuleValid()) {
break;
}

SPIRVEntry *Entry =
parseAndCreateSPIRVEntry(WordCount, OpCode, Scope, MI, I);
if (Entry != nullptr) {
MI.add(Entry);
}
if (I.eof()) {
SPIRVDBG(spvdbgs() << "getWordCountAndOpCode EOF 0 0\n");
break;
}
}

MI.resolveUnknownStructFields();
return I;
}

std::istream &SPIRVModuleImpl::parseSPIRV(std::istream &I) {
SPIRVModuleImpl &MI = *this;
MI.setAutoAddCapability(false);
MI.setAutoAddExtensions(false);

SPIRVWord Header[5] = {0};
I.read(reinterpret_cast<char *>(&Header), sizeof(Header));

SPIRVErrorLog ErrorLog = MI.getErrorLog();
if (!ErrorLog.checkError(!I.eof(), SPIRVEC_InvalidModule,
"input file is empty") ||
!ErrorLog.checkError(!I.fail(), SPIRVEC_InvalidModule,
"header parsing error") ||
!ErrorLog.checkError(Header[0] == MagicNumber, SPIRVEC_InvalidModule,
"invalid magic number") ||
!ErrorLog.checkError(
isSPIRVVersionKnown(Header[1]), SPIRVEC_InvalidModule,
"unsupported SPIR-V version number '" + to_string(Header[1]) +
"'. Range of supported/known SPIR-V "
"versions is " +
to_string(VersionNumber::MinimumVersion) + " - " +
to_string(VersionNumber::MaximumVersion)) ||
!ErrorLog.checkError(
MI.isAllowedToUseVersion(Header[1]), SPIRVEC_InvalidModule,
"incorrect SPIR-V version number " + to_string(Header[1]) +
" - it conflicts with maximum allowed version which is set to " +
to_string(MI.getMaximumAllowedSPIRVVersion())) ||
!ErrorLog.checkError(Header[4] == SPIRVISCH_Default,
SPIRVEC_InvalidModule,
"unsupported instruction schema")) {
MI.setInvalid();
return I;
}

MI.SPIRVVersion = Header[1];
MI.GeneratorId = Header[2] >> 16;
MI.GeneratorVer = Header[2] & 0xFFFF;
MI.NextId = Header[3];
MI.InstSchema = static_cast<SPIRVInstructionSchemaKind>(Header[4]);

SPIRVEntry *Scope = nullptr;
while (true) {
SPIRVWord WordCountAndOpCode = 0;
I.read(reinterpret_cast<char *>(&WordCountAndOpCode), sizeof(SPIRVWord));
SPIRVDBG(spvdbgs() << "Read word: W = " << WordCountAndOpCode
<< " V = 0\n");
SPIRVWord WordCount = WordCountAndOpCode >> 16;
Op OpCode = static_cast<Op>(WordCountAndOpCode & 0xFFFF);
if (I.fail()) {
SPIRVDBG(spvdbgs() << "getWordCountAndOpCode FAIL 0 0\n");
break;
}
SPIRVDBG(spvdbgs() << "getWordCountAndOpCode " << WordCount << " "
<< OpCodeNameMap::map(OpCode) << '\n');
if (!MI.isModuleValid()) {
break;
}
SPIRVEntry *Entry =
parseAndCreateSPIRVEntry(WordCount, OpCode, Scope, MI, I);
if (Entry != nullptr) {
MI.add(Entry);
}
if (I.eof()) {
SPIRVDBG(spvdbgs() << "getWordCountAndOpCode EOF 0 0\n");
break;
}
}
MI.resolveUnknownStructFields();
return I;
}

std::istream &operator>>(std::istream &I, SPIRVModule &M) {
SPIRVModuleImpl &MI = *static_cast<SPIRVModuleImpl *>(&M);
#ifdef _SPIRV_SUPPORT_TEXT_FMT
if (SPIRVUseTextFormat) {
return MI.parseSPT(I);
}
#endif
return MI.parseSPIRV(I);
}

SPIRVModule *SPIRVModule::createSPIRVModule() { return new SPIRVModuleImpl(); }

SPIRVModule *SPIRVModule::createSPIRVModule(const SPIRV::TranslatorOpts &Opts) {
Expand Down

0 comments on commit d54f77c

Please sign in to comment.