From: John Kessenich Date: Fri, 15 May 2015 16:02:07 +0000 (+0000) Subject: SPV compression: Remove file/path manipulation stuff, setting up for that to be a... X-Git-Tag: upstream/0.1~554 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2aa7f3a671aa20b60daf3b9864ab6115aab454c0;p=platform%2Fupstream%2Fglslang.git SPV compression: Remove file/path manipulation stuff, setting up for that to be a separate tool. Added copyright messages as well. git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@31201 e7fa87d3-cd2b-0410-9028-fcbf551c1848 --- diff --git a/SPIRV/SPVRemapper.cpp b/SPIRV/SPVRemapper.cpp index 8147a28..97eb4a5 100644 --- a/SPIRV/SPVRemapper.cpp +++ b/SPIRV/SPVRemapper.cpp @@ -1,29 +1,50 @@ +// +//Copyright (C) 2015 LunarG, Inc. +// +//All rights reserved. +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions +//are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of 3Dlabs Inc. Ltd. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +//POSSIBILITY OF SUCH DAMAGE. +// + #include "SPVRemapper.h" #include "doc.h" - -/* -*-mode:c++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 3 -*- */ - -// 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. -const std::string spv::spirvbin_base_t::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); -} - -#if !defined (use_cpp11) -// ... not supported before C++11 -#else // defined (use_cpp11) - -#include -#include -#include - + +/* -*-mode:c++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 3 -*- */ + +#if !defined (use_cpp11) +// ... not supported before C++11 +#else // defined (use_cpp11) + +#include +#include + namespace spv { // By default, just abort on error. Can be overridden via RegisterErrorHandler @@ -201,20 +222,20 @@ spv::Id spirvbin_t::localId(spv::Id id, spv::Id newId) if (id >= idMapL.size()) idMapL.resize(id+1, unused); - - if (newId != unmapped && newId != unused) { - if (isOldIdUnused(id)) - ferror(std::string("ID unused in module: ") + std::to_string(id)); - - if (!isOldIdUnmapped(id)) - ferror(std::string("ID already mapped: ") + std::to_string(id) + " -> " - + std::to_string(localId(id))); - - if (isNewIdMapped(newId)) - ferror(std::string("ID already used in module: ") + std::to_string(newId)); - - msg(4, 4, std::string("map: ") + std::to_string(id) + " -> " + std::to_string(newId)); - setMapped(newId); + + if (newId != unmapped && newId != unused) { + if (isOldIdUnused(id)) + error(std::string("ID unused in module: ") + std::to_string(id)); + + if (!isOldIdUnmapped(id)) + error(std::string("ID already mapped: ") + std::to_string(id) + " -> " + + std::to_string(localId(id))); + + if (isNewIdMapped(newId)) + error(std::string("ID already used in module: ") + std::to_string(newId)); + + msg(4, 4, std::string("map: ") + std::to_string(id) + " -> " + std::to_string(newId)); + setMapped(newId); largestNewId = std::max(largestNewId, newId); } @@ -235,45 +256,15 @@ std::string spirvbin_t::literalString(int word) const literal += *bytes++; return literal; -} - - -// Write word stream to disk, in outputDir, with same filename used to read it. -void spirvbin_t::write(const std::string& outputDir) const -{ - if (filename.empty()) - error("missing filename"); - - if (outputDir.empty()) - error("missing output directory"); - - const std::string outfile = outputDir + path_sep_char() + basename(filename); - - std::ofstream fp; - - msg(2, 2, std::string("writing: ") + outfile); - - fp.open(outfile, std::fstream::out | std::fstream::binary); - - if (fp.fail()) - error(std::string("error opening file for write: ") + outfile); - - for (auto word : spv) { - fp.write((char *)&word, sizeof(word)); - if (fp.fail()) - error(std::string("error writing file: ") + outfile); - } - - // file is closed by destructor -} - - -void spirvbin_t::applyMap() -{ - msg(3, 2, std::string("Applying map: ") + basename(filename)); - - // Map local IDs through the ID map - process(inst_fn_nop, // ignore instructions +} + + +void spirvbin_t::applyMap() +{ + msg(3, 2, std::string("Applying map: ")); + + // Map local IDs through the ID map + process(inst_fn_nop, // ignore instructions [this](spv::Id& id) { id = localId(id); assert(id != unused && id != unmapped); @@ -282,13 +273,13 @@ void spirvbin_t::applyMap() } -// Find free IDs for anything we haven't mapped -void spirvbin_t::mapRemainder() -{ - msg(3, 2, std::string("Remapping remainder: ") + basename(filename)); - - spv::Id unusedId = 1; // can't use 0: that's NoResult - spirword_t maxBound = 0; +// Find free IDs for anything we haven't mapped +void spirvbin_t::mapRemainder() +{ + msg(3, 2, std::string("Remapping remainder: ")); + + spv::Id unusedId = 1; // can't use 0: that's NoResult + spirword_t maxBound = 0; for (spv::Id id = 0; id < idMapL.size(); ++id) { if (isOldIdUnused(id)) @@ -296,13 +287,13 @@ void spirvbin_t::mapRemainder() // Find a new mapping for any used but unmapped IDs if (isOldIdUnmapped(id)) - localId(id, unusedId = nextUnusedId(unusedId)); - - if (isOldIdUnmapped(id)) - ferror(std::string("old ID not mapped: ") + std::to_string(id)); - - // Track max bound - maxBound = std::max(maxBound, localId(id) + 1); + localId(id, unusedId = nextUnusedId(unusedId)); + + if (isOldIdUnmapped(id)) + error(std::string("old ID not mapped: ") + std::to_string(id)); + + // Track max bound + maxBound = std::max(maxBound, localId(id) + 1); } bound(maxBound); // reset header ID bound to as big as it now needs to be @@ -323,13 +314,13 @@ void spirvbin_t::stripDebug() }, op_fn_nop); } - -void spirvbin_t::buildLocalMaps() -{ - msg(2, 2, std::string("build local maps: ") + filename); - - mapped.clear(); - idMapL.clear(); + +void spirvbin_t::buildLocalMaps() +{ + msg(2, 2, std::string("build local maps: ")); + + mapped.clear(); + idMapL.clear(); nameMap.clear(); fnPos.clear(); fnPosDCE.clear(); @@ -359,19 +350,19 @@ void spirvbin_t::buildLocalMaps() } else if (opCode == spv::Op::OpFunctionCall) { ++fnCalls[asId(start + 3)]; } else if (opCode == spv::Op::OpEntryPoint) { - entryPoint = asId(start + 2); - } else if (opCode == spv::Op::OpFunction) { - if (fnStart != 0) - ferror("nested function found"); - fnStart = start; - fnRes = asId(start + 2); - } else if (opCode == spv::Op::OpFunctionEnd) { - assert(fnRes != spv::NoResult); - if (fnStart == 0) - ferror("function end without function start"); - fnPos[fnRes] = {fnStart, start + asWordCount(start)}; - fnStart = 0; - } else if (isConstOp(opCode)) { + entryPoint = asId(start + 2); + } else if (opCode == spv::Op::OpFunction) { + if (fnStart != 0) + error("nested function found"); + fnStart = start; + fnRes = asId(start + 2); + } else if (opCode == spv::Op::OpFunctionEnd) { + assert(fnRes != spv::NoResult); + if (fnStart == 0) + error("function end without function start"); + fnPos[fnRes] = {fnStart, start + asWordCount(start)}; + fnStart = 0; + } else if (isConstOp(opCode)) { assert(asId(start + 2) != spv::NoResult); typeConstPos.insert(start); typeConstPosR[asId(start + 2)] = start; @@ -385,79 +376,45 @@ void spirvbin_t::buildLocalMaps() }, [this](spv::Id& id) { localId(id, unmapped); } - ); -} - -// Read word stream from disk -void spirvbin_t::read(const std::string& inFilename) -{ - std::ifstream fp; - filename = inFilename; - - msg(2, 2, std::string("reading: ") + filename); - - spv.clear(); - fp.open(filename, std::fstream::in | std::fstream::binary); - - if (fp.fail()) - ferror("error opening file for read: "); - - // Reserve space (for efficiency, not for correctness) - fp.seekg(0, fp.end); - spv.reserve(size_t(fp.tellg()) / sizeof(spirword_t)); - fp.seekg(0, fp.beg); - - while (!fp.eof()) { - spirword_t inWord; - fp.read((char *)&inWord, sizeof(inWord)); - - if (!fp.eof()) { - spv.push_back(inWord); - if (fp.fail()) - ferror("error reading file: "); - } - } -} - - -// Validate the SPIR header -void spirvbin_t::validate() const -{ - msg(2, 2, std::string("validating: ") + filename); - - if (spv.size() < header_size) - ferror("file too short: "); - - if (magic() != spv::MagicNumber) - ferror("bad magic number"); - - // 1 = version: TODO: print for verbose output - // 2 = generator magic: TODO: print for verbose output - // 3 = result bound: TODO: print for verbose output - - if (schemaNum() != 0) - ferror("bad schema, must be 0"); -} - - + ); +} + +// Validate the SPIR header +void spirvbin_t::validate() const +{ + msg(2, 2, std::string("validating: ")); + + if (spv.size() < header_size) + error("file too short: "); + + if (magic() != spv::MagicNumber) + error("bad magic number"); + + // field 1 = version + // field 2 = generator magic + // field 3 = result bound + + if (schemaNum() != 0) + error("bad schema, must be 0"); +} + + int spirvbin_t::processInstruction(int word, instfn_t instFn, idfn_t idFn) { const auto instructionStart = word; const unsigned wordCount = asWordCount(instructionStart); const spv::Op opCode = asOpCode(instructionStart); - const int nextInst = word++ + wordCount; - - if (nextInst > int(spv.size())) - ferror("spir instruction terminated too early"); - - // Base for computing number of operands; will be updated as more is learned - unsigned numOperands = wordCount - 1; - - // msg(5, 4, std::string("opcode: ") + spv::InstructionDesc[opCode].opName); - - if (instFn(opCode, instructionStart)) - return nextInst; - + const int nextInst = word++ + wordCount; + + if (nextInst > int(spv.size())) + error("spir instruction terminated too early"); + + // Base for computing number of operands; will be updated as more is learned + unsigned numOperands = wordCount - 1; + + if (instFn(opCode, instructionStart)) + return nextInst; + // Read type and result ID from instruction desc table if (spv::InstructionDesc[opCode].hasType()) { idFn(asId(word++)); @@ -903,13 +860,13 @@ void spirvbin_t::optLoadStore() buildLocalMaps(); // rebuild ID mapping data } -// remove bodies of uncalled functions -void spirvbin_t::dceFuncs() -{ - msg(3, 2, std::string("Removing Dead Functions: ") + filename); - - // TODO: There are more efficient ways to do this. - bool changed = true; +// remove bodies of uncalled functions +void spirvbin_t::dceFuncs() +{ + msg(3, 2, std::string("Removing Dead Functions: ")); + + // TODO: There are more efficient ways to do this. + bool changed = true; while (changed) { changed = false; @@ -920,13 +877,12 @@ void spirvbin_t::dceFuncs() continue; } - const auto call_it = fnCalls.find(fn->first); - - if (call_it == fnCalls.end() || call_it->second == 0) { - // msg(3, 4, std::string("removing dead function: ") + std::to_string(fn->first)); - changed = true; - stripRange.push_back(fn->second); - fnPosDCE.insert(*fn); + const auto call_it = fnCalls.find(fn->first); + + if (call_it == fnCalls.end() || call_it->second == 0) { + changed = true; + stripRange.push_back(fn->second); + fnPosDCE.insert(*fn); // decrease counts of called functions process( @@ -951,13 +907,13 @@ void spirvbin_t::dceFuncs() } } -// remove unused function variables + decorations -void spirvbin_t::dceVars() -{ - msg(3, 2, std::string("DCE Vars: ") + basename(filename)); - - std::unordered_map varUseCount; - +// remove unused function variables + decorations +void spirvbin_t::dceVars() +{ + msg(3, 2, std::string("DCE Vars: ")); + + std::unordered_map varUseCount; + // Count function variable use process( [&](spv::Op opCode, int start) { @@ -1088,13 +1044,13 @@ spv::Id spirvbin_t::findType(const spirvbin_t::globaltypes_t& globalTypes, spv:: // Return start position in SPV of given type. error if not found. int spirvbin_t::typePos(spv::Id id) const -{ - const auto tid_it = typeConstPosR.find(id); - if (tid_it == typeConstPosR.end()) - ferror("type ID not found"); - - return tid_it->second; -} +{ + const auto tid_it = typeConstPosR.find(id); + if (tid_it == typeConstPosR.end()) + error("type ID not found"); + + return tid_it->second; +} // 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. @@ -1168,22 +1124,22 @@ std::uint32_t spirvbin_t::hashType(int typeStart) const for (unsigned w=3; w < wordCount; ++w) hash += w * spv[typeStart+w]; return hash; - } - - default: - ferror("unknown type opcode"); - return 0; - } -} + } + + default: + error("unknown type opcode"); + return 0; + } +} void spirvbin_t::mapTypeConst() -{ - globaltypes_t globalTypeMap; - - msg(3, 2, std::string("Remapping Consts & Types: ") + basename(filename)); - - static const std::uint32_t softTypeIdLimit = 3011; // small prime. TODO: get from options - static const std::uint32_t firstMappedID = 8; // offset into ID space +{ + globaltypes_t globalTypeMap; + + msg(3, 2, std::string("Remapping Consts & Types: ")); + + static const std::uint32_t softTypeIdLimit = 3011; // small prime. TODO: get from options + static const std::uint32_t firstMappedID = 8; // offset into ID space for (auto& typeStart : typeConstPos) { const spv::Id resId = asTypeConstId(typeStart); @@ -1257,19 +1213,10 @@ void spirvbin_t::remap(std::vector& in_spv, std::uint32_t opts) { spv.swap(in_spv); remap(opts); - spv.swap(in_spv); -} - -// remap from a disk file -void spirvbin_t::remap(const std::string& file, const std::string& outputDir, - std::uint32_t opts) -{ - read(file); - remap(opts); - write(outputDir); -} - -} // namespace SPV - -#endif // defined (use_cpp11) + spv.swap(in_spv); +} + +} // namespace SPV + +#endif // defined (use_cpp11) diff --git a/SPIRV/SPVRemapper.h b/SPIRV/SPVRemapper.h index 4d63410..73b09ec 100644 --- a/SPIRV/SPVRemapper.h +++ b/SPIRV/SPVRemapper.h @@ -1,281 +1,286 @@ - -#ifndef SPIRVREMAPPER_H -#define SPIRVREMAPPER_H - -#include -#include - -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 -# define use_cpp11 1 -#endif - -class spirvbin_base_t -{ -public: - enum Options { - NONE = 0, - STRIP = (1<<0), - MAP_TYPES = (1<<1), - MAP_NAMES = (1<<2), - MAP_FUNCS = (1<<3), - DCE_FUNCS = (1<<4), - DCE_VARS = (1<<5), - DCE_TYPES = (1<<6), - OPT_LOADSTORE = (1<<7), - OPT_FWD_LS = (1<<8), // EXPERIMENTAL: PRODUCES INVALID SCHEMA-0 SPIRV - MAP_ALL = (MAP_TYPES | MAP_NAMES | MAP_FUNCS), - DCE_ALL = (DCE_FUNCS | DCE_VARS | DCE_TYPES), - OPT_ALL = (OPT_LOADSTORE), - - ALL_BUT_STRIP = (MAP_ALL | DCE_ALL | OPT_ALL), - DO_EVERYTHING = (STRIP | ALL_BUT_STRIP) - }; - -// OS dependent path separator (avoiding boost::filesystem dependency) -#if defined(_WIN32) - static const char path_sep_char() { return '\\'; } -#else - static const char path_sep_char() { return '/'; } -#endif - - // Poor man's basename, to avoid external dependencies - static const std::string basename(const std::string& filename); -}; - -} // namespace SPV - -#if !defined (use_cpp11) -#include - -namespace spv { - -class spirvbin_t : public spirvbin_base_t -{ -public: - spirvbin_t(int verbose = 0) { } - - void remap(std::vector& spv, unsigned int opts = 0) - { - printf("Tool not compiled for C++11, which is required for SPIR-V remapping.\n"); - } - - void remap(const std::string& filename, const std::string& outputDir, unsigned int opts = 0) - { - printf("Tool not compiled for C++11, which is required for SPIR-V remapping.\n"); - } -}; - -} // namespace SPV - -#else // defined (use_cpp11) - -#include -#include -#include -#include -#include -#include -#include - -#include "../../glslang/SPIRV/spirv.h" -#include "../../glslang/SPIRV/spvIR.h" - -namespace spv { - -// class to hold SPIRV binary data for remapping, DCE, and debug stripping -class spirvbin_t : public spirvbin_base_t -{ -public: - spirvbin_t(int verbose = 0) : entryPoint(spv::NoResult), largestNewId(0), verbose(verbose) { } - - // remap on an existing binary in memory - void remap(std::vector& spv, std::uint32_t opts = Options::DO_EVERYTHING); - - // load binary from disk file, and remap that. - void remap(const std::string& filename, const std::string& outputDir, - std::uint32_t opts = Options::DO_EVERYTHING); - - // Type for error/log handler functions - typedef std::function errorfn_t; - typedef std::function logfn_t; - - // Register error/log handling functions (can be lambda fn / functor / etc) - static void registerErrorHandler(errorfn_t handler) { errorHandler = handler; } - static void registerLogHandler(logfn_t handler) { logHandler = handler; } - -protected: - // This can be overridden to provide other message behavior if needed - virtual void msg(int minVerbosity, int indent, const std::string& txt) const; - -private: - // write SPV to given directory using filename passed to remap(filename...) - void write(const std::string& outputDir) const; - - // Local to global, or global to local ID map - typedef std::unordered_map idmap_t; - typedef std::unordered_set idset_t; - - void read(const std::string& filename); // read SPV from disk file - void remap(std::uint32_t opts = Options::DO_EVERYTHING); - - // Map of names to IDs - typedef std::unordered_map namemap_t; - - typedef std::uint32_t spirword_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 - static const spv::Id unused; // unused ID - static const int header_size; // SPIR header = 5 words - - class id_iterator_t; - - // For mapping type entries between different shaders - typedef std::vector typeentry_t; - typedef std::map globaltypes_t; - - // A set that preserves position order, and a reverse map - typedef std::set posmap_t; - typedef std::unordered_map posmap_rev_t; - - // handle error - void error(const std::string& txt) const { errorHandler(txt); } - // handle error with our filename appended to the string - void ferror(const std::string& txt) const { - error(std::string("\nERROR processing file ") + filename + ":\n" + txt); - } - - bool isConstOp(spv::Op opCode) const; - bool isTypeOp(spv::Op opCode) const; - bool isStripOp(spv::Op opCode) const; - bool isFlowCtrlOpen(spv::Op opCode) const; - bool isFlowCtrlClose(spv::Op opCode) const; - range_t literalRange(spv::Op opCode) const; - 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; - - static unsigned opWordCount(spirword_t data) { return data >> spv::WordCountShift; } - static spv::Op opOpCode(spirword_t data) { return spv::Op(data & spv::OpCodeMask); } - - // Header access & set methods - spirword_t magic() const { return spv[0]; } // return magic number - spirword_t bound() const { return spv[3]; } // return Id bound from header - spirword_t bound(spirword_t b) { return spv[3] = b; }; - spirword_t genmagic() const { return spv[2]; } // generator magic - spirword_t genmagic(spirword_t m) { return spv[2] = m; } - spirword_t schemaNum() const { return spv[4]; } // schema number from header - - // Mapping fns: get - spv::Id localId(spv::Id id) const { return idMapL[id]; } - - // Mapping fns: set - inline spv::Id localId(spv::Id id, spv::Id newId); - void countIds(spv::Id id); - - // Return next unused new local ID. - // NOTE: boost::dynamic_bitset would be more efficient due to find_next(), - // which std::vector doens't have. - inline spv::Id nextUnusedId(spv::Id id); - - void buildLocalMaps(); - std::string literalString(int 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); } - bool isOldIdUnmapped(spv::Id oldId) const { return localId(oldId) == unmapped; } - bool isOldIdUnused(spv::Id oldId) const { return localId(oldId) == unused; } - bool isOldIdMapped(spv::Id oldId) const { return !isOldIdUnused(oldId) && !isOldIdUnmapped(oldId); } - bool isFunction(spv::Id oldId) const { return fnPos.find(oldId) != fnPos.end(); } - - // 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; - - spirvbin_t& process(instfn_t, idfn_t, int begin = 0, int end = 0); - int processInstruction(int word, instfn_t, idfn_t); - - void validate() const; - void mapTypeConst(); - void mapFnBodies(); - void optLoadStore(); - void dceFuncs(); - void dceVars(); - void dceTypes(); - void mapNames(); - void foldIds(); // fold IDs to smallest space - void forwardLoadStores(); // load store forwarding (EXPERIMENTAL) - void offsetIds(); // create relative offset IDs - - void applyMap(); // remap per local name map - void mapRemainder(); // map any IDs we haven't touched yet - void stripDebug(); // strip debug info - void strip(); // remove debug symbols - - std::vector spv; // SPIR words - std::string filename; // the file this came from - - namemap_t nameMap; // ID names from OpName - - // Since we want to also do binary ops, we can't use std::vector. we could use - // boost::dynamic_bitset, but we're trying to avoid a boost dependency. - typedef std::uint64_t bits_t; - std::vector mapped; // which new IDs have been mapped - static const int mBits = sizeof(bits_t) * 4; - - bool isMapped(spv::Id id) const { return id < maxMappedId() && ((mapped[id/mBits] & (1LL<<(id%mBits))) != 0); } - void setMapped(spv::Id id) { resizeMapped(id); mapped[id/mBits] |= (1LL<<(id%mBits)); } - void resizeMapped(spv::Id id) { if (id >= maxMappedId()) mapped.resize(id/mBits+1, 0); } - size_t maxMappedId() const { return mapped.size() * mBits; } - - // 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))); } - - // 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 - - // Which functions are called, anywhere in the module, with a call count - std::unordered_map fnCalls; - - posmap_t typeConstPos; // word positions that define types & consts (ordered) - posmap_rev_t typeConstPosR; // reverse map from IDs to positions - - std::vector idMapL; // ID {M}ap from {L}ocal to {G}lobal IDs - - spv::Id entryPoint; // module entry point - spv::Id largestNewId; // biggest new ID we have mapped anything to - - // Sections of the binary to strip, given as [begin,end) - std::vector> stripRange; - - // processing options: - std::uint32_t options; - int verbose; // verbosity level - - static errorfn_t errorHandler; - static logfn_t logHandler; -}; - -} // namespace SPV - -#endif // defined (use_cpp11) -#endif // SPIRVREMAPPER_H +// +//Copyright (C) 2015 LunarG, Inc. +// +//All rights reserved. +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions +//are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of 3Dlabs Inc. Ltd. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +//POSSIBILITY OF SUCH DAMAGE. +// + +#ifndef SPIRVREMAPPER_H +#define SPIRVREMAPPER_H + +#include +#include + +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 +# define use_cpp11 1 +#endif + +class spirvbin_base_t +{ +public: + enum Options { + NONE = 0, + STRIP = (1<<0), + MAP_TYPES = (1<<1), + MAP_NAMES = (1<<2), + MAP_FUNCS = (1<<3), + DCE_FUNCS = (1<<4), + DCE_VARS = (1<<5), + DCE_TYPES = (1<<6), + OPT_LOADSTORE = (1<<7), + OPT_FWD_LS = (1<<8), // EXPERIMENTAL: PRODUCES INVALID SCHEMA-0 SPIRV + MAP_ALL = (MAP_TYPES | MAP_NAMES | MAP_FUNCS), + DCE_ALL = (DCE_FUNCS | DCE_VARS | DCE_TYPES), + OPT_ALL = (OPT_LOADSTORE), + + ALL_BUT_STRIP = (MAP_ALL | DCE_ALL | OPT_ALL), + DO_EVERYTHING = (STRIP | ALL_BUT_STRIP) + }; +}; + +} // namespace SPV + +#if !defined (use_cpp11) +#include + +namespace spv { +class spirvbin_t : public spirvbin_base_t +{ +public: + spirvbin_t(int verbose = 0) { } + + void remap(std::vector& spv, unsigned int opts = 0) + { + printf("Tool not compiled for C++11, which is required for SPIR-V remapping.\n"); + } +}; + +} // namespace SPV + +#else // defined (use_cpp11) + +#include +#include +#include +#include +#include +#include +#include + +#include "../../glslang/SPIRV/spirv.h" +#include "../../glslang/SPIRV/spvIR.h" + +namespace spv { + +// class to hold SPIRV binary data for remapping, DCE, and debug stripping +class spirvbin_t : public spirvbin_base_t +{ +public: + spirvbin_t(int verbose = 0) : entryPoint(spv::NoResult), largestNewId(0), verbose(verbose) { } + + // remap on an existing binary in memory + void remap(std::vector& spv, std::uint32_t opts = Options::DO_EVERYTHING); + + // Type for error/log handler functions + typedef std::function errorfn_t; + typedef std::function logfn_t; + + // Register error/log handling functions (can be lambda fn / functor / etc) + static void registerErrorHandler(errorfn_t handler) { errorHandler = handler; } + static void registerLogHandler(logfn_t handler) { logHandler = handler; } + +protected: + // This can be overridden to provide other message behavior if needed + virtual void msg(int minVerbosity, int indent, const std::string& txt) const; + +private: + // Local to global, or global to local ID map + typedef std::unordered_map idmap_t; + typedef std::unordered_set idset_t; + + void remap(std::uint32_t opts = Options::DO_EVERYTHING); + + // Map of names to IDs + typedef std::unordered_map namemap_t; + + typedef std::uint32_t spirword_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 + static const spv::Id unused; // unused ID + static const int header_size; // SPIR header = 5 words + + class id_iterator_t; + + // For mapping type entries between different shaders + typedef std::vector typeentry_t; + typedef std::map globaltypes_t; + + // A set that preserves position order, and a reverse map + typedef std::set posmap_t; + typedef std::unordered_map posmap_rev_t; + + // handle error + void error(const std::string& txt) const { errorHandler(txt); } + + bool isConstOp(spv::Op opCode) const; + bool isTypeOp(spv::Op opCode) const; + bool isStripOp(spv::Op opCode) const; + bool isFlowCtrlOpen(spv::Op opCode) const; + bool isFlowCtrlClose(spv::Op opCode) const; + range_t literalRange(spv::Op opCode) const; + 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; + + static unsigned opWordCount(spirword_t data) { return data >> spv::WordCountShift; } + static spv::Op opOpCode(spirword_t data) { return spv::Op(data & spv::OpCodeMask); } + + // Header access & set methods + spirword_t magic() const { return spv[0]; } // return magic number + spirword_t bound() const { return spv[3]; } // return Id bound from header + spirword_t bound(spirword_t b) { return spv[3] = b; }; + spirword_t genmagic() const { return spv[2]; } // generator magic + spirword_t genmagic(spirword_t m) { return spv[2] = m; } + spirword_t schemaNum() const { return spv[4]; } // schema number from header + + // Mapping fns: get + spv::Id localId(spv::Id id) const { return idMapL[id]; } + + // Mapping fns: set + inline spv::Id localId(spv::Id id, spv::Id newId); + void countIds(spv::Id id); + + // Return next unused new local ID. + // NOTE: boost::dynamic_bitset would be more efficient due to find_next(), + // which std::vector doens't have. + inline spv::Id nextUnusedId(spv::Id id); + + void buildLocalMaps(); + std::string literalString(int 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); } + bool isOldIdUnmapped(spv::Id oldId) const { return localId(oldId) == unmapped; } + bool isOldIdUnused(spv::Id oldId) const { return localId(oldId) == unused; } + bool isOldIdMapped(spv::Id oldId) const { return !isOldIdUnused(oldId) && !isOldIdUnmapped(oldId); } + bool isFunction(spv::Id oldId) const { return fnPos.find(oldId) != fnPos.end(); } + + // 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; + + spirvbin_t& process(instfn_t, idfn_t, int begin = 0, int end = 0); + int processInstruction(int word, instfn_t, idfn_t); + + void validate() const; + void mapTypeConst(); + void mapFnBodies(); + void optLoadStore(); + void dceFuncs(); + void dceVars(); + void dceTypes(); + void mapNames(); + void foldIds(); // fold IDs to smallest space + void forwardLoadStores(); // load store forwarding (EXPERIMENTAL) + void offsetIds(); // create relative offset IDs + + void applyMap(); // remap per local name map + void mapRemainder(); // map any IDs we haven't touched yet + void stripDebug(); // strip debug info + void strip(); // remove debug symbols + + std::vector spv; // SPIR words + + namemap_t nameMap; // ID names from OpName + + // Since we want to also do binary ops, we can't use std::vector. we could use + // boost::dynamic_bitset, but we're trying to avoid a boost dependency. + typedef std::uint64_t bits_t; + std::vector mapped; // which new IDs have been mapped + static const int mBits = sizeof(bits_t) * 4; + + bool isMapped(spv::Id id) const { return id < maxMappedId() && ((mapped[id/mBits] & (1LL<<(id%mBits))) != 0); } + void setMapped(spv::Id id) { resizeMapped(id); mapped[id/mBits] |= (1LL<<(id%mBits)); } + void resizeMapped(spv::Id id) { if (id >= maxMappedId()) mapped.resize(id/mBits+1, 0); } + size_t maxMappedId() const { return mapped.size() * mBits; } + + // 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))); } + + // 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 + + // Which functions are called, anywhere in the module, with a call count + std::unordered_map fnCalls; + + posmap_t typeConstPos; // word positions that define types & consts (ordered) + posmap_rev_t typeConstPosR; // reverse map from IDs to positions + + std::vector idMapL; // ID {M}ap from {L}ocal to {G}lobal IDs + + spv::Id entryPoint; // module entry point + spv::Id largestNewId; // biggest new ID we have mapped anything to + + // Sections of the binary to strip, given as [begin,end) + std::vector> stripRange; + + // processing options: + std::uint32_t options; + int verbose; // verbosity level + + static errorfn_t errorHandler; + static logfn_t logHandler; +}; + +} // namespace SPV + +#endif // defined (use_cpp11) +#endif // SPIRVREMAPPER_H diff --git a/StandAlone/StandAlone.cpp b/StandAlone/StandAlone.cpp index 3cf9deb..4f38566 100644 --- a/StandAlone/StandAlone.cpp +++ b/StandAlone/StandAlone.cpp @@ -44,7 +44,6 @@ #include "../SPIRV/GLSL450Lib.h" #include "../SPIRV/doc.h" #include "../SPIRV/disassemble.h" -#include "../SPIRV/SPVRemapper.h" #include #include #include @@ -72,8 +71,6 @@ enum TOptions { EOptionSpv = 0x0800, EOptionHumanReadableSpv = 0x1000, EOptionDefaultDesktop = 0x2000, - EOptionCanonicalizeSpv = 0x4000, - EOptionStripSpv = 0x8000, }; // @@ -484,17 +481,11 @@ bool ProcessArguments(int argc, char* argv[]) for (; argc >= 1; argc--, argv++) { Work[argc] = 0; if (argv[0][0] == '-') { - const char optLetter = argv[0][1]; - - switch (optLetter) { - case 'S': // fall through to -V - case 'C': // fall through to -V - case 'H': // fall through to -V + switch (argv[0][1]) { + case 'H': + Options |= EOptionHumanReadableSpv; + // fall through to -V case 'V': - if (optLetter == 'H') Options |= EOptionHumanReadableSpv; - if (optLetter == 'S') Options |= EOptionStripSpv; - if (optLetter == 'C') Options |= EOptionCanonicalizeSpv; - Options |= EOptionSpv; Options |= EOptionLinkProgram; break; @@ -669,17 +660,7 @@ void CompileAndLinkShaders() case EShLangCompute: name = "comp"; break; default: name = "unknown"; break; } - if (Options & (EOptionCanonicalizeSpv | EOptionStripSpv)) { - const unsigned int remapOpts = - ((Options & EOptionCanonicalizeSpv) ? (spv::spirvbin_t::ALL_BUT_STRIP) : 0) | - ((Options & EOptionStripSpv) ? (spv::spirvbin_t::STRIP) : 0); - - spv::Parameterize(); - spv::spirvbin_t().remap(spirv, remapOpts); - } - glslang::OutputSpv(spirv, name); - if (Options & EOptionHumanReadableSpv) { spv::Parameterize(); GLSL_STD_450::GetDebugNames(GlslStd450DebugNames); @@ -886,8 +867,6 @@ void usage() "To get other information, use one of the following options:\n" "(Each option must be specified separately, but can go anywhere in the command line.)\n" " -V create SPIR-V in file .spv\n" - " -C canonicalize generated SPIR-V: turns on -V\n" - " -S debug-strip SPIR-V: turns on -V\n" " -H print human readable form of SPIR-V; turns on -V\n" " -c configuration dump; use to create default configuration file (redirect to a .conf file)\n" " -d default to desktop (#version 110) when there is no version in the shader (default is ES version 100)\n"