From: John Kessenich Date: Wed, 20 May 2015 16:04:17 +0000 (+0000) Subject: SPV compression: Final check-in enabling this on MSVC 2012. All compression submissi... X-Git-Tag: upstream/0.1~540 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=01685c3ff85f66effb7ebb72c5fa1a453d69a154;p=platform%2Fupstream%2Fglslang.git SPV compression: Final check-in enabling this on MSVC 2012. All compression submissions from Steve (spvremapper@lunarg.com). git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@31236 e7fa87d3-cd2b-0410-9028-fcbf551c1848 --- diff --git a/README-spirv-remap.txt b/README-spirv-remap.txt index abd924e..691c312 100644 --- a/README-spirv-remap.txt +++ b/README-spirv-remap.txt @@ -48,11 +48,11 @@ for Linux. Command line arguments can be provided in any order. Perform ID remapping on all shaders in "*.spv", writing new files with the same basenames to /tmp/out_dir. - spirv-remap --map all --input *.spv --output /tmp/out_dir + spirv-remap -v --map all --input *.spv --output /tmp/out_dir 2. Perform all possible size reductions - spirv-remap-linux-64 --do-everything --input *.spv --output /tmp/out_dir + spirv-remap-linux-64 -v --do-everything --input *.spv --output /tmp/out_dir Note that --do-everything is a synonym for: diff --git a/SPIRV/SPVRemapper.cpp b/SPIRV/SPVRemapper.cpp index d390c77..e6f13ae 100644 --- a/SPIRV/SPVRemapper.cpp +++ b/SPIRV/SPVRemapper.cpp @@ -58,7 +58,7 @@ namespace spv { } // hash opcode, with special handling for OpExtInst - std::uint32_t spirvbin_t::asOpCodeHash(int word) + std::uint32_t spirvbin_t::asOpCodeHash(unsigned word) { const spv::Op opCode = asOpCode(word); @@ -196,8 +196,8 @@ namespace spv { } } - const auto inst_fn_nop = [](spv::Op, int) { return false; }; - const auto op_fn_nop = [](spv::Id&) { }; + const auto inst_fn_nop = [](spv::Op, unsigned) { return false; }; + const auto op_fn_nop = [](spv::Id&) { }; // g++ doesn't like these defined in the class proper in an anonymous namespace. // Dunno why. Also MSVC doesn't like the constexpr keyword. Also dunno why. @@ -242,7 +242,7 @@ namespace spv { // Parse a literal string from the SPIR binary and return it as an std::string // Due to C++11 RValue references, this doesn't copy the result string. - std::string spirvbin_t::literalString(int word) const + std::string spirvbin_t::literalString(unsigned word) const { std::string literal; @@ -304,7 +304,7 @@ namespace spv { // build local Id and name maps process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { // remember opcodes we want to strip later if (isStripOp(opCode)) stripInst(start); @@ -335,7 +335,7 @@ namespace spv { // build local Id and name maps process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { // remember opcodes we want to strip later if ((options & Options::STRIP) && isStripOp(opCode)) stripInst(start); @@ -358,7 +358,7 @@ namespace spv { assert(fnRes != spv::NoResult); if (fnStart == 0) error("function end without function start"); - fnPos[fnRes] = {fnStart, start + asWordCount(start)}; + fnPos[fnRes] = range_t(fnStart, start + asWordCount(start)); fnStart = 0; } else if (isConstOp(opCode)) { assert(asId(start + 2) != spv::NoResult); @@ -397,7 +397,7 @@ namespace spv { } - int spirvbin_t::processInstruction(int word, instfn_t instFn, idfn_t idFn) + int spirvbin_t::processInstruction(unsigned word, instfn_t instFn, idfn_t idFn) { const auto instructionStart = word; const unsigned wordCount = asWordCount(instructionStart); @@ -501,19 +501,19 @@ namespace spv { } // Make a pass over all the instructions and process them given appropriate functions - spirvbin_t& spirvbin_t::process(instfn_t instFn, idfn_t idFn, int begin, int end) + spirvbin_t& spirvbin_t::process(instfn_t instFn, idfn_t idFn, unsigned begin, unsigned end) { // For efficiency, reserve name map space. It can grow if needed. nameMap.reserve(32); // If begin or end == 0, use defaults - begin = (begin == 0 ? header_size : begin); - end = (end == 0 ? int(spv.size()) : end); + begin = (begin == 0 ? header_size : begin); + end = (end == 0 ? unsigned(spv.size()) : end); // basic parsing and InstructionDesc table borrowed from SpvDisassemble.cpp... - int nextInst = int(spv.size()); + unsigned nextInst = unsigned(spv.size()); - for (int word = begin; word < end; word = nextInst) + for (unsigned word = begin; word < end; word = nextInst) nextInst = processInstruction(word, instFn, idFn); return *this; @@ -544,13 +544,13 @@ namespace spv { // Initial approach: go through some high priority opcodes first and assign them // hash values. - spv::Id fnId = spv::NoResult; - std::vector instPos; - instPos.reserve(int(spv.size()) / 16); // initial estimate; can grow if needed. + spv::Id fnId = spv::NoResult; + std::vector instPos; + instPos.reserve(unsigned(spv.size()) / 16); // initial estimate; can grow if needed. // Build local table of instruction start positions process( - [&](spv::Op, int start) { instPos.push_back(start); return true; }, + [&](spv::Op, unsigned start) { instPos.push_back(start); return true; }, op_fn_nop); // Window size for context-sensitive canonicalization values @@ -558,11 +558,11 @@ namespace spv { // We essentially performa a little convolution around each instruction, // to capture the flavor of nearby code, to hopefully match to similar // code in other modules. - static const int windowSize = 2; + static const unsigned windowSize = 2; - for (int entry = 0; entry < int(instPos.size()); ++entry) { - const int start = instPos[entry]; - const spv::Op opCode = asOpCode(start); + for (unsigned entry = 0; entry < unsigned(instPos.size()); ++entry) { + const unsigned start = instPos[entry]; + const spv::Op opCode = asOpCode(start); if (opCode == spv::OpFunction) fnId = asId(start + 2); @@ -571,20 +571,18 @@ namespace spv { fnId = spv::NoResult; if (fnId != spv::NoResult) { // if inside a function - const int word = start + (spv::InstructionDesc[opCode].hasType() ? 2 : 1); - const int result = spv::InstructionDesc[opCode].hasResult() ? word : -1; + if (spv::InstructionDesc[opCode].hasResult()) { + const unsigned word = start + (spv::InstructionDesc[opCode].hasType() ? 2 : 1); + const spv::Id resId = asId(word); + std::uint32_t hashval = fnId * 17; // small prime - if (result > 0) { - const spv::Id resId = asId(result); - std::uint32_t hashval = fnId * 17; // small prime - - for (int i = entry-1; i >= entry-windowSize; --i) { + for (unsigned i = entry-1; i >= entry-windowSize; --i) { if (asOpCode(instPos[i]) == spv::OpFunction) break; hashval = hashval * 30103 + asOpCodeHash(instPos[i]); // 30103 = semiarbitrary prime } - for (int i = entry; i <= entry + windowSize; ++i) { + for (unsigned i = entry; i <= entry + windowSize; ++i) { if (asOpCode(instPos[i]) == spv::OpFunctionEnd) break; hashval = hashval * 30103 + asOpCodeHash(instPos[i]); // 30103 = semiarbitrary prime @@ -602,7 +600,7 @@ namespace spv { fnId = spv::NoResult; process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { switch (opCode) { case spv::OpFunction: // Reset counters at each function @@ -645,7 +643,7 @@ namespace spv { } return false; - }, + }, [&](spv::Id& id) { if (thisOpCode != spv::OpNop) { @@ -655,63 +653,9 @@ namespace spv { if (isOldIdUnmapped(id)) localId(id, nextUnusedId(hashval % softTypeIdLimit + firstMappedID)); } - }); + }); } -#ifdef NOTDEF - // remove bodies of uncalled functions - void spirvbin_t::offsetIds() - { - // Count of how many functions each ID appears within - std::unordered_map idFnCount; - std::unordered_map idDefinedLoc; - idset_t idsUsed; // IDs used in a given function - - int instCount = 0; - - // create a count of how many functions each ID is used within - process( - [&](spv::OpCode opCode, int start) { - ++instCount; - - switch (opCode) { - case spv::OpFunction: - for (const auto id : idsUsed) - ++idFnCount[id]; - idsUsed.clear(); - break; - - default: - { - const int word = start + (spv::InstructionDesc[opCode].hasType() ? 2 : 1); - const int result = spv::InstructionDesc[opCode].hasResult() ? word : -1; - - if (result > 0) - idDefinedLoc[asId(result)] = instCount; - } - break; - } - - return false; - }, - - [&](spv::Id& id) { idsUsed.insert(id); }); - - // For each ID defined in exactly one function, replace uses by - // negative offset to definitions in instructions. - - static const int relOffsetLimit = 64; - - instCount = 0; - process([&](spv::OpCode, int) { ++instCount; return false; }, - [&](spv::Id& id) { - if (idFnCount[id] == 1 && (instCount - idDefinedLoc[id]) < relOffsetLimit) - id = idDefinedLoc[id] - instCount; - }); - } -#endif - - // EXPERIMENTAL: forward IO and uniform load/stores into operands // This produces invalid Schema-0 SPIRV void spirvbin_t::forwardLoadStores() @@ -721,7 +665,7 @@ namespace spv { // EXPERIMENTAL: Forward input and access chain loads into consumptions process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { // Add inputs and uniforms to the map if (((opCode == spv::OpVariable && asWordCount(start) == 4) || (opCode == spv::OpVariableArray)) && (spv[start+3] == spv::StorageClassUniform || @@ -738,7 +682,7 @@ namespace spv { } return false; - }, + }, [&](spv::Id& id) { if (idMap.find(id) != idMap.end()) id = idMap[id]; } ); @@ -748,7 +692,7 @@ namespace spv { idMap.clear(); process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { // Add inputs and uniforms to the map if (((opCode == spv::OpVariable && asWordCount(start) == 4) || (opCode == spv::OpVariableArray)) && (spv[start+3] == spv::StorageClassOutput)) @@ -760,7 +704,7 @@ namespace spv { } return false; - }, + }, op_fn_nop); process( @@ -781,7 +725,7 @@ namespace spv { // Find all the function local pointers stored at most once, and not via access chains process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { const int wordCount = asWordCount(start); // Add local variables to the map @@ -826,34 +770,33 @@ namespace spv { } return true; - }, + }, op_fn_nop); process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { if (opCode == spv::OpLoad && fnLocalVars.count(asId(start+3)) > 0) idMap[asId(start+2)] = idMap[asId(start+3)]; return false; - }, + }, op_fn_nop); // Remove the load/store/variables for the ones we've discovered process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { if ((opCode == spv::OpLoad && fnLocalVars.count(asId(start+3)) > 0) || (opCode == spv::OpStore && fnLocalVars.count(asId(start+1)) > 0) || (opCode == spv::OpVariable && fnLocalVars.count(asId(start+2)) > 0)) { - stripInst(start); - return true; + stripInst(start); + return true; } return false; - }, + }, [&](spv::Id& id) { if (idMap.find(id) != idMap.end()) id = idMap[id]; } ); - strip(); // strip out data we decided to eliminate buildLocalMaps(); // rebuild ID mapping data } @@ -884,7 +827,7 @@ namespace spv { // decrease counts of called functions process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { if (opCode == spv::Op::OpFunctionCall) { const auto call_it = fnCalls.find(asId(start + 3)); if (call_it != fnCalls.end()) { @@ -894,7 +837,7 @@ namespace spv { } return true; - }, + }, op_fn_nop, fn->second.first, fn->second.second); @@ -914,7 +857,7 @@ namespace spv { // Count function variable use process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { if (opCode == spv::OpVariable) { ++varUseCount[asId(start+2)]; return true; } return false; }, @@ -924,7 +867,7 @@ namespace spv { // Remove single-use function variables + associated decorations and names process( - [&](spv::Op opCode, int start) { + [&](spv::Op opCode, unsigned start) { if ((opCode == spv::OpVariable && varUseCount[asId(start+2)] == 1) || (opCode == spv::OpDecorate && varUseCount[asId(start+1)] == 1) || (opCode == spv::OpName && varUseCount[asId(start+1)] == 1)) { @@ -1041,7 +984,7 @@ namespace spv { #endif // NOTDEF // Return start position in SPV of given type. error if not found. - int spirvbin_t::typePos(spv::Id id) const + unsigned spirvbin_t::typePos(spv::Id id) const { const auto tid_it = typeConstPosR.find(id); if (tid_it == typeConstPosR.end()) @@ -1052,7 +995,7 @@ namespace spv { // Hash types to canonical values. This can return ID collisions (it's a bit // inevitable): it's up to the caller to handle that gracefully. - std::uint32_t spirvbin_t::hashType(int typeStart) const + std::uint32_t spirvbin_t::hashType(unsigned typeStart) const { const unsigned wordCount = asWordCount(typeStart); const spv::Op opCode = asOpCode(typeStart); @@ -1160,7 +1103,7 @@ namespace spv { // Allocate a new binary big enough to hold old binary // We'll step this iterator through the strip ranges as we go through the binary - decltype(stripRange)::const_iterator strip_it = stripRange.begin(); + auto strip_it = stripRange.begin(); int strippedPos = 0; for (unsigned word = 0; word < unsigned(spv.size()); ++word) { @@ -1201,12 +1144,6 @@ namespace spv { mapRemainder(); // map any unmapped IDs applyMap(); // Now remap each shader to the new IDs we've come up with strip(); // strip out data we decided to eliminate - -#define EXPERIMENT3 0 -#if (EXPERIMENT3) - // TODO: ... shortcuts for simple single-const access chains and constants, - // folded into high half of the ID space. -#endif } // remap from a memory image diff --git a/SPIRV/SPVRemapper.h b/SPIRV/SPVRemapper.h index 5af95f9..dc41264 100644 --- a/SPIRV/SPVRemapper.h +++ b/SPIRV/SPVRemapper.h @@ -43,7 +43,7 @@ namespace spv { // MSVC defines __cplusplus as an older value, even when it supports almost all of 11. // We handle that here by making our own symbol. -#if __cplusplus >= 201103L || _MSC_VER >= 1800 +#if __cplusplus >= 201103L || _MSC_VER >= 1700 # define use_cpp11 1 #endif @@ -84,6 +84,7 @@ public: void remap(std::vector& /*spv*/, unsigned int /*opts = 0*/) { printf("Tool not compiled for C++11, which is required for SPIR-V remapping.\n"); + exit(5); } }; @@ -137,9 +138,9 @@ private: typedef std::uint32_t spirword_t; - typedef std::pair range_t; - typedef std::function idfn_t; - typedef std::function instfn_t; + typedef std::pair range_t; + typedef std::function idfn_t; + typedef std::function instfn_t; // Special Values for ID map: static const spv::Id unmapped; // unchanged from default value @@ -168,14 +169,14 @@ private: range_t typeRange(spv::Op opCode) const; range_t constRange(spv::Op opCode) const; - spv::Id& asId(int word) { return spv[word]; } - const spv::Id& asId(int word) const { return spv[word]; } - spv::Op asOpCode(int word) const { return opOpCode(spv[word]); } - std::uint32_t asOpCodeHash(int word); - spv::Decoration asDecoration(int word) const { return spv::Decoration(spv[word]); } - unsigned asWordCount(int word) const { return opWordCount(spv[word]); } - spv::Id asTypeConstId(int word) const { return asId(word + (isTypeOp(asOpCode(word)) ? 1 : 2)); } - int typePos(spv::Id id) const; + spv::Id& asId(unsigned word) { return spv[word]; } + const spv::Id& asId(unsigned word) const { return spv[word]; } + spv::Op asOpCode(unsigned word) const { return opOpCode(spv[word]); } + std::uint32_t asOpCodeHash(unsigned word); + spv::Decoration asDecoration(unsigned word) const { return spv::Decoration(spv[word]); } + unsigned asWordCount(unsigned word) const { return opWordCount(spv[word]); } + spv::Id asTypeConstId(unsigned word) const { return asId(word + (isTypeOp(asOpCode(word)) ? 1 : 2)); } + unsigned typePos(spv::Id id) const; static unsigned opWordCount(spirword_t data) { return data >> spv::WordCountShift; } static spv::Op opOpCode(spirword_t data) { return spv::Op(data & spv::OpCodeMask); } @@ -201,7 +202,7 @@ private: inline spv::Id nextUnusedId(spv::Id id); void buildLocalMaps(); - std::string literalString(int word) const; // Return literal as a std::string + std::string literalString(unsigned word) const; // Return literal as a std::string int literalStringWords(const std::string& str) const { return (int(str.size())+4)/4; } bool isNewIdMapped(spv::Id newId) const { return isMapped(newId); } @@ -212,10 +213,10 @@ private: // bool matchType(const globaltypes_t& globalTypes, spv::Id lt, spv::Id gt) const; // spv::Id findType(const globaltypes_t& globalTypes, spv::Id lt) const; - std::uint32_t hashType(int typeStart) const; + std::uint32_t hashType(unsigned typeStart) const; - spirvbin_t& process(instfn_t, idfn_t, int begin = 0, int end = 0); - int processInstruction(int word, instfn_t, idfn_t); + spirvbin_t& process(instfn_t, idfn_t, unsigned begin = 0, unsigned end = 0); + int processInstruction(unsigned word, instfn_t, idfn_t); void validate() const; void mapTypeConst(); @@ -251,12 +252,12 @@ private: // Add a strip range for a given instruction starting at 'start' // Note: avoiding brace initializers to please older versions os MSVC. - void stripInst(int start) { stripRange.push_back(std::pair(start, start + asWordCount(start))); } + void stripInst(unsigned start) { stripRange.push_back(range_t(start, start + asWordCount(start))); } // Function start and end. use unordered_map because we'll have // many fewer functions than IDs. - std::unordered_map> fnPos; - std::unordered_map> fnPosDCE; // deleted functions + std::unordered_map fnPos; + std::unordered_map fnPosDCE; // deleted functions // Which functions are called, anywhere in the module, with a call count std::unordered_map fnCalls; @@ -270,7 +271,7 @@ private: spv::Id largestNewId; // biggest new ID we have mapped anything to // Sections of the binary to strip, given as [begin,end) - std::vector> stripRange; + std::vector stripRange; // processing options: std::uint32_t options; diff --git a/StandAlone/spirv-remap.cpp b/StandAlone/spirv-remap.cpp index e14c8b5..444403b 100644 --- a/StandAlone/spirv-remap.cpp +++ b/StandAlone/spirv-remap.cpp @@ -33,304 +33,305 @@ //POSSIBILITY OF SUCH DAMAGE. // -#include -#include -#include -#include - -#include "../SPIRV/SPVRemapper.h" - -namespace { - - typedef unsigned int SpvWord; - - // Poor man's basename: given a complete path, return file portion. - // E.g: - // Linux: /foo/bar/test -> test - // Win: c:\foo\bar\test -> test - // It's not very efficient, but that doesn't matter for our minimal-duty use. - // Using boost::filesystem would be better in many ways, but want to avoid that dependency. - - // OS dependent path separator (avoiding boost::filesystem dependency) -#if defined(_WIN32) - char path_sep_char() { return '\\'; } -#else - char path_sep_char() { return '/'; } -#endif - - std::string basename(const std::string filename) - { - const size_t sepLoc = filename.find_last_of(path_sep_char()); - - return (sepLoc == filename.npos) ? filename : filename.substr(sepLoc+1); - } - - void errHandler(const std::string& str) { - std::cout << str << std::endl; - exit(5); - } - - void logHandler(const std::string& str) { - std::cout << str << std::endl; - } - - // Read word stream from disk - void read(std::vector& spv, const std::string& inFilename) - { - std::ifstream fp; - - std::cout << " reading: " << inFilename << std::endl; - - spv.clear(); - fp.open(inFilename, std::fstream::in | std::fstream::binary); - - if (fp.fail()) - errHandler("error opening file for read: "); - - // Reserve space (for efficiency, not for correctness) - fp.seekg(0, fp.end); - spv.reserve(size_t(fp.tellg()) / sizeof(SpvWord)); - fp.seekg(0, fp.beg); - - while (!fp.eof()) { - SpvWord inWord; - fp.read((char *)&inWord, sizeof(inWord)); - - if (!fp.eof()) { - spv.push_back(inWord); - if (fp.fail()) - errHandler(std::string("error reading file: ") + inFilename); - } - } - } - - void write(std::vector& spv, const std::string& outFile) - { - if (outFile.empty()) - errHandler("missing output filename."); - - std::ofstream fp; - - std::cout << " writing: " << outFile << std::endl; - - fp.open(outFile, std::fstream::out | std::fstream::binary); - - if (fp.fail()) - errHandler(std::string("error opening file for write: ") + outFile); - - for (auto word : spv) { - fp.write((char *)&word, sizeof(word)); - if (fp.fail()) - errHandler(std::string("error writing file: ") + outFile); - } - - // file is closed by destructor - } - - // Print helpful usage message to stdout, and exit - void usage(const char* const name, const char* const msg = 0) - { - if (msg) - std::cout << msg << std::endl << std::endl; - - std::cout << "Usage: " << std::endl; - - std::cout << " " << basename(name) - << " [-v[v[...]] | --verbose [int]]" - << " [--map (all|types|names|funcs)]" - << " [--dce (all|types|funcs)]" - << " [--opt (all|loadstore)]" - << " [--strip-all | --strip all | -s]" - << " [--do-everything]" - << " --input | -i file1 [file2...] --output|-o DESTDIR" - << std::endl; - - std::cout << " " << basename(name) << " [--version | -V]" << std::endl; - std::cout << " " << basename(name) << " [--help | -?]" << std::endl; - - exit(5); - } - - // grind through each SPIR in turn - void execute(const std::vector& inputFile, const std::string& outputDir, - int opts, int verbosity) - { - for (const auto& filename : inputFile) { - std::vector spv; - read(spv, filename); - spv::spirvbin_t(verbosity).remap(spv, opts); - - const std::string outfile = outputDir + path_sep_char() + basename(filename); - - write(spv, outfile); - } - - if (verbosity > 0) - std::cout << "Done: " << inputFile.size() << " file(s) processed" << std::endl; - } - - // Parse command line options - void parseCmdLine(int argc, char** argv, std::vector& inputFile, - std::string& outputDir, - int& options, - int& verbosity) - { - if (argc < 2) - usage(argv[0]); - - verbosity = 0; - options = spv::spirvbin_t::Options::NONE; - - // Parse command line. - // boost::program_options would be quite a bit nicer, but we don't want to - // introduce a dependency on boost. - for (int a=1; a= argc) - usage(argv[0], "--output requires an argument"); - if (!outputDir.empty()) - usage(argv[0], "--output can be provided only once"); - - outputDir = argv[a++]; - - // Remove trailing directory separator characters - while (!outputDir.empty() && outputDir.back() == path_sep_char()) - outputDir.pop_back(); - - } - else if (arg == "-vv") { verbosity = 2; ++a; } // verbosity shortcuts - else if (arg == "-vvv") { verbosity = 3; ++a; } // ... - else if (arg == "-vvvv") { verbosity = 4; ++a; } // ... - else if (arg == "-vvvvv") { verbosity = 5; ++a; } // ... - - else if (arg == "--verbose" || arg == "-v") { - ++a; - verbosity = 1; - - if (a < argc) { - try { - verbosity = std::stoi(argv[a]); - ++a; - } catch (const std::invalid_argument&) { } // ok to have no numeric value - } - } - else if (arg == "--version" || arg == "-V") { - std::cout << basename(argv[0]) << " version 0.97 " << __DATE__ << " " << __TIME__ << std::endl; - exit(0); - } else if (arg == "--input" || arg == "-i") { - // Collect input files - for (++a; a < argc && argv[a][0] != '-'; ++a) - inputFile.push_back(argv[a]); - } else if (arg == "--do-everything") { - ++a; - options = options | spv::spirvbin_t::Options::DO_EVERYTHING; - } else if (arg == "--strip-all" || arg == "-s") { - ++a; - options = options | spv::spirvbin_t::Options::STRIP; - } else if (arg == "--strip") { - ++a; - if (strncmp(argv[a], "all", 3) == 0) { - options = options | spv::spirvbin_t::Options::STRIP; - ++a; - } - } else if (arg == "--dce") { - // Parse comma (or colon, etc) separated list of things to dce - ++a; - for (const char* c = argv[a]; *c; ++c) { - if (strncmp(c, "all", 3) == 0) { - options = (options | spv::spirvbin_t::Options::DCE_ALL); - c += 3; - } else if (strncmp(c, "*", 1) == 0) { - options = (options | spv::spirvbin_t::Options::DCE_ALL); - c += 1; - } else if (strncmp(c, "funcs", 5) == 0) { - options = (options | spv::spirvbin_t::Options::DCE_FUNCS); - c += 5; - } else if (strncmp(c, "types", 5) == 0) { - options = (options | spv::spirvbin_t::Options::DCE_TYPES); - c += 5; - } - } - ++a; - } else if (arg == "--map") { - // Parse comma (or colon, etc) separated list of things to map - ++a; - for (const char* c = argv[a]; *c; ++c) { - if (strncmp(c, "all", 3) == 0) { - options = (options | spv::spirvbin_t::Options::MAP_ALL); - c += 3; - } else if (strncmp(c, "*", 1) == 0) { - options = (options | spv::spirvbin_t::Options::MAP_ALL); - c += 1; - } else if (strncmp(c, "types", 5) == 0) { - options = (options | spv::spirvbin_t::Options::MAP_TYPES); - c += 5; - } else if (strncmp(c, "names", 5) == 0) { - options = (options | spv::spirvbin_t::Options::MAP_NAMES); - c += 5; - } else if (strncmp(c, "funcs", 5) == 0) { - options = (options | spv::spirvbin_t::Options::MAP_FUNCS); - c += 5; - } - } - ++a; - } else if (arg == "--opt") { - ++a; - for (const char* c = argv[a]; *c; ++c) { - if (strncmp(c, "all", 3) == 0) { - options = (options | spv::spirvbin_t::Options::OPT_ALL); - c += 3; - } else if (strncmp(c, "*", 1) == 0) { - options = (options | spv::spirvbin_t::Options::OPT_ALL); - c += 1; - } else if (strncmp(c, "loadstore", 9) == 0) { - options = (options | spv::spirvbin_t::Options::OPT_LOADSTORE); - c += 9; - } - } - ++a; - } else if (arg == "--help" || arg == "-?") { - usage(argv[0]); - } else { - usage(argv[0], "Unknown command line option"); - } - } - } - -} // namespace - - -int main(int argc, char** argv) -{ -#ifdef use_cpp11 - std::vector inputFile; - std::string outputDir; - int opts; - int verbosity; - - // handle errors by exiting - spv::spirvbin_t::registerErrorHandler(errHandler); - - // Log messages to std::cout - spv::spirvbin_t::registerLogHandler(logHandler); - - if (argc < 2) - usage(argv[0]); - - parseCmdLine(argc, argv, inputFile, outputDir, opts, verbosity); - - if (outputDir.empty()) - usage(argv[0], "Output directory required"); - - std::string errmsg; - - // Main operations: read, remap, and write. - execute(inputFile, outputDir, opts, verbosity); - -#endif - - // If we get here, everything went OK! Nothing more to be done. -} +#include +#include +#include +#include + +#include "../SPIRV/SPVRemapper.h" + +namespace { + + typedef unsigned int SpvWord; + + // Poor man's basename: given a complete path, return file portion. + // E.g: + // Linux: /foo/bar/test -> test + // Win: c:\foo\bar\test -> test + // It's not very efficient, but that doesn't matter for our minimal-duty use. + // Using boost::filesystem would be better in many ways, but want to avoid that dependency. + + // OS dependent path separator (avoiding boost::filesystem dependency) +#if defined(_WIN32) + char path_sep_char() { return '\\'; } +#else + char path_sep_char() { return '/'; } +#endif + + std::string basename(const std::string filename) + { + const size_t sepLoc = filename.find_last_of(path_sep_char()); + + return (sepLoc == filename.npos) ? filename : filename.substr(sepLoc+1); + } + + void errHandler(const std::string& str) { + std::cout << str << std::endl; + exit(5); + } + + void logHandler(const std::string& str) { + std::cout << str << std::endl; + } + + // Read word stream from disk + void read(std::vector& spv, const std::string& inFilename, int verbosity) + { + std::ifstream fp; + + if (verbosity > 0) + logHandler(std::string(" reading: ") + inFilename); + + spv.clear(); + fp.open(inFilename, std::fstream::in | std::fstream::binary); + + if (fp.fail()) + errHandler("error opening file for read: "); + + // Reserve space (for efficiency, not for correctness) + fp.seekg(0, fp.end); + spv.reserve(size_t(fp.tellg()) / sizeof(SpvWord)); + fp.seekg(0, fp.beg); + + while (!fp.eof()) { + SpvWord inWord; + fp.read((char *)&inWord, sizeof(inWord)); + + if (!fp.eof()) { + spv.push_back(inWord); + if (fp.fail()) + errHandler(std::string("error reading file: ") + inFilename); + } + } + } + + void write(std::vector& spv, const std::string& outFile, int verbosity) + { + if (outFile.empty()) + errHandler("missing output filename."); + + std::ofstream fp; + + if (verbosity > 0) + logHandler(std::string(" writing: ") + outFile); + + fp.open(outFile, std::fstream::out | std::fstream::binary); + + if (fp.fail()) + errHandler(std::string("error opening file for write: ") + outFile); + + for (auto word : spv) { + fp.write((char *)&word, sizeof(word)); + if (fp.fail()) + errHandler(std::string("error writing file: ") + outFile); + } + + // file is closed by destructor + } + + // Print helpful usage message to stdout, and exit + void usage(const char* const name, const char* const msg = 0) + { + if (msg) + std::cout << msg << std::endl << std::endl; + + std::cout << "Usage: " << std::endl; + + std::cout << " " << basename(name) + << " [-v[v[...]] | --verbose [int]]" + << " [--map (all|types|names|funcs)]" + << " [--dce (all|types|funcs)]" + << " [--opt (all|loadstore)]" + << " [--strip-all | --strip all | -s]" + << " [--do-everything]" + << " --input | -i file1 [file2...] --output|-o DESTDIR" + << std::endl; + + std::cout << " " << basename(name) << " [--version | -V]" << std::endl; + std::cout << " " << basename(name) << " [--help | -?]" << std::endl; + + exit(5); + } + + // grind through each SPIR in turn + void execute(const std::vector& inputFile, const std::string& outputDir, + int opts, int verbosity) + { + for (const auto& filename : inputFile) { + std::vector spv; + read(spv, filename, verbosity); + spv::spirvbin_t(verbosity).remap(spv, opts); + + const std::string outfile = outputDir + path_sep_char() + basename(filename); + + write(spv, outfile, verbosity); + } + + if (verbosity > 0) + std::cout << "Done: " << inputFile.size() << " file(s) processed" << std::endl; + } + + // Parse command line options + void parseCmdLine(int argc, char** argv, std::vector& inputFile, + std::string& outputDir, + int& options, + int& verbosity) + { + if (argc < 2) + usage(argv[0]); + + verbosity = 0; + options = spv::spirvbin_t::Options::NONE; + + // Parse command line. + // boost::program_options would be quite a bit nicer, but we don't want to + // introduce a dependency on boost. + for (int a=1; a= argc) + usage(argv[0], "--output requires an argument"); + if (!outputDir.empty()) + usage(argv[0], "--output can be provided only once"); + + outputDir = argv[a++]; + + // Remove trailing directory separator characters + while (!outputDir.empty() && outputDir.back() == path_sep_char()) + outputDir.pop_back(); + + } + else if (arg == "-vv") { verbosity = 2; ++a; } // verbosity shortcuts + else if (arg == "-vvv") { verbosity = 3; ++a; } // ... + else if (arg == "-vvvv") { verbosity = 4; ++a; } // ... + else if (arg == "-vvvvv") { verbosity = 5; ++a; } // ... + + else if (arg == "--verbose" || arg == "-v") { + ++a; + verbosity = 1; + + if (a < argc) { + try { + verbosity = std::stoi(argv[a]); + ++a; + } catch (const std::invalid_argument&) { } // ok to have no numeric value + } + } + else if (arg == "--version" || arg == "-V") { + std::cout << basename(argv[0]) << " version 0.97 " << __DATE__ << " " << __TIME__ << std::endl; + exit(0); + } else if (arg == "--input" || arg == "-i") { + // Collect input files + for (++a; a < argc && argv[a][0] != '-'; ++a) + inputFile.push_back(argv[a]); + } else if (arg == "--do-everything") { + ++a; + options = options | spv::spirvbin_t::Options::DO_EVERYTHING; + } else if (arg == "--strip-all" || arg == "-s") { + ++a; + options = options | spv::spirvbin_t::Options::STRIP; + } else if (arg == "--strip") { + ++a; + if (strncmp(argv[a], "all", 3) == 0) { + options = options | spv::spirvbin_t::Options::STRIP; + ++a; + } + } else if (arg == "--dce") { + // Parse comma (or colon, etc) separated list of things to dce + ++a; + for (const char* c = argv[a]; *c; ++c) { + if (strncmp(c, "all", 3) == 0) { + options = (options | spv::spirvbin_t::Options::DCE_ALL); + c += 3; + } else if (strncmp(c, "*", 1) == 0) { + options = (options | spv::spirvbin_t::Options::DCE_ALL); + c += 1; + } else if (strncmp(c, "funcs", 5) == 0) { + options = (options | spv::spirvbin_t::Options::DCE_FUNCS); + c += 5; + } else if (strncmp(c, "types", 5) == 0) { + options = (options | spv::spirvbin_t::Options::DCE_TYPES); + c += 5; + } + } + ++a; + } else if (arg == "--map") { + // Parse comma (or colon, etc) separated list of things to map + ++a; + for (const char* c = argv[a]; *c; ++c) { + if (strncmp(c, "all", 3) == 0) { + options = (options | spv::spirvbin_t::Options::MAP_ALL); + c += 3; + } else if (strncmp(c, "*", 1) == 0) { + options = (options | spv::spirvbin_t::Options::MAP_ALL); + c += 1; + } else if (strncmp(c, "types", 5) == 0) { + options = (options | spv::spirvbin_t::Options::MAP_TYPES); + c += 5; + } else if (strncmp(c, "names", 5) == 0) { + options = (options | spv::spirvbin_t::Options::MAP_NAMES); + c += 5; + } else if (strncmp(c, "funcs", 5) == 0) { + options = (options | spv::spirvbin_t::Options::MAP_FUNCS); + c += 5; + } + } + ++a; + } else if (arg == "--opt") { + ++a; + for (const char* c = argv[a]; *c; ++c) { + if (strncmp(c, "all", 3) == 0) { + options = (options | spv::spirvbin_t::Options::OPT_ALL); + c += 3; + } else if (strncmp(c, "*", 1) == 0) { + options = (options | spv::spirvbin_t::Options::OPT_ALL); + c += 1; + } else if (strncmp(c, "loadstore", 9) == 0) { + options = (options | spv::spirvbin_t::Options::OPT_LOADSTORE); + c += 9; + } + } + ++a; + } else if (arg == "--help" || arg == "-?") { + usage(argv[0]); + } else { + usage(argv[0], "Unknown command line option"); + } + } + } + +} // namespace + + +int main(int argc, char** argv) +{ + std::vector inputFile; + std::string outputDir; + int opts; + int verbosity; + +#ifdef use_cpp11 + // handle errors by exiting + spv::spirvbin_t::registerErrorHandler(errHandler); + + // Log messages to std::cout + spv::spirvbin_t::registerLogHandler(logHandler); +#endif + + if (argc < 2) + usage(argv[0]); + + parseCmdLine(argc, argv, inputFile, outputDir, opts, verbosity); + + if (outputDir.empty()) + usage(argv[0], "Output directory required"); + + std::string errmsg; + + // Main operations: read, remap, and write. + execute(inputFile, outputDir, opts, verbosity); + + // If we get here, everything went OK! Nothing more to be done. +}