SPIR-V compression: Requires rerunning CMake. Adds a standalone tool for running...
authorJohn Kessenich <cepheus@frii.com>
Tue, 19 May 2015 21:07:04 +0000 (21:07 +0000)
committerJohn Kessenich <cepheus@frii.com>
Tue, 19 May 2015 21:07:04 +0000 (21:07 +0000)
git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@31232 e7fa87d3-cd2b-0410-9028-fcbf551c1848

README-spirv-remap.txt [new file with mode: 0644]
SPIRV/SPVRemapper.cpp
StandAlone/CMakeLists.txt
StandAlone/spirv-remap.cpp [new file with mode: 0644]

diff --git a/README-spirv-remap.txt b/README-spirv-remap.txt
new file mode 100644 (file)
index 0000000..abd924e
--- /dev/null
@@ -0,0 +1,137 @@
+
+VERSION
+--------------------------------------------------------------------------------
+spirv-remap 0.97
+
+INTRO:
+--------------------------------------------------------------------------------
+spirv-remap is a utility to improve compression of SPIRV binary files via
+entropy reduction, plus optional stripping of debug information and
+load/store optimization.  It transforms SPIRV to SPIRV, remapping IDs.  The
+resulting modules have an increased ID range (IDs are not as tightly packed
+around zero), but will compress better when multiple modules are compressed
+together, since compressor's dictionary can find better cross module
+commonality.
+
+Remapping is accomplished via canonicalization.  Thus, modules can be
+compressed one at a time with no loss of quality relative to operating on
+many modules at once.  The command line tool operates on multiple modules
+only in the trivial repetition sense, for ease of use.  The remapper API
+only accepts a single module at a time.
+
+There are two modes of use: command line, and a C++11 API.  Both are
+described below.
+
+spirv-remap is currently in an alpha state.  Although there are no known
+remapping defects, it has only been exercised on one real world game shader
+workload.
+
+
+FEEDBACK
+--------------------------------------------------------------------------------
+Report defects, enhancements requests, code improvements, etc to:
+   spvremapper@lunarg.com
+
+
+COMMAND LINE USAGE:
+--------------------------------------------------------------------------------
+Examples are given with a verbosity of one (-v), but more verbosity can be
+had via -vv, -vvv, etc, or an integer parameter to --verbose, such as
+"--verbose 4".  With no verbosity, the command is silent and returns 0 on
+success, and a positive integer error on failure.
+
+Pre-built binaries for several OSs are available.  Examples presented are
+for Linux.  Command line arguments can be provided in any order.
+
+1. Basic ID remapping
+
+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
+
+2. Perform all possible size reductions
+
+  spirv-remap-linux-64 --do-everything --input *.spv --output /tmp/out_dir
+
+Note that --do-everything is a synonym for:
+
+  --map all --dce all --opt all --strip all
+
+API USAGE:
+--------------------------------------------------------------------------------
+
+The public interface to the remapper is defined in SPIRV/SPVRemapper.h as follows:
+
+namespace spv {
+
+class spirvbin_t
+{
+public:
+   enum Options { ... };
+   spirvbin_t(int verbose = 0);  // construct
+
+   // remap an existing binary in memory
+   void remap(std::vector<std::uint32_t>& spv, std::uint32_t opts = Options::DO_EVERYTHING);
+
+   // Type for error/log handler functions
+   typedef std::function<void(const std::string&)> errorfn_t;
+   typedef std::function<void(const std::string&)> logfn_t;
+
+   // Register error/log handling functions (can be c/c++ fn, lambda fn, or functor)
+   static void registerErrorHandler(errorfn_t handler) { errorHandler = handler; }
+   static void registerLogHandler(logfn_t handler)     { logHandler   = handler; }
+};
+
+} // namespace spv
+
+The class definition is in SPVRemapper.cpp.
+
+remap() accepts an std::vector of SPIRV words, modifies them per the
+request given in 'opts', and leaves the 'spv' container with the result.
+It is safe to instantiate one spirvbin_t per thread and process a different
+SPIRV in each.
+
+The "opts" parameter to remap() accepts a bit mask of desired remapping
+options.  See REMAPPING AND OPTIMIZATION OPTIONS.
+
+On error, the function supplied to registerErrorHandler() will be invoked.
+This can be a standard C/C++ function, a lambda function, or a functor.
+The default handler simply calls exit(5); The error handler is a static
+members, so need only be set up once, not once per spirvbin_t instance.
+
+Log messages are supplied to registerLogHandler().  By default, log
+messages are eaten silently.  The log handler is also a static member.
+
+BUILD DEPENDENCIES:
+--------------------------------------------------------------------------------
+ 1. C++11 compatible compiler
+ 2. cmake
+ 3. glslang
+
+
+BUILDING
+--------------------------------------------------------------------------------
+The standalone remapper is built along side glslangValidator through its
+normal build process.
+
+
+REMAPPING AND OPTIMIZATION OPTIONS
+--------------------------------------------------------------------------------
+API:
+   These are bits defined under spv::spirvbin_t::Options::, and can be
+   bitwise or-ed together as desired.
+
+   MAP_TYPES      = canonicalize type IDs
+   MAP_NAMES      = canonicalize named data
+   MAP_FUNCS      = canonicalize function bodies
+   DCE_FUNCS      = remove dead functions
+   DCE_VARS       = remove dead variables
+   DCE_TYPES      = remove dead types
+   OPT_LOADSTORE  = optimize unneeded load/stores
+   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)
+
index 97eb4a5..d390c77 100644 (file)
 //ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
 //POSSIBILITY OF SUCH DAMAGE.\r
 //\r
-
-#include "SPVRemapper.h"
-#include "doc.h"
 \r
-/* -*-mode:c++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 3 -*- */\r
+#include "SPVRemapper.h"\r
+#include "doc.h"\r
 \r
 #if !defined (use_cpp11)\r
 // ... not supported before C++11\r
 #include <algorithm>\r
 #include <cassert>\r
 \r
-namespace spv {
-
-// By default, just abort on error.  Can be overridden via RegisterErrorHandler
-spirvbin_t::errorfn_t spirvbin_t::errorHandler = [](const std::string&) { exit(5); };
-// By default, eat log messages.  Can be overridden via RegisterLogHandler
-spirvbin_t::logfn_t   spirvbin_t::logHandler   = [](const std::string&) { };
-
-// This can be overridden to provide other message behavior if needed
-void spirvbin_t::msg(int minVerbosity, int indent, const std::string& txt) const
-{
-   if (verbose >= minVerbosity)
-      logHandler(std::string(indent, ' ') + txt);
-}
-
-// hash opcode, with special handling for OpExtInst
-std::uint32_t spirvbin_t::asOpCodeHash(int word)
-{
-   const spv::Op opCode = asOpCode(word);
-
-   std::uint32_t offset = 0;
-
-   switch (opCode) {
-      case spv::OpExtInst:
-         offset += asId(word + 4); break;
-      default:
-         break;
-   }
-   
-   return opCode * 19 + offset; // 19 = small prime
-}
-
-spirvbin_t::range_t spirvbin_t::literalRange(spv::Op opCode) const
-{
-   static const int maxCount = 1<<30;
-
-   switch (opCode) {
-   case spv::OpTypeFloat:        // fall through...
-   case spv::OpTypePointer:      return range_t(2, 3);
-   case spv::OpTypeInt:          return range_t(2, 4);
-   case spv::OpTypeSampler:      return range_t(3, 8);
-   case spv::OpTypeVector:       // fall through
-   case spv::OpTypeMatrix:       // ...
-   case spv::OpTypePipe:         return range_t(3, 4);
-   case spv::OpConstant:         return range_t(3, maxCount);
-   default:                      return range_t(0, 0);
-   }
-}
-
-spirvbin_t::range_t spirvbin_t::typeRange(spv::Op opCode) const
-{
-   static const int maxCount = 1<<30;
-
-   if (isConstOp(opCode))
-      return range_t(1, 2);
-   
-   switch (opCode) {
-   case spv::OpTypeVector:       // fall through
-   case spv::OpTypeMatrix:       // ... 
-   case spv::OpTypeSampler:      // ... 
-   case spv::OpTypeArray:        // ... 
-   case spv::OpTypeRuntimeArray: // ... 
-   case spv::OpTypePipe:         return range_t(2, 3);
-   case spv::OpTypeStruct:       // fall through
-   case spv::OpTypeFunction:     return range_t(2, maxCount);
-   case spv::OpTypePointer:      return range_t(3, 4);
-   default:                      return range_t(0, 0);
-   }
-}
-
-spirvbin_t::range_t spirvbin_t::constRange(spv::Op opCode) const
-{
-   static const int maxCount = 1<<30;
-
-   switch (opCode) {
-   case spv::OpTypeArray:         // fall through...
-   case spv::OpTypeRuntimeArray:  return range_t(3, 4);
-   case spv::OpConstantComposite: return range_t(3, maxCount);
-   default:                       return range_t(0, 0);
-   }
-}
-
-// Is this an opcode we should remove when using --strip?
-bool spirvbin_t::isStripOp(spv::Op opCode) const
-{
-   switch (opCode) {
-      case spv::OpSource:
-      case spv::OpSourceExtension:
-      case spv::OpName:
-      case spv::OpMemberName:
-      case spv::OpLine:           return true;
-      default:                    return false;
-   }
-}
-
-bool spirvbin_t::isFlowCtrlOpen(spv::Op opCode) const
-{
-   switch (opCode) {
-      case spv::OpBranchConditional:
-      case spv::OpSwitch:         return true;
-      default:                    return false;
-   }
-}
-
-bool spirvbin_t::isFlowCtrlClose(spv::Op opCode) const
-{
-   switch (opCode) {
-      case spv::OpLoopMerge:
-      case spv::OpSelectionMerge: return true;
-      default:                    return false;
-   }
-}
-
-bool spirvbin_t::isTypeOp(spv::Op opCode) const
-{
-   switch (opCode) {
-      case spv::OpTypeVoid:
-      case spv::OpTypeBool:
-      case spv::OpTypeInt:
-      case spv::OpTypeFloat:
-      case spv::OpTypeVector:
-      case spv::OpTypeMatrix:
-      case spv::OpTypeSampler:
-      case spv::OpTypeFilter:
-      case spv::OpTypeArray:
-      case spv::OpTypeRuntimeArray:
-      case spv::OpTypeStruct:
-      case spv::OpTypeOpaque:
-      case spv::OpTypePointer:
-      case spv::OpTypeFunction:
-      case spv::OpTypeEvent:
-      case spv::OpTypeDeviceEvent:
-      case spv::OpTypeReserveId:
-      case spv::OpTypeQueue:
-      case spv::OpTypePipe:         return true;
-      default:                      return false;
-   }
-}
-
-bool spirvbin_t::isConstOp(spv::Op opCode) const
-{
-   switch (opCode) {
-      case spv::OpConstantNullObject: error("unimplemented constant type");
-      case spv::OpConstantSampler:    error("unimplemented constant type");
-
-      case spv::OpConstantTrue:
-      case spv::OpConstantFalse:
-      case spv::OpConstantNullPointer:
-      case spv::OpConstantComposite:
-      case spv::OpConstant:         return true;
-      default:                      return false;
-   }
-}
-
-const auto inst_fn_nop = [](spv::Op, int) { 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.
-// Defining them externally seems to please both compilers, so, here they are.
-const spv::Id spirvbin_t::unmapped    = spv::Id(-10000);
-const spv::Id spirvbin_t::unused      = spv::Id(-10001);
-const int     spirvbin_t::header_size = 5;
-
-spv::Id spirvbin_t::nextUnusedId(spv::Id id)
-{
-   while (isNewIdMapped(id))  // search for an unused ID
-      ++id;
-
-   return id;
-}
-
-spv::Id spirvbin_t::localId(spv::Id id, spv::Id newId)
-{
-   assert(id != spv::NoResult && newId != spv::NoResult);
-   
-   if (id >= idMapL.size())
-      idMapL.resize(id+1, unused);
-\r
-   if (newId != unmapped && newId != unused) {\r
-      if (isOldIdUnused(id))\r
-          error(std::string("ID unused in module: ") + std::to_string(id));\r
-\r
-      if (!isOldIdUnmapped(id))\r
-         error(std::string("ID already mapped: ") + std::to_string(id) + " -> "\r
+namespace spv {\r
+\r
+    // By default, just abort on error.  Can be overridden via RegisterErrorHandler\r
+    spirvbin_t::errorfn_t spirvbin_t::errorHandler = [](const std::string&) { exit(5); };\r
+    // By default, eat log messages.  Can be overridden via RegisterLogHandler\r
+    spirvbin_t::logfn_t   spirvbin_t::logHandler   = [](const std::string&) { };\r
+\r
+    // This can be overridden to provide other message behavior if needed\r
+    void spirvbin_t::msg(int minVerbosity, int indent, const std::string& txt) const\r
+    {\r
+        if (verbose >= minVerbosity)\r
+            logHandler(std::string(indent, ' ') + txt);\r
+    }\r
+\r
+    // hash opcode, with special handling for OpExtInst\r
+    std::uint32_t spirvbin_t::asOpCodeHash(int word)\r
+    {\r
+        const spv::Op opCode = asOpCode(word);\r
+\r
+        std::uint32_t offset = 0;\r
+\r
+        switch (opCode) {\r
+        case spv::OpExtInst:\r
+            offset += asId(word + 4); break;\r
+        default:\r
+            break;\r
+        }\r
+\r
+        return opCode * 19 + offset; // 19 = small prime\r
+    }\r
+\r
+    spirvbin_t::range_t spirvbin_t::literalRange(spv::Op opCode) const\r
+    {\r
+        static const int maxCount = 1<<30;\r
+\r
+        switch (opCode) {\r
+        case spv::OpTypeFloat:        // fall through...\r
+        case spv::OpTypePointer:      return range_t(2, 3);\r
+        case spv::OpTypeInt:          return range_t(2, 4);\r
+        case spv::OpTypeSampler:      return range_t(3, 8);\r
+        case spv::OpTypeVector:       // fall through\r
+        case spv::OpTypeMatrix:       // ...\r
+        case spv::OpTypePipe:         return range_t(3, 4);\r
+        case spv::OpConstant:         return range_t(3, maxCount);\r
+        default:                      return range_t(0, 0);\r
+        }\r
+    }\r
+\r
+    spirvbin_t::range_t spirvbin_t::typeRange(spv::Op opCode) const\r
+    {\r
+        static const int maxCount = 1<<30;\r
+\r
+        if (isConstOp(opCode))\r
+            return range_t(1, 2);\r
+\r
+        switch (opCode) {\r
+        case spv::OpTypeVector:       // fall through\r
+        case spv::OpTypeMatrix:       // ... \r
+        case spv::OpTypeSampler:      // ... \r
+        case spv::OpTypeArray:        // ... \r
+        case spv::OpTypeRuntimeArray: // ... \r
+        case spv::OpTypePipe:         return range_t(2, 3);\r
+        case spv::OpTypeStruct:       // fall through\r
+        case spv::OpTypeFunction:     return range_t(2, maxCount);\r
+        case spv::OpTypePointer:      return range_t(3, 4);\r
+        default:                      return range_t(0, 0);\r
+        }\r
+    }\r
+\r
+    spirvbin_t::range_t spirvbin_t::constRange(spv::Op opCode) const\r
+    {\r
+        static const int maxCount = 1<<30;\r
+\r
+        switch (opCode) {\r
+        case spv::OpTypeArray:         // fall through...\r
+        case spv::OpTypeRuntimeArray:  return range_t(3, 4);\r
+        case spv::OpConstantComposite: return range_t(3, maxCount);\r
+        default:                       return range_t(0, 0);\r
+        }\r
+    }\r
+\r
+    // Is this an opcode we should remove when using --strip?\r
+    bool spirvbin_t::isStripOp(spv::Op opCode) const\r
+    {\r
+        switch (opCode) {\r
+        case spv::OpSource:\r
+        case spv::OpSourceExtension:\r
+        case spv::OpName:\r
+        case spv::OpMemberName:\r
+        case spv::OpLine:           return true;\r
+        default:                    return false;\r
+        }\r
+    }\r
+\r
+    bool spirvbin_t::isFlowCtrlOpen(spv::Op opCode) const\r
+    {\r
+        switch (opCode) {\r
+        case spv::OpBranchConditional:\r
+        case spv::OpSwitch:         return true;\r
+        default:                    return false;\r
+        }\r
+    }\r
+\r
+    bool spirvbin_t::isFlowCtrlClose(spv::Op opCode) const\r
+    {\r
+        switch (opCode) {\r
+        case spv::OpLoopMerge:\r
+        case spv::OpSelectionMerge: return true;\r
+        default:                    return false;\r
+        }\r
+    }\r
+\r
+    bool spirvbin_t::isTypeOp(spv::Op opCode) const\r
+    {\r
+        switch (opCode) {\r
+        case spv::OpTypeVoid:\r
+        case spv::OpTypeBool:\r
+        case spv::OpTypeInt:\r
+        case spv::OpTypeFloat:\r
+        case spv::OpTypeVector:\r
+        case spv::OpTypeMatrix:\r
+        case spv::OpTypeSampler:\r
+        case spv::OpTypeFilter:\r
+        case spv::OpTypeArray:\r
+        case spv::OpTypeRuntimeArray:\r
+        case spv::OpTypeStruct:\r
+        case spv::OpTypeOpaque:\r
+        case spv::OpTypePointer:\r
+        case spv::OpTypeFunction:\r
+        case spv::OpTypeEvent:\r
+        case spv::OpTypeDeviceEvent:\r
+        case spv::OpTypeReserveId:\r
+        case spv::OpTypeQueue:\r
+        case spv::OpTypePipe:         return true;\r
+        default:                      return false;\r
+        }\r
+    }\r
+\r
+    bool spirvbin_t::isConstOp(spv::Op opCode) const\r
+    {\r
+        switch (opCode) {\r
+        case spv::OpConstantNullObject: error("unimplemented constant type");\r
+        case spv::OpConstantSampler:    error("unimplemented constant type");\r
+\r
+        case spv::OpConstantTrue:\r
+        case spv::OpConstantFalse:\r
+        case spv::OpConstantNullPointer:\r
+        case spv::OpConstantComposite:\r
+        case spv::OpConstant:         return true;\r
+        default:                      return false;\r
+        }\r
+    }\r
+\r
+    const auto inst_fn_nop = [](spv::Op, int) { return false; };\r
+    const auto op_fn_nop   = [](spv::Id&)     { };\r
+\r
+    // g++ doesn't like these defined in the class proper in an anonymous namespace.\r
+    // Dunno why.  Also MSVC doesn't like the constexpr keyword.  Also dunno why.\r
+    // Defining them externally seems to please both compilers, so, here they are.\r
+    const spv::Id spirvbin_t::unmapped    = spv::Id(-10000);\r
+    const spv::Id spirvbin_t::unused      = spv::Id(-10001);\r
+    const int     spirvbin_t::header_size = 5;\r
+\r
+    spv::Id spirvbin_t::nextUnusedId(spv::Id id)\r
+    {\r
+        while (isNewIdMapped(id))  // search for an unused ID\r
+            ++id;\r
+\r
+        return id;\r
+    }\r
+\r
+    spv::Id spirvbin_t::localId(spv::Id id, spv::Id newId)\r
+    {\r
+        assert(id != spv::NoResult && newId != spv::NoResult);\r
+\r
+        if (id >= idMapL.size())\r
+            idMapL.resize(id+1, unused);\r
+\r
+        if (newId != unmapped && newId != unused) {\r
+            if (isOldIdUnused(id))\r
+                error(std::string("ID unused in module: ") + std::to_string(id));\r
+\r
+            if (!isOldIdUnmapped(id))\r
+                error(std::string("ID already mapped: ") + std::to_string(id) + " -> "\r
                 + std::to_string(localId(id)));\r
 \r
-      if (isNewIdMapped(newId))\r
-         error(std::string("ID already used in module: ") + std::to_string(newId));\r
-\r
-      msg(4, 4, std::string("map: ") + std::to_string(id) + " -> " + std::to_string(newId));\r
-      setMapped(newId);\r
-      largestNewId = std::max(largestNewId, newId);
-   }
-
-   return idMapL[id] = newId;
-}
-
-// 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 literal;
-
-   literal.reserve(16);
-
-   const char* bytes = reinterpret_cast<const char*>(spv.data() + word);
-
-   while (bytes && *bytes)
-      literal += *bytes++;
-   
-   return literal;
-}\r
-\r
-\r
-void spirvbin_t::applyMap()\r
-{\r
-   msg(3, 2, std::string("Applying map: "));\r
-   \r
-   // Map local IDs through the ID map\r
-   process(inst_fn_nop, // ignore instructions\r
-           [this](spv::Id& id) {
-              id = localId(id);
-              assert(id != unused && id != unmapped);
-           }
-          );
-}
-
-
-// Find free IDs for anything we haven't mapped\r
-void spirvbin_t::mapRemainder()\r
-{\r
-   msg(3, 2, std::string("Remapping remainder: "));\r
-\r
-   spv::Id     unusedId  = 1;  // can't use 0: that's NoResult\r
-   spirword_t  maxBound  = 0;\r
-
-   for (spv::Id id = 0; id < idMapL.size(); ++id) {
-      if (isOldIdUnused(id))
-         continue;
-
-      // Find a new mapping for any used but unmapped IDs
-      if (isOldIdUnmapped(id))
-         localId(id, unusedId = nextUnusedId(unusedId));\r
-\r
-      if (isOldIdUnmapped(id))\r
-         error(std::string("old ID not mapped: ") + std::to_string(id));\r
-\r
-      // Track max bound\r
-      maxBound = std::max(maxBound, localId(id) + 1);\r
-   }
-
-   bound(maxBound); // reset header ID bound to as big as it now needs to be
-}
-
-void spirvbin_t::stripDebug()
-{
-   if ((options & Options::STRIP) == 0)
-      return;
-
-   // build local Id and name maps
-   process(
-      [&](spv::Op opCode, int start) {
-         // remember opcodes we want to strip later
-         if (isStripOp(opCode))
-            stripInst(start);
-         return true;
-      },
-      op_fn_nop);
-}
-\r
-void spirvbin_t::buildLocalMaps()\r
-{\r
-   msg(2, 2, std::string("build local maps: "));\r
-\r
-   mapped.clear();\r
-   idMapL.clear();\r
-   nameMap.clear();
-   fnPos.clear();
-   fnPosDCE.clear();
-   fnCalls.clear();
-   typeConstPos.clear();
-   typeConstPosR.clear();
-   entryPoint = spv::NoResult;
-   largestNewId = 0;
-   
-   idMapL.resize(bound(), unused);
-
-   int         fnStart = 0;
-   spv::Id     fnRes   = spv::NoResult;
-
-   // build local Id and name maps
-   process(
-      [&](spv::Op opCode, int start) {
-         // remember opcodes we want to strip later
-         if ((options & Options::STRIP) && isStripOp(opCode))
-            stripInst(start);
-
-         if (opCode == spv::Op::OpName) {
-            const spv::Id    target = asId(start+1);
-            const std::string  name   = literalString(start+2);
-            nameMap[name] = target;
-            return true;
-         } else if (opCode == spv::Op::OpFunctionCall) {
-            ++fnCalls[asId(start + 3)];
-         } else if (opCode == spv::Op::OpEntryPoint) {
-            entryPoint = asId(start + 2);\r
-         } else if (opCode == spv::Op::OpFunction) {\r
-            if (fnStart != 0)\r
-               error("nested function found");\r
-            fnStart = start;\r
-            fnRes   = asId(start + 2);\r
-         } else if (opCode == spv::Op::OpFunctionEnd) {\r
-            assert(fnRes != spv::NoResult);\r
-            if (fnStart == 0)\r
-               error("function end without function start");\r
-            fnPos[fnRes] = {fnStart, start + asWordCount(start)};\r
-            fnStart = 0;\r
-         } else if (isConstOp(opCode)) {\r
-            assert(asId(start + 2) != spv::NoResult);
-            typeConstPos.insert(start);
-            typeConstPosR[asId(start + 2)] = start;
-         } else if (isTypeOp(opCode)) {
-            assert(asId(start + 1) != spv::NoResult);
-            typeConstPos.insert(start);
-            typeConstPosR[asId(start + 1)] = start;
-         }
-
-         return false;
-      },
-
-      [this](spv::Id& id) { localId(id, unmapped); }
-      );\r
-}\r
-\r
-// Validate the SPIR header\r
-void spirvbin_t::validate() const\r
-{\r
-   msg(2, 2, std::string("validating: "));\r
-\r
-   if (spv.size() < header_size)\r
-      error("file too short: ");\r
-\r
-   if (magic() != spv::MagicNumber)\r
-      error("bad magic number");\r
-\r
-   // field 1 = version\r
-   // field 2 = generator magic\r
-   // field 3 = result <id> bound\r
-\r
-   if (schemaNum() != 0)\r
-      error("bad schema, must be 0");\r
-}\r
-\r
-\r
-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;\r
-\r
-   if (nextInst > int(spv.size()))\r
-      error("spir instruction terminated too early");\r
-\r
-   // Base for computing number of operands; will be updated as more is learned\r
-   unsigned numOperands = wordCount - 1;\r
-\r
-   if (instFn(opCode, instructionStart))\r
-      return nextInst;\r
-\r
-   // Read type and result ID from instruction desc table
-   if (spv::InstructionDesc[opCode].hasType()) {
-      idFn(asId(word++));
-      --numOperands;
-   }
-
-   if (spv::InstructionDesc[opCode].hasResult()) {
-      idFn(asId(word++));
-      --numOperands;
-   }
-
-   // Extended instructions: currently, assume everything is an ID.
-   // TODO: add whatever data we need for exceptions to that
-   if (opCode == spv::OpExtInst) {
-      word        += 2; // instruction set, and instruction from set
-      numOperands -= 2;
-
-      for (unsigned op=0; op < numOperands; ++op)
-         idFn(asId(word++)); // ID
-
-      return nextInst;
-   }
-   
-   // Store IDs from instruction in our map
-   for (int op = 0; op < spv::InstructionDesc[opCode].operands.getNum(); ++op, --numOperands) {
-      switch (spv::InstructionDesc[opCode].operands.getClass(op)) {
-      case spv::OperandId:
-         idFn(asId(word++));
-         break;
-
-      case spv::OperandOptionalId:
-      case spv::OperandVariableIds:
-         for (unsigned i = 0; i < numOperands; ++i)
-            idFn(asId(word++));
-         return nextInst;
-
-      case spv::OperandVariableLiterals:
-         if (opCode == spv::OpDecorate && asDecoration(word - 1) == spv::DecorationBuiltIn) {
-            ++word;
-            --numOperands;
-         }
-         word += numOperands;
-         return nextInst;
-
-      case spv::OperandVariableLiteralId:
-         while (numOperands > 0) {
-            ++word;             // immediate
-            idFn(asId(word++)); // ID
-            numOperands -= 2;
-         }
-         return nextInst;
-
-      case spv::OperandLiteralString:
-         word += literalStringWords(literalString(word));
-         return nextInst;
-
-      // Single word operands we simply ignore, as they hold no IDs
-      case spv::OperandLiteralNumber:
-      case spv::OperandSource:
-      case spv::OperandExecutionModel:
-      case spv::OperandAddressing:
-      case spv::OperandMemory:
-      case spv::OperandExecutionMode:
-      case spv::OperandStorage:
-      case spv::OperandDimensionality:
-      case spv::OperandDecoration:
-      case spv::OperandBuiltIn:
-      case spv::OperandSelect:
-      case spv::OperandLoop:
-      case spv::OperandFunction:
-      case spv::OperandMemorySemantics:
-      case spv::OperandMemoryAccess:
-      case spv::OperandExecutionScope:
-      case spv::OperandGroupOperation:
-      case spv::OperandKernelEnqueueFlags:
-      case spv::OperandKernelProfilingInfo:
-         ++word;
-         break;
-         
-      default:
-         break;
-      }
-   }
-
-   return nextInst;
-}
-
-// 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)
-{
-   // 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);
-
-   // basic parsing and InstructionDesc table borrowed from SpvDisassemble.cpp...
-   int nextInst = int(spv.size());
-
-   for (int word = begin; word < end; word = nextInst)
-      nextInst = processInstruction(word, instFn, idFn);
-
-   return *this;
-}
-
-// Apply global name mapping to a single module
-void spirvbin_t::mapNames()
-{
-   static const std::uint32_t softTypeIdLimit = 3011;  // small prime.  TODO: get from options
-   static const std::uint32_t firstMappedID   = 3019;  // offset into ID space
-
-   for (const auto& name : nameMap) {
-      std::uint32_t hashval = 1911;
-      for (const char c : name.first)
-         hashval = hashval * 1009 + c;
-
-      if (isOldIdUnmapped(name.second))
-         localId(name.second, nextUnusedId(hashval % softTypeIdLimit + firstMappedID));
-   }
-}
-
-// Map fn contents to IDs of similar functions in other modules
-void spirvbin_t::mapFnBodies()
-{
-   static const std::uint32_t softTypeIdLimit = 19071;  // small prime.  TODO: get from options
-   static const std::uint32_t firstMappedID   =  6203;  // offset into ID space
-
-   // Initial approach: go through some high priority opcodes first and assign them
-   // hash values.
-
-   spv::Id          fnId       = spv::NoResult;
-   std::vector<int> instPos;
-   instPos.reserve(int(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; },
-      op_fn_nop);
-
-   // Window size for context-sensitive canonicalization values
-   // Emperical best size from a single data set.  TODO: Would be a good tunable.
-   // 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;
-   
-   for (int entry = 0; entry < int(instPos.size()); ++entry) {
-      const int     start  = instPos[entry];
-      const spv::Op opCode = asOpCode(start);
-
-      if (opCode == spv::OpFunction)
-         fnId   = asId(start + 2);
-
-      if (opCode == spv::OpFunctionEnd)
-         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 (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) {
-               if (asOpCode(instPos[i]) == spv::OpFunction)
-                  break;
-               hashval = hashval * 30103 + asOpCodeHash(instPos[i]); // 30103 = semiarbitrary prime
-            }
-
-            for (int i = entry; i <= entry + windowSize; ++i) {
-               if (asOpCode(instPos[i]) == spv::OpFunctionEnd)
-                  break;
-               hashval = hashval * 30103 + asOpCodeHash(instPos[i]); // 30103 = semiarbitrary prime
-            }
-
-            if (isOldIdUnmapped(resId))
-               localId(resId, nextUnusedId(hashval % softTypeIdLimit + firstMappedID));
-         }
-      }
-   }
-   
-   spv::Op          thisOpCode(spv::OpNop);
-   std::unordered_map<int, int> opCounter;
-   int              idCounter(0);
-   fnId = spv::NoResult;
-
-   process(
-      [&](spv::Op opCode, int start) {
-         switch (opCode) {
-            case spv::OpFunction:
-               // Reset counters at each function
-               idCounter = 0;
-               opCounter.clear();
-               fnId = asId(start + 2);
-               break;
-
-            case spv::OpTextureSample:
-            case spv::OpTextureSampleDref:
-            case spv::OpTextureSampleLod:
-            case spv::OpTextureSampleProj:
-            case spv::OpTextureSampleGrad:
-            case spv::OpTextureSampleOffset:
-            case spv::OpTextureSampleProjLod:
-            case spv::OpTextureSampleProjGrad:
-            case spv::OpTextureSampleLodOffset:
-            case spv::OpTextureSampleProjOffset:
-            case spv::OpTextureSampleGradOffset:                     
-            case spv::OpTextureSampleProjLodOffset:
-            case spv::OpTextureSampleProjGradOffset:
-            case spv::OpDot:
-            case spv::OpCompositeExtract:
-            case spv::OpCompositeInsert:
-            case spv::OpVectorShuffle:
-            case spv::OpLabel:
-            case spv::OpVariable:
-
-            case spv::OpAccessChain:
-            case spv::OpLoad:
-            case spv::OpStore:
-            case spv::OpCompositeConstruct:
-            case spv::OpFunctionCall:
-               ++opCounter[opCode];
-               idCounter = 0;
-               thisOpCode = opCode;
-               break;
-            default:
-               thisOpCode = spv::OpNop;
-         }
-                  
-         return false;
-      },
-
-      [&](spv::Id& id) {
-         if (thisOpCode != spv::OpNop) {
-            ++idCounter;
-            const std::uint32_t hashval = opCounter[thisOpCode] * thisOpCode * 50047 + idCounter + fnId * 117;
-
-            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<spv::Id, int> idFnCount;
-   std::unordered_map<spv::Id, int> 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()
-{
-   idset_t fnLocalVars; // set of function local vars
-   idmap_t idMap;       // Map of load result IDs to what they load
-
-   // EXPERIMENTAL: Forward input and access chain loads into consumptions
-   process(
-      [&](spv::Op opCode, int start) {
-         // Add inputs and uniforms to the map
-         if (((opCode == spv::OpVariable && asWordCount(start) == 4) || (opCode == spv::OpVariableArray)) &&
-             (spv[start+3] == spv::StorageClassUniform ||
-              spv[start+3] == spv::StorageClassUniformConstant ||
-              spv[start+3] == spv::StorageClassInput))
-            fnLocalVars.insert(asId(start+2));
-
-         if (opCode == spv::OpAccessChain && fnLocalVars.count(asId(start+3)) > 0)
-            fnLocalVars.insert(asId(start+2));
-
-         if (opCode == spv::OpLoad && fnLocalVars.count(asId(start+3)) > 0) {
-            idMap[asId(start+2)] = asId(start+3);
-            stripInst(start);
-         }
-         
-         return false;
-      },
-
-      [&](spv::Id& id) { if (idMap.find(id) != idMap.end()) id = idMap[id]; }
-      );
-
-   // EXPERIMENTAL: Implicit output stores
-   fnLocalVars.clear();
-   idMap.clear();
-   
-   process(
-      [&](spv::Op opCode, int start) {
-         // Add inputs and uniforms to the map
-         if (((opCode == spv::OpVariable && asWordCount(start) == 4) || (opCode == spv::OpVariableArray)) &&
-             (spv[start+3] == spv::StorageClassOutput))
-            fnLocalVars.insert(asId(start+2));
-
-         if (opCode == spv::OpStore && fnLocalVars.count(asId(start+1)) > 0) {
-            idMap[asId(start+2)] = asId(start+1);
-            stripInst(start);
-         }
-
-         return false;
-      },
-      op_fn_nop);
-      
-   process(
-      inst_fn_nop,
-      [&](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
-}
-
-// remove bodies of uncalled functions
-void spirvbin_t::optLoadStore()
-{
-   idset_t fnLocalVars;
-   // Map of load result IDs to what they load
-   idmap_t idMap;
-
-   // Find all the function local pointers stored at most once, and not via access chains
-   process(
-      [&](spv::Op opCode, int start) {
-         const int wordCount = asWordCount(start);
-          
-         // Add local variables to the map
-         if ((opCode == spv::OpVariable && spv[start+3] == spv::StorageClassFunction && asWordCount(start) == 4) ||
-             (opCode == spv::OpVariableArray && spv[start+3] == spv::StorageClassFunction))
-            fnLocalVars.insert(asId(start+2));
-
-         // Ignore process vars referenced via access chain
-         if ((opCode == spv::OpAccessChain || opCode == spv::OpInBoundsAccessChain) && fnLocalVars.count(asId(start+3)) > 0) {
-            fnLocalVars.erase(asId(start+3));
-            idMap.erase(asId(start+3));
-         }
-
-         if (opCode == spv::OpLoad && fnLocalVars.count(asId(start+3)) > 0) {
-            // Avoid loads before stores (TODO: why?  Crashes driver, but seems like it shouldn't).
-            if (idMap.find(asId(start+3)) == idMap.end()) {
-               fnLocalVars.erase(asId(start+3));
-               idMap.erase(asId(start+3));
-            }
-
-            // don't do for volatile references
-            if (wordCount > 4 && (spv[start+4] & spv::MemoryAccessVolatileMask)) {
-               fnLocalVars.erase(asId(start+3));
-               idMap.erase(asId(start+3));
-            }
-         }
-
-         if (opCode == spv::OpStore && fnLocalVars.count(asId(start+1)) > 0) {
-            if (idMap.find(asId(start+1)) == idMap.end()) {
-               idMap[asId(start+1)] = asId(start+2);
-            } else {
-               // Remove if it has more than one store to the same pointer
-               fnLocalVars.erase(asId(start+1));
-               idMap.erase(asId(start+1));
-            }
-
-            // don't do for volatile references
-            if (wordCount > 3 && (spv[start+3] & spv::MemoryAccessVolatileMask)) {
-               fnLocalVars.erase(asId(start+3));
-               idMap.erase(asId(start+3));
-            }
-         }
-
-         return true;
-      },
-      op_fn_nop);
-
-   process(
-      [&](spv::Op opCode, int 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) {
-         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;
-         }
-
-         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
-}
-
-// remove bodies of uncalled functions\r
-void spirvbin_t::dceFuncs()\r
-{\r
-   msg(3, 2, std::string("Removing Dead Functions: "));\r
-\r
-   // TODO: There are more efficient ways to do this.\r
-   bool changed = true;\r
-
-   while (changed) {
-      changed = false;
-
-      for (auto fn = fnPos.begin(); fn != fnPos.end(); ) {
-         if (fn->first == entryPoint) { // don't DCE away the entry point!
-            ++fn;
-            continue;
-         }
-         
-         const auto call_it = fnCalls.find(fn->first);\r
-         \r
-         if (call_it == fnCalls.end() || call_it->second == 0) {\r
-            changed = true;\r
-            stripRange.push_back(fn->second);\r
-            fnPosDCE.insert(*fn);\r
-
-            // decrease counts of called functions
-            process(
-               [&](spv::Op opCode, int start) {
-                  if (opCode == spv::Op::OpFunctionCall) {
-                     const auto call_it = fnCalls.find(asId(start + 3));
-                     if (call_it != fnCalls.end()) {
-                        if (--call_it->second <= 0)
-                           fnCalls.erase(call_it);
-                     }
-                  }
-
-                  return true;
-               },
-               op_fn_nop,
-               fn->second.first,
-               fn->second.second);
-
-            fn = fnPos.erase(fn);
-         } else ++fn;
-      }
-   }
-}
-
-// remove unused function variables + decorations\r
-void spirvbin_t::dceVars()\r
-{\r
-   msg(3, 2, std::string("DCE Vars: "));\r
-\r
-   std::unordered_map<spv::Id, int> varUseCount;\r
-\r
-   // Count function variable use
-   process(
-      [&](spv::Op opCode, int start) {
-         if (opCode == spv::OpVariable) { ++varUseCount[asId(start+2)]; return true; }
-         return false;
-      },
-
-      [&](spv::Id& id) { if (varUseCount[id]) ++varUseCount[id]; }
-      );
-
-   // Remove single-use function variables + associated decorations and names
-   process(
-      [&](spv::Op opCode, int 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)) {
-            stripInst(start);
-         }
-         return true;
-      },
-      op_fn_nop);
-}
-
-// remove unused types
-void spirvbin_t::dceTypes()
-{
-   std::vector<bool> isType(bound(), false);
-
-   // for speed, make O(1) way to get to type query (map is log(n))
-   for (const auto typeStart : typeConstPos)
-      isType[asTypeConstId(typeStart)] = true;
-
-   std::unordered_map<spv::Id, int> typeUseCount;
-
-   // Count total type usage
-   process(inst_fn_nop,
-           [&](spv::Id& id) { if (isType[id]) ++typeUseCount[id]; }
-          );
-
-   // Remove types from deleted code
-   for (const auto& fn : fnPosDCE)
-      process(inst_fn_nop,
-              [&](spv::Id& id) { if (isType[id]) --typeUseCount[id]; },
-              fn.second.first, fn.second.second);
-
-   // Remove single reference types
-   for (const auto typeStart : typeConstPos) {
-      const spv::Id typeId = asTypeConstId(typeStart);
-      if (typeUseCount[typeId] == 1) {
-         --typeUseCount[typeId];
-         stripInst(typeStart);
-      }
-   }
-}
-
-
-#ifdef NOTDEF
-bool spirvbin_t::matchType(const spirvbin_t::globaltypes_t& globalTypes, spv::Id lt, spv::Id gt) const
-{
-   // Find the local type id "lt" and global type id "gt"
-   const auto lt_it = typeConstPosR.find(lt);
-   if (lt_it == typeConstPosR.end())
-      return false;
-
-   const auto typeStart = lt_it->second;
-
-   // Search for entry in global table
-   const auto gtype = globalTypes.find(gt);
-   if (gtype == globalTypes.end())
-      return false;
-
-   const auto& gdata = gtype->second;
-
-   // local wordcount and opcode
-   const int     wordCount   = asWordCount(typeStart);
-   const spv::Op opCode      = asOpCode(typeStart);
-
-   // no type match if opcodes don't match, or operand count doesn't match
-   if (opCode != opOpCode(gdata[0]) || wordCount != opWordCount(gdata[0]))
-      return false;
-
-   const unsigned numOperands = wordCount - 2; // all types have a result
-
-   const auto cmpIdRange = [&](range_t range) {
-      for (int x=range.first; x<std::min(range.second, wordCount); ++x)
-         if (!matchType(globalTypes, asId(typeStart+x), gdata[x]))
-            return false;
-      return true;
-   };
-
-   const auto cmpConst   = [&]() { return cmpIdRange(constRange(opCode)); };
-   const auto cmpSubType = [&]() { return cmpIdRange(typeRange(opCode));  };
-
-   // Compare literals in range [start,end)
-   const auto cmpLiteral = [&]() {
-      const auto range = literalRange(opCode);
-      return std::equal(spir.begin() + typeStart + range.first,
-                        spir.begin() + typeStart + std::min(range.second, wordCount),
-                        gdata.begin() + range.first);
-   };
-
-   assert(isTypeOp(opCode) || isConstOp(opCode));
-
-   switch (opCode) {
-   case spv::OpTypeOpaque:       // TODO: disable until we compare the literal strings.
-   case spv::OpTypeQueue:        return false;
-   case spv::OpTypeEvent:        // fall through...
-   case spv::OpTypeDeviceEvent:  // ...
-   case spv::OpTypeReserveId:    return false;
-   // for samplers, we don't handle the optional parameters yet
-   case spv::OpTypeSampler:      return cmpLiteral() && cmpConst() && cmpSubType() && wordCount == 8;
-   default:                      return cmpLiteral() && cmpConst() && cmpSubType();
-   }
-}
-
-
-// Look for an equivalent type in the globalTypes map
-spv::Id spirvbin_t::findType(const spirvbin_t::globaltypes_t& globalTypes, spv::Id lt) const
-{
-   // Try a recursive type match on each in turn, and return a match if we find one
-   for (const auto& gt : globalTypes)
-      if (matchType(globalTypes, lt, gt.first))
-         return gt.first;
-
-   return spv::NoType;
-}
-#endif // NOTDEF
-
-// Return start position in SPV of given type.  error if not found.
-int spirvbin_t::typePos(spv::Id id) const
-{\r
-   const auto tid_it = typeConstPosR.find(id);\r
-   if (tid_it == typeConstPosR.end())\r
-      error("type ID not found");\r
-\r
-   return tid_it->second;\r
-}\r
-
-// 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
-{
-   const unsigned wordCount   = asWordCount(typeStart);
-   const spv::Op  opCode      = asOpCode(typeStart);
-
-   switch (opCode) {
-      case spv::OpTypeVoid:         return 0;
-      case spv::OpTypeBool:         return 1;
-      case spv::OpTypeInt:          return 3 + (spv[typeStart+3]);
-      case spv::OpTypeFloat:        return 5;
-      case spv::OpTypeVector:
-         return 6 + hashType(typePos(spv[typeStart+2])) * (spv[typeStart+3] - 1);
-      case spv::OpTypeMatrix:
-         return 30 + hashType(typePos(spv[typeStart+2])) * (spv[typeStart+3] - 1);
-      case spv::OpTypeSampler:
-         return 120 + hashType(typePos(spv[typeStart+2])) +
-            spv[typeStart+3] +            // dimensionality
-            spv[typeStart+4] * 8 * 16 +   // content
-            spv[typeStart+5] * 4 * 16 +   // arrayed
-            spv[typeStart+6] * 2 * 16 +   // compare
-            spv[typeStart+7] * 1 * 16;    // multisampled
-      case spv::OpTypeFilter:
-         return 500;
-      case spv::OpTypeArray:
-         return 501 + hashType(typePos(spv[typeStart+2])) * spv[typeStart+3];
-      case spv::OpTypeRuntimeArray:
-         return 5000  + hashType(typePos(spv[typeStart+2]));
-      case spv::OpTypeStruct:
-         {
-            std::uint32_t hash = 10000;
-            for (unsigned w=2; w < wordCount; ++w)
-               hash += w * hashType(typePos(spv[typeStart+w]));
-            return hash;
-         }
-         
-      case spv::OpTypeOpaque:         return 6000 + spv[typeStart+2];
-      case spv::OpTypePointer:        return 100000  + hashType(typePos(spv[typeStart+3]));
-      case spv::OpTypeFunction:
-         {
-            std::uint32_t hash = 200000;
-            for (unsigned w=2; w < wordCount; ++w)
-               hash += w * hashType(typePos(spv[typeStart+w]));
-            return hash;
-         }
-
-      case spv::OpTypeEvent:           return 300000;
-      case spv::OpTypeDeviceEvent:     return 300001;
-      case spv::OpTypeReserveId:       return 300002;
-      case spv::OpTypeQueue:           return 300003;
-      case spv::OpTypePipe:            return 300004;
-
-      case spv::OpConstantNullObject:  return 300005;
-      case spv::OpConstantSampler:     return 300006;
-
-      case spv::OpConstantTrue:        return 300007;
-      case spv::OpConstantFalse:       return 300008;
-      case spv::OpConstantNullPointer: return 300009;
-      case spv::OpConstantComposite:
-         {
-            std::uint32_t hash = 300011 + hashType(typePos(spv[typeStart+1]));
-            for (unsigned w=3; w < wordCount; ++w)
-               hash += w * hashType(typePos(spv[typeStart+w]));
-            return hash;
-         }
-      case spv::OpConstant:
-         {
-            std::uint32_t hash = 400011 + hashType(typePos(spv[typeStart+1]));
-            for (unsigned w=3; w < wordCount; ++w)
-               hash += w * spv[typeStart+w];
-            return hash;
-         }\r
-\r
-      default:\r
-         error("unknown type opcode");\r
-         return 0;\r
-   }\r
-}\r
-
-void spirvbin_t::mapTypeConst()
-{\r
-   globaltypes_t globalTypeMap;\r
-\r
-   msg(3, 2, std::string("Remapping Consts & Types: "));\r
-\r
-   static const std::uint32_t softTypeIdLimit = 3011; // small prime.  TODO: get from options\r
-   static const std::uint32_t firstMappedID   = 8;    // offset into ID space\r
-   
-   for (auto& typeStart : typeConstPos) {
-      const spv::Id       resId     = asTypeConstId(typeStart);
-      const std::uint32_t hashval   = hashType(typeStart);
-
-      if (isOldIdUnmapped(resId))
-         localId(resId, nextUnusedId(hashval % softTypeIdLimit + firstMappedID));
-   }
-}
-
-
-// Strip a single binary by removing ranges given in stripRange
-void spirvbin_t::strip()
-{
-   if (stripRange.empty()) // nothing to do
-      return;
-
-   // Sort strip ranges in order of traversal
-   std::sort(stripRange.begin(), stripRange.end());
-
-   // 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();
-
-   int strippedPos = 0;
-   for (unsigned word = 0; word < unsigned(spv.size()); ++word) {
-      if (strip_it != stripRange.end() && word >= strip_it->second)
-         ++strip_it;
-
-      if (strip_it == stripRange.end() || word < strip_it->first || word >= strip_it->second)
-         spv[strippedPos++] = spv[word];
-   }
-
-   spv.resize(strippedPos);
-   stripRange.clear();
-}
-
-// Strip a single binary by removing ranges given in stripRange
-void spirvbin_t::remap(std::uint32_t opts)
-{
-   options = opts;
-
-   validate();  // validate header
-   buildLocalMaps();
-
-   msg(3, 4, std::string("ID bound: ") + std::to_string(bound()));
-
-   if (options & OPT_LOADSTORE) optLoadStore();
-   if (options & OPT_FWD_LS)    forwardLoadStores();
-   if (options & DCE_FUNCS)     dceFuncs();
-   if (options & DCE_VARS)      dceVars();
-   if (options & DCE_TYPES)     dceTypes();
-   if (options & MAP_TYPES)     mapTypeConst();
-   if (options & MAP_NAMES)     mapNames();
-   if (options & MAP_FUNCS)     mapFnBodies();
-   // if (options & STRIP)         stripDebug();
-   
-   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
-void spirvbin_t::remap(std::vector<std::uint32_t>& in_spv, std::uint32_t opts)
-{
-   spv.swap(in_spv);
-   remap(opts);
-   spv.swap(in_spv);\r
-}\r
+            if (isNewIdMapped(newId))\r
+                error(std::string("ID already used in module: ") + std::to_string(newId));\r
+\r
+            msg(4, 4, std::string("map: ") + std::to_string(id) + " -> " + std::to_string(newId));\r
+            setMapped(newId);\r
+            largestNewId = std::max(largestNewId, newId);\r
+        }\r
+\r
+        return idMapL[id] = newId;\r
+    }\r
+\r
+    // Parse a literal string from the SPIR binary and return it as an std::string\r
+    // Due to C++11 RValue references, this doesn't copy the result string.\r
+    std::string spirvbin_t::literalString(int word) const\r
+    {\r
+        std::string literal;\r
+\r
+        literal.reserve(16);\r
+\r
+        const char* bytes = reinterpret_cast<const char*>(spv.data() + word);\r
+\r
+        while (bytes && *bytes)\r
+            literal += *bytes++;\r
+\r
+        return literal;\r
+    }\r
+\r
+\r
+    void spirvbin_t::applyMap()\r
+    {\r
+        msg(3, 2, std::string("Applying map: "));\r
+\r
+        // Map local IDs through the ID map\r
+        process(inst_fn_nop, // ignore instructions\r
+            [this](spv::Id& id) {\r
+                id = localId(id);\r
+                assert(id != unused && id != unmapped);\r
+        }\r
+        );\r
+    }\r
+\r
+\r
+    // Find free IDs for anything we haven't mapped\r
+    void spirvbin_t::mapRemainder()\r
+    {\r
+        msg(3, 2, std::string("Remapping remainder: "));\r
+\r
+        spv::Id     unusedId  = 1;  // can't use 0: that's NoResult\r
+        spirword_t  maxBound  = 0;\r
+\r
+        for (spv::Id id = 0; id < idMapL.size(); ++id) {\r
+            if (isOldIdUnused(id))\r
+                continue;\r
+\r
+            // Find a new mapping for any used but unmapped IDs\r
+            if (isOldIdUnmapped(id))\r
+                localId(id, unusedId = nextUnusedId(unusedId));\r
+\r
+            if (isOldIdUnmapped(id))\r
+                error(std::string("old ID not mapped: ") + std::to_string(id));\r
+\r
+            // Track max bound\r
+            maxBound = std::max(maxBound, localId(id) + 1);\r
+        }\r
+\r
+        bound(maxBound); // reset header ID bound to as big as it now needs to be\r
+    }\r
+\r
+    void spirvbin_t::stripDebug()\r
+    {\r
+        if ((options & Options::STRIP) == 0)\r
+            return;\r
+\r
+        // build local Id and name maps\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                // remember opcodes we want to strip later\r
+                if (isStripOp(opCode))\r
+                    stripInst(start);\r
+                return true;\r
+        },\r
+            op_fn_nop);\r
+    }\r
+\r
+    void spirvbin_t::buildLocalMaps()\r
+    {\r
+        msg(2, 2, std::string("build local maps: "));\r
+\r
+        mapped.clear();\r
+        idMapL.clear();\r
+        nameMap.clear();\r
+        fnPos.clear();\r
+        fnPosDCE.clear();\r
+        fnCalls.clear();\r
+        typeConstPos.clear();\r
+        typeConstPosR.clear();\r
+        entryPoint = spv::NoResult;\r
+        largestNewId = 0;\r
+\r
+        idMapL.resize(bound(), unused);\r
+\r
+        int         fnStart = 0;\r
+        spv::Id     fnRes   = spv::NoResult;\r
+\r
+        // build local Id and name maps\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                // remember opcodes we want to strip later\r
+                if ((options & Options::STRIP) && isStripOp(opCode))\r
+                    stripInst(start);\r
+\r
+                if (opCode == spv::Op::OpName) {\r
+                    const spv::Id    target = asId(start+1);\r
+                    const std::string  name   = literalString(start+2);\r
+                    nameMap[name] = target;\r
+                    return true;\r
+                } else if (opCode == spv::Op::OpFunctionCall) {\r
+                    ++fnCalls[asId(start + 3)];\r
+                } else if (opCode == spv::Op::OpEntryPoint) {\r
+                    entryPoint = asId(start + 2);\r
+                } else if (opCode == spv::Op::OpFunction) {\r
+                    if (fnStart != 0)\r
+                        error("nested function found");\r
+                    fnStart = start;\r
+                    fnRes   = asId(start + 2);\r
+                } else if (opCode == spv::Op::OpFunctionEnd) {\r
+                    assert(fnRes != spv::NoResult);\r
+                    if (fnStart == 0)\r
+                        error("function end without function start");\r
+                    fnPos[fnRes] = {fnStart, start + asWordCount(start)};\r
+                    fnStart = 0;\r
+                } else if (isConstOp(opCode)) {\r
+                    assert(asId(start + 2) != spv::NoResult);\r
+                    typeConstPos.insert(start);\r
+                    typeConstPosR[asId(start + 2)] = start;\r
+                } else if (isTypeOp(opCode)) {\r
+                    assert(asId(start + 1) != spv::NoResult);\r
+                    typeConstPos.insert(start);\r
+                    typeConstPosR[asId(start + 1)] = start;\r
+                }\r
+\r
+                return false;\r
+        },\r
+\r
+            [this](spv::Id& id) { localId(id, unmapped); }\r
+        );\r
+    }\r
+\r
+    // Validate the SPIR header\r
+    void spirvbin_t::validate() const\r
+    {\r
+        msg(2, 2, std::string("validating: "));\r
+\r
+        if (spv.size() < header_size)\r
+            error("file too short: ");\r
+\r
+        if (magic() != spv::MagicNumber)\r
+            error("bad magic number");\r
+\r
+        // field 1 = version\r
+        // field 2 = generator magic\r
+        // field 3 = result <id> bound\r
+\r
+        if (schemaNum() != 0)\r
+            error("bad schema, must be 0");\r
+    }\r
+\r
+\r
+    int spirvbin_t::processInstruction(int word, instfn_t instFn, idfn_t idFn)\r
+    {\r
+        const auto     instructionStart = word;\r
+        const unsigned wordCount = asWordCount(instructionStart);\r
+        const spv::Op  opCode    = asOpCode(instructionStart);\r
+        const int      nextInst  = word++ + wordCount;\r
+\r
+        if (nextInst > int(spv.size()))\r
+            error("spir instruction terminated too early");\r
+\r
+        // Base for computing number of operands; will be updated as more is learned\r
+        unsigned numOperands = wordCount - 1;\r
+\r
+        if (instFn(opCode, instructionStart))\r
+            return nextInst;\r
+\r
+        // Read type and result ID from instruction desc table\r
+        if (spv::InstructionDesc[opCode].hasType()) {\r
+            idFn(asId(word++));\r
+            --numOperands;\r
+        }\r
+\r
+        if (spv::InstructionDesc[opCode].hasResult()) {\r
+            idFn(asId(word++));\r
+            --numOperands;\r
+        }\r
+\r
+        // Extended instructions: currently, assume everything is an ID.\r
+        // TODO: add whatever data we need for exceptions to that\r
+        if (opCode == spv::OpExtInst) {\r
+            word        += 2; // instruction set, and instruction from set\r
+            numOperands -= 2;\r
+\r
+            for (unsigned op=0; op < numOperands; ++op)\r
+                idFn(asId(word++)); // ID\r
+\r
+            return nextInst;\r
+        }\r
+\r
+        // Store IDs from instruction in our map\r
+        for (int op = 0; op < spv::InstructionDesc[opCode].operands.getNum(); ++op, --numOperands) {\r
+            switch (spv::InstructionDesc[opCode].operands.getClass(op)) {\r
+            case spv::OperandId:\r
+                idFn(asId(word++));\r
+                break;\r
+\r
+            case spv::OperandOptionalId:\r
+            case spv::OperandVariableIds:\r
+                for (unsigned i = 0; i < numOperands; ++i)\r
+                    idFn(asId(word++));\r
+                return nextInst;\r
+\r
+            case spv::OperandVariableLiterals:\r
+                if (opCode == spv::OpDecorate && asDecoration(word - 1) == spv::DecorationBuiltIn) {\r
+                    ++word;\r
+                    --numOperands;\r
+                }\r
+                word += numOperands;\r
+                return nextInst;\r
+\r
+            case spv::OperandVariableLiteralId:\r
+                while (numOperands > 0) {\r
+                    ++word;             // immediate\r
+                    idFn(asId(word++)); // ID\r
+                    numOperands -= 2;\r
+                }\r
+                return nextInst;\r
+\r
+            case spv::OperandLiteralString:\r
+                word += literalStringWords(literalString(word));\r
+                return nextInst;\r
+\r
+                // Single word operands we simply ignore, as they hold no IDs\r
+            case spv::OperandLiteralNumber:\r
+            case spv::OperandSource:\r
+            case spv::OperandExecutionModel:\r
+            case spv::OperandAddressing:\r
+            case spv::OperandMemory:\r
+            case spv::OperandExecutionMode:\r
+            case spv::OperandStorage:\r
+            case spv::OperandDimensionality:\r
+            case spv::OperandDecoration:\r
+            case spv::OperandBuiltIn:\r
+            case spv::OperandSelect:\r
+            case spv::OperandLoop:\r
+            case spv::OperandFunction:\r
+            case spv::OperandMemorySemantics:\r
+            case spv::OperandMemoryAccess:\r
+            case spv::OperandExecutionScope:\r
+            case spv::OperandGroupOperation:\r
+            case spv::OperandKernelEnqueueFlags:\r
+            case spv::OperandKernelProfilingInfo:\r
+                ++word;\r
+                break;\r
+\r
+            default:\r
+                break;\r
+            }\r
+        }\r
+\r
+        return nextInst;\r
+    }\r
+\r
+    // Make a pass over all the instructions and process them given appropriate functions\r
+    spirvbin_t& spirvbin_t::process(instfn_t instFn, idfn_t idFn, int begin, int end)\r
+    {\r
+        // For efficiency, reserve name map space.  It can grow if needed.\r
+        nameMap.reserve(32);\r
+\r
+        // If begin or end == 0, use defaults\r
+        begin = (begin == 0 ? header_size      : begin);\r
+        end   = (end   == 0 ? int(spv.size()) : end);\r
+\r
+        // basic parsing and InstructionDesc table borrowed from SpvDisassemble.cpp...\r
+        int nextInst = int(spv.size());\r
+\r
+        for (int word = begin; word < end; word = nextInst)\r
+            nextInst = processInstruction(word, instFn, idFn);\r
+\r
+        return *this;\r
+    }\r
+\r
+    // Apply global name mapping to a single module\r
+    void spirvbin_t::mapNames()\r
+    {\r
+        static const std::uint32_t softTypeIdLimit = 3011;  // small prime.  TODO: get from options\r
+        static const std::uint32_t firstMappedID   = 3019;  // offset into ID space\r
+\r
+        for (const auto& name : nameMap) {\r
+            std::uint32_t hashval = 1911;\r
+            for (const char c : name.first)\r
+                hashval = hashval * 1009 + c;\r
+\r
+            if (isOldIdUnmapped(name.second))\r
+                localId(name.second, nextUnusedId(hashval % softTypeIdLimit + firstMappedID));\r
+        }\r
+    }\r
+\r
+    // Map fn contents to IDs of similar functions in other modules\r
+    void spirvbin_t::mapFnBodies()\r
+    {\r
+        static const std::uint32_t softTypeIdLimit = 19071;  // small prime.  TODO: get from options\r
+        static const std::uint32_t firstMappedID   =  6203;  // offset into ID space\r
+\r
+        // Initial approach: go through some high priority opcodes first and assign them\r
+        // hash values.\r
+\r
+        spv::Id          fnId       = spv::NoResult;\r
+        std::vector<int> instPos;\r
+        instPos.reserve(int(spv.size()) / 16); // initial estimate; can grow if needed.\r
+\r
+        // Build local table of instruction start positions\r
+        process(\r
+            [&](spv::Op, int start) { instPos.push_back(start); return true; },\r
+            op_fn_nop);\r
+\r
+        // Window size for context-sensitive canonicalization values\r
+        // Emperical best size from a single data set.  TODO: Would be a good tunable.\r
+        // We essentially performa a little convolution around each instruction,\r
+        // to capture the flavor of nearby code, to hopefully match to similar\r
+        // code in other modules.\r
+        static const int windowSize = 2;\r
+\r
+        for (int entry = 0; entry < int(instPos.size()); ++entry) {\r
+            const int     start  = instPos[entry];\r
+            const spv::Op opCode = asOpCode(start);\r
+\r
+            if (opCode == spv::OpFunction)\r
+                fnId   = asId(start + 2);\r
+\r
+            if (opCode == spv::OpFunctionEnd)\r
+                fnId = spv::NoResult;\r
+\r
+            if (fnId != spv::NoResult) { // if inside a function\r
+                const int word   = start + (spv::InstructionDesc[opCode].hasType() ? 2 : 1);\r
+                const int result = spv::InstructionDesc[opCode].hasResult() ? word : -1;\r
+\r
+                if (result > 0) {\r
+                    const spv::Id resId = asId(result);\r
+                    std::uint32_t hashval = fnId * 17; // small prime\r
+\r
+                    for (int i = entry-1; i >= entry-windowSize; --i) {\r
+                        if (asOpCode(instPos[i]) == spv::OpFunction)\r
+                            break;\r
+                        hashval = hashval * 30103 + asOpCodeHash(instPos[i]); // 30103 = semiarbitrary prime\r
+                    }\r
+\r
+                    for (int i = entry; i <= entry + windowSize; ++i) {\r
+                        if (asOpCode(instPos[i]) == spv::OpFunctionEnd)\r
+                            break;\r
+                        hashval = hashval * 30103 + asOpCodeHash(instPos[i]); // 30103 = semiarbitrary prime\r
+                    }\r
+\r
+                    if (isOldIdUnmapped(resId))\r
+                        localId(resId, nextUnusedId(hashval % softTypeIdLimit + firstMappedID));\r
+                }\r
+            }\r
+        }\r
+\r
+        spv::Op          thisOpCode(spv::OpNop);\r
+        std::unordered_map<int, int> opCounter;\r
+        int              idCounter(0);\r
+        fnId = spv::NoResult;\r
+\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                switch (opCode) {\r
+                case spv::OpFunction:\r
+                    // Reset counters at each function\r
+                    idCounter = 0;\r
+                    opCounter.clear();\r
+                    fnId = asId(start + 2);\r
+                    break;\r
+\r
+                case spv::OpTextureSample:\r
+                case spv::OpTextureSampleDref:\r
+                case spv::OpTextureSampleLod:\r
+                case spv::OpTextureSampleProj:\r
+                case spv::OpTextureSampleGrad:\r
+                case spv::OpTextureSampleOffset:\r
+                case spv::OpTextureSampleProjLod:\r
+                case spv::OpTextureSampleProjGrad:\r
+                case spv::OpTextureSampleLodOffset:\r
+                case spv::OpTextureSampleProjOffset:\r
+                case spv::OpTextureSampleGradOffset:                     \r
+                case spv::OpTextureSampleProjLodOffset:\r
+                case spv::OpTextureSampleProjGradOffset:\r
+                case spv::OpDot:\r
+                case spv::OpCompositeExtract:\r
+                case spv::OpCompositeInsert:\r
+                case spv::OpVectorShuffle:\r
+                case spv::OpLabel:\r
+                case spv::OpVariable:\r
+\r
+                case spv::OpAccessChain:\r
+                case spv::OpLoad:\r
+                case spv::OpStore:\r
+                case spv::OpCompositeConstruct:\r
+                case spv::OpFunctionCall:\r
+                    ++opCounter[opCode];\r
+                    idCounter = 0;\r
+                    thisOpCode = opCode;\r
+                    break;\r
+                default:\r
+                    thisOpCode = spv::OpNop;\r
+                }\r
+\r
+                return false;\r
+        },\r
+\r
+            [&](spv::Id& id) {\r
+                if (thisOpCode != spv::OpNop) {\r
+                    ++idCounter;\r
+                    const std::uint32_t hashval = opCounter[thisOpCode] * thisOpCode * 50047 + idCounter + fnId * 117;\r
+\r
+                    if (isOldIdUnmapped(id))\r
+                        localId(id, nextUnusedId(hashval % softTypeIdLimit + firstMappedID));\r
+                }\r
+        });\r
+    }\r
+\r
+#ifdef NOTDEF\r
+    // remove bodies of uncalled functions\r
+    void spirvbin_t::offsetIds()\r
+    {\r
+        // Count of how many functions each ID appears within\r
+        std::unordered_map<spv::Id, int> idFnCount;\r
+        std::unordered_map<spv::Id, int> idDefinedLoc;\r
+        idset_t                          idsUsed;  // IDs used in a given function\r
+\r
+        int instCount = 0;\r
+\r
+        // create a count of how many functions each ID is used within\r
+        process(\r
+            [&](spv::OpCode opCode, int start) {\r
+                ++instCount;\r
+\r
+                switch (opCode) {\r
+                case spv::OpFunction:\r
+                    for (const auto id : idsUsed)\r
+                        ++idFnCount[id];\r
+                    idsUsed.clear();\r
+                    break;\r
+\r
+                default:\r
+                    {\r
+                        const int word   = start + (spv::InstructionDesc[opCode].hasType() ? 2 : 1);\r
+                        const int result = spv::InstructionDesc[opCode].hasResult() ? word : -1;\r
+\r
+                        if (result > 0)\r
+                            idDefinedLoc[asId(result)] = instCount;\r
+                    }\r
+                    break;\r
+                }\r
+\r
+                return false;\r
+        },\r
+\r
+            [&](spv::Id& id) { idsUsed.insert(id); });\r
+\r
+        // For each ID defined in exactly one function, replace uses by\r
+        // negative offset to definitions in instructions.\r
+\r
+        static const int relOffsetLimit = 64;\r
+\r
+        instCount = 0;\r
+        process([&](spv::OpCode, int) { ++instCount; return false; },\r
+            [&](spv::Id& id) {\r
+                if (idFnCount[id] == 1 && (instCount - idDefinedLoc[id]) < relOffsetLimit)\r
+                    id = idDefinedLoc[id] - instCount;\r
+        });\r
+    }\r
+#endif\r
+\r
+\r
+    // EXPERIMENTAL: forward IO and uniform load/stores into operands\r
+    // This produces invalid Schema-0 SPIRV\r
+    void spirvbin_t::forwardLoadStores()\r
+    {\r
+        idset_t fnLocalVars; // set of function local vars\r
+        idmap_t idMap;       // Map of load result IDs to what they load\r
+\r
+        // EXPERIMENTAL: Forward input and access chain loads into consumptions\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                // Add inputs and uniforms to the map\r
+                if (((opCode == spv::OpVariable && asWordCount(start) == 4) || (opCode == spv::OpVariableArray)) &&\r
+                    (spv[start+3] == spv::StorageClassUniform ||\r
+                    spv[start+3] == spv::StorageClassUniformConstant ||\r
+                    spv[start+3] == spv::StorageClassInput))\r
+                    fnLocalVars.insert(asId(start+2));\r
+\r
+                if (opCode == spv::OpAccessChain && fnLocalVars.count(asId(start+3)) > 0)\r
+                    fnLocalVars.insert(asId(start+2));\r
+\r
+                if (opCode == spv::OpLoad && fnLocalVars.count(asId(start+3)) > 0) {\r
+                    idMap[asId(start+2)] = asId(start+3);\r
+                    stripInst(start);\r
+                }\r
+\r
+                return false;\r
+        },\r
+\r
+            [&](spv::Id& id) { if (idMap.find(id) != idMap.end()) id = idMap[id]; }\r
+        );\r
+\r
+        // EXPERIMENTAL: Implicit output stores\r
+        fnLocalVars.clear();\r
+        idMap.clear();\r
+\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                // Add inputs and uniforms to the map\r
+                if (((opCode == spv::OpVariable && asWordCount(start) == 4) || (opCode == spv::OpVariableArray)) &&\r
+                    (spv[start+3] == spv::StorageClassOutput))\r
+                    fnLocalVars.insert(asId(start+2));\r
+\r
+                if (opCode == spv::OpStore && fnLocalVars.count(asId(start+1)) > 0) {\r
+                    idMap[asId(start+2)] = asId(start+1);\r
+                    stripInst(start);\r
+                }\r
+\r
+                return false;\r
+        },\r
+            op_fn_nop);\r
+\r
+        process(\r
+            inst_fn_nop,\r
+            [&](spv::Id& id) { if (idMap.find(id) != idMap.end()) id = idMap[id]; }\r
+        );\r
+\r
+        strip();          // strip out data we decided to eliminate\r
+        buildLocalMaps(); // rebuild ID mapping data\r
+    }\r
+\r
+    // remove bodies of uncalled functions\r
+    void spirvbin_t::optLoadStore()\r
+    {\r
+        idset_t fnLocalVars;\r
+        // Map of load result IDs to what they load\r
+        idmap_t idMap;\r
+\r
+        // Find all the function local pointers stored at most once, and not via access chains\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                const int wordCount = asWordCount(start);\r
+\r
+                // Add local variables to the map\r
+                if ((opCode == spv::OpVariable && spv[start+3] == spv::StorageClassFunction && asWordCount(start) == 4) ||\r
+                    (opCode == spv::OpVariableArray && spv[start+3] == spv::StorageClassFunction))\r
+                    fnLocalVars.insert(asId(start+2));\r
+\r
+                // Ignore process vars referenced via access chain\r
+                if ((opCode == spv::OpAccessChain || opCode == spv::OpInBoundsAccessChain) && fnLocalVars.count(asId(start+3)) > 0) {\r
+                    fnLocalVars.erase(asId(start+3));\r
+                    idMap.erase(asId(start+3));\r
+                }\r
+\r
+                if (opCode == spv::OpLoad && fnLocalVars.count(asId(start+3)) > 0) {\r
+                    // Avoid loads before stores (TODO: why?  Crashes driver, but seems like it shouldn't).\r
+                    if (idMap.find(asId(start+3)) == idMap.end()) {\r
+                        fnLocalVars.erase(asId(start+3));\r
+                        idMap.erase(asId(start+3));\r
+                    }\r
+\r
+                    // don't do for volatile references\r
+                    if (wordCount > 4 && (spv[start+4] & spv::MemoryAccessVolatileMask)) {\r
+                        fnLocalVars.erase(asId(start+3));\r
+                        idMap.erase(asId(start+3));\r
+                    }\r
+                }\r
+\r
+                if (opCode == spv::OpStore && fnLocalVars.count(asId(start+1)) > 0) {\r
+                    if (idMap.find(asId(start+1)) == idMap.end()) {\r
+                        idMap[asId(start+1)] = asId(start+2);\r
+                    } else {\r
+                        // Remove if it has more than one store to the same pointer\r
+                        fnLocalVars.erase(asId(start+1));\r
+                        idMap.erase(asId(start+1));\r
+                    }\r
+\r
+                    // don't do for volatile references\r
+                    if (wordCount > 3 && (spv[start+3] & spv::MemoryAccessVolatileMask)) {\r
+                        fnLocalVars.erase(asId(start+3));\r
+                        idMap.erase(asId(start+3));\r
+                    }\r
+                }\r
+\r
+                return true;\r
+        },\r
+            op_fn_nop);\r
+\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                if (opCode == spv::OpLoad && fnLocalVars.count(asId(start+3)) > 0)\r
+                    idMap[asId(start+2)] = idMap[asId(start+3)];\r
+                return false;\r
+        },\r
+            op_fn_nop);\r
+\r
+        // Remove the load/store/variables for the ones we've discovered\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                if ((opCode == spv::OpLoad  && fnLocalVars.count(asId(start+3)) > 0) ||\r
+                    (opCode == spv::OpStore && fnLocalVars.count(asId(start+1)) > 0) ||\r
+                    (opCode == spv::OpVariable && fnLocalVars.count(asId(start+2)) > 0)) {\r
+                        stripInst(start);\r
+                        return true;\r
+                }\r
+\r
+                return false;\r
+        },\r
+\r
+            [&](spv::Id& id) { if (idMap.find(id) != idMap.end()) id = idMap[id]; }\r
+        );\r
+\r
+\r
+        strip();          // strip out data we decided to eliminate\r
+        buildLocalMaps(); // rebuild ID mapping data\r
+    }\r
+\r
+    // remove bodies of uncalled functions\r
+    void spirvbin_t::dceFuncs()\r
+    {\r
+        msg(3, 2, std::string("Removing Dead Functions: "));\r
+\r
+        // TODO: There are more efficient ways to do this.\r
+        bool changed = true;\r
+\r
+        while (changed) {\r
+            changed = false;\r
+\r
+            for (auto fn = fnPos.begin(); fn != fnPos.end(); ) {\r
+                if (fn->first == entryPoint) { // don't DCE away the entry point!\r
+                    ++fn;\r
+                    continue;\r
+                }\r
+\r
+                const auto call_it = fnCalls.find(fn->first);\r
+\r
+                if (call_it == fnCalls.end() || call_it->second == 0) {\r
+                    changed = true;\r
+                    stripRange.push_back(fn->second);\r
+                    fnPosDCE.insert(*fn);\r
+\r
+                    // decrease counts of called functions\r
+                    process(\r
+                        [&](spv::Op opCode, int start) {\r
+                            if (opCode == spv::Op::OpFunctionCall) {\r
+                                const auto call_it = fnCalls.find(asId(start + 3));\r
+                                if (call_it != fnCalls.end()) {\r
+                                    if (--call_it->second <= 0)\r
+                                        fnCalls.erase(call_it);\r
+                                }\r
+                            }\r
+\r
+                            return true;\r
+                    },\r
+                        op_fn_nop,\r
+                        fn->second.first,\r
+                        fn->second.second);\r
+\r
+                    fn = fnPos.erase(fn);\r
+                } else ++fn;\r
+            }\r
+        }\r
+    }\r
+\r
+    // remove unused function variables + decorations\r
+    void spirvbin_t::dceVars()\r
+    {\r
+        msg(3, 2, std::string("DCE Vars: "));\r
+\r
+        std::unordered_map<spv::Id, int> varUseCount;\r
+\r
+        // Count function variable use\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                if (opCode == spv::OpVariable) { ++varUseCount[asId(start+2)]; return true; }\r
+                return false;\r
+        },\r
+\r
+            [&](spv::Id& id) { if (varUseCount[id]) ++varUseCount[id]; }\r
+        );\r
+\r
+        // Remove single-use function variables + associated decorations and names\r
+        process(\r
+            [&](spv::Op opCode, int start) {\r
+                if ((opCode == spv::OpVariable && varUseCount[asId(start+2)] == 1)  ||\r
+                    (opCode == spv::OpDecorate && varUseCount[asId(start+1)] == 1)  ||\r
+                    (opCode == spv::OpName     && varUseCount[asId(start+1)] == 1)) {\r
+                        stripInst(start);\r
+                }\r
+                return true;\r
+        },\r
+            op_fn_nop);\r
+    }\r
+\r
+    // remove unused types\r
+    void spirvbin_t::dceTypes()\r
+    {\r
+        std::vector<bool> isType(bound(), false);\r
+\r
+        // for speed, make O(1) way to get to type query (map is log(n))\r
+        for (const auto typeStart : typeConstPos)\r
+            isType[asTypeConstId(typeStart)] = true;\r
+\r
+        std::unordered_map<spv::Id, int> typeUseCount;\r
+\r
+        // Count total type usage\r
+        process(inst_fn_nop,\r
+            [&](spv::Id& id) { if (isType[id]) ++typeUseCount[id]; }\r
+        );\r
+\r
+        // Remove types from deleted code\r
+        for (const auto& fn : fnPosDCE)\r
+            process(inst_fn_nop,\r
+            [&](spv::Id& id) { if (isType[id]) --typeUseCount[id]; },\r
+            fn.second.first, fn.second.second);\r
+\r
+        // Remove single reference types\r
+        for (const auto typeStart : typeConstPos) {\r
+            const spv::Id typeId = asTypeConstId(typeStart);\r
+            if (typeUseCount[typeId] == 1) {\r
+                --typeUseCount[typeId];\r
+                stripInst(typeStart);\r
+            }\r
+        }\r
+    }\r
+\r
+\r
+#ifdef NOTDEF\r
+    bool spirvbin_t::matchType(const spirvbin_t::globaltypes_t& globalTypes, spv::Id lt, spv::Id gt) const\r
+    {\r
+        // Find the local type id "lt" and global type id "gt"\r
+        const auto lt_it = typeConstPosR.find(lt);\r
+        if (lt_it == typeConstPosR.end())\r
+            return false;\r
+\r
+        const auto typeStart = lt_it->second;\r
+\r
+        // Search for entry in global table\r
+        const auto gtype = globalTypes.find(gt);\r
+        if (gtype == globalTypes.end())\r
+            return false;\r
+\r
+        const auto& gdata = gtype->second;\r
+\r
+        // local wordcount and opcode\r
+        const int     wordCount   = asWordCount(typeStart);\r
+        const spv::Op opCode      = asOpCode(typeStart);\r
+\r
+        // no type match if opcodes don't match, or operand count doesn't match\r
+        if (opCode != opOpCode(gdata[0]) || wordCount != opWordCount(gdata[0]))\r
+            return false;\r
+\r
+        const unsigned numOperands = wordCount - 2; // all types have a result\r
+\r
+        const auto cmpIdRange = [&](range_t range) {\r
+            for (int x=range.first; x<std::min(range.second, wordCount); ++x)\r
+                if (!matchType(globalTypes, asId(typeStart+x), gdata[x]))\r
+                    return false;\r
+            return true;\r
+        };\r
+\r
+        const auto cmpConst   = [&]() { return cmpIdRange(constRange(opCode)); };\r
+        const auto cmpSubType = [&]() { return cmpIdRange(typeRange(opCode));  };\r
+\r
+        // Compare literals in range [start,end)\r
+        const auto cmpLiteral = [&]() {\r
+            const auto range = literalRange(opCode);\r
+            return std::equal(spir.begin() + typeStart + range.first,\r
+                spir.begin() + typeStart + std::min(range.second, wordCount),\r
+                gdata.begin() + range.first);\r
+        };\r
+\r
+        assert(isTypeOp(opCode) || isConstOp(opCode));\r
+\r
+        switch (opCode) {\r
+        case spv::OpTypeOpaque:       // TODO: disable until we compare the literal strings.\r
+        case spv::OpTypeQueue:        return false;\r
+        case spv::OpTypeEvent:        // fall through...\r
+        case spv::OpTypeDeviceEvent:  // ...\r
+        case spv::OpTypeReserveId:    return false;\r
+            // for samplers, we don't handle the optional parameters yet\r
+        case spv::OpTypeSampler:      return cmpLiteral() && cmpConst() && cmpSubType() && wordCount == 8;\r
+        default:                      return cmpLiteral() && cmpConst() && cmpSubType();\r
+        }\r
+    }\r
+\r
+\r
+    // Look for an equivalent type in the globalTypes map\r
+    spv::Id spirvbin_t::findType(const spirvbin_t::globaltypes_t& globalTypes, spv::Id lt) const\r
+    {\r
+        // Try a recursive type match on each in turn, and return a match if we find one\r
+        for (const auto& gt : globalTypes)\r
+            if (matchType(globalTypes, lt, gt.first))\r
+                return gt.first;\r
+\r
+        return spv::NoType;\r
+    }\r
+#endif // NOTDEF\r
+\r
+    // Return start position in SPV of given type.  error if not found.\r
+    int spirvbin_t::typePos(spv::Id id) const\r
+    {\r
+        const auto tid_it = typeConstPosR.find(id);\r
+        if (tid_it == typeConstPosR.end())\r
+            error("type ID not found");\r
+\r
+        return tid_it->second;\r
+    }\r
+\r
+    // Hash types to canonical values.  This can return ID collisions (it's a bit\r
+    // inevitable): it's up to the caller to handle that gracefully.\r
+    std::uint32_t spirvbin_t::hashType(int typeStart) const\r
+    {\r
+        const unsigned wordCount   = asWordCount(typeStart);\r
+        const spv::Op  opCode      = asOpCode(typeStart);\r
+\r
+        switch (opCode) {\r
+        case spv::OpTypeVoid:         return 0;\r
+        case spv::OpTypeBool:         return 1;\r
+        case spv::OpTypeInt:          return 3 + (spv[typeStart+3]);\r
+        case spv::OpTypeFloat:        return 5;\r
+        case spv::OpTypeVector:\r
+            return 6 + hashType(typePos(spv[typeStart+2])) * (spv[typeStart+3] - 1);\r
+        case spv::OpTypeMatrix:\r
+            return 30 + hashType(typePos(spv[typeStart+2])) * (spv[typeStart+3] - 1);\r
+        case spv::OpTypeSampler:\r
+            return 120 + hashType(typePos(spv[typeStart+2])) +\r
+                spv[typeStart+3] +            // dimensionality\r
+                spv[typeStart+4] * 8 * 16 +   // content\r
+                spv[typeStart+5] * 4 * 16 +   // arrayed\r
+                spv[typeStart+6] * 2 * 16 +   // compare\r
+                spv[typeStart+7] * 1 * 16;    // multisampled\r
+        case spv::OpTypeFilter:\r
+            return 500;\r
+        case spv::OpTypeArray:\r
+            return 501 + hashType(typePos(spv[typeStart+2])) * spv[typeStart+3];\r
+        case spv::OpTypeRuntimeArray:\r
+            return 5000  + hashType(typePos(spv[typeStart+2]));\r
+        case spv::OpTypeStruct:\r
+            {\r
+                std::uint32_t hash = 10000;\r
+                for (unsigned w=2; w < wordCount; ++w)\r
+                    hash += w * hashType(typePos(spv[typeStart+w]));\r
+                return hash;\r
+            }\r
+\r
+        case spv::OpTypeOpaque:         return 6000 + spv[typeStart+2];\r
+        case spv::OpTypePointer:        return 100000  + hashType(typePos(spv[typeStart+3]));\r
+        case spv::OpTypeFunction:\r
+            {\r
+                std::uint32_t hash = 200000;\r
+                for (unsigned w=2; w < wordCount; ++w)\r
+                    hash += w * hashType(typePos(spv[typeStart+w]));\r
+                return hash;\r
+            }\r
+\r
+        case spv::OpTypeEvent:           return 300000;\r
+        case spv::OpTypeDeviceEvent:     return 300001;\r
+        case spv::OpTypeReserveId:       return 300002;\r
+        case spv::OpTypeQueue:           return 300003;\r
+        case spv::OpTypePipe:            return 300004;\r
+\r
+        case spv::OpConstantNullObject:  return 300005;\r
+        case spv::OpConstantSampler:     return 300006;\r
+\r
+        case spv::OpConstantTrue:        return 300007;\r
+        case spv::OpConstantFalse:       return 300008;\r
+        case spv::OpConstantNullPointer: return 300009;\r
+        case spv::OpConstantComposite:\r
+            {\r
+                std::uint32_t hash = 300011 + hashType(typePos(spv[typeStart+1]));\r
+                for (unsigned w=3; w < wordCount; ++w)\r
+                    hash += w * hashType(typePos(spv[typeStart+w]));\r
+                return hash;\r
+            }\r
+        case spv::OpConstant:\r
+            {\r
+                std::uint32_t hash = 400011 + hashType(typePos(spv[typeStart+1]));\r
+                for (unsigned w=3; w < wordCount; ++w)\r
+                    hash += w * spv[typeStart+w];\r
+                return hash;\r
+            }\r
+\r
+        default:\r
+            error("unknown type opcode");\r
+            return 0;\r
+        }\r
+    }\r
+\r
+    void spirvbin_t::mapTypeConst()\r
+    {\r
+        globaltypes_t globalTypeMap;\r
+\r
+        msg(3, 2, std::string("Remapping Consts & Types: "));\r
+\r
+        static const std::uint32_t softTypeIdLimit = 3011; // small prime.  TODO: get from options\r
+        static const std::uint32_t firstMappedID   = 8;    // offset into ID space\r
+\r
+        for (auto& typeStart : typeConstPos) {\r
+            const spv::Id       resId     = asTypeConstId(typeStart);\r
+            const std::uint32_t hashval   = hashType(typeStart);\r
+\r
+            if (isOldIdUnmapped(resId))\r
+                localId(resId, nextUnusedId(hashval % softTypeIdLimit + firstMappedID));\r
+        }\r
+    }\r
+\r
+\r
+    // Strip a single binary by removing ranges given in stripRange\r
+    void spirvbin_t::strip()\r
+    {\r
+        if (stripRange.empty()) // nothing to do\r
+            return;\r
+\r
+        // Sort strip ranges in order of traversal\r
+        std::sort(stripRange.begin(), stripRange.end());\r
+\r
+        // Allocate a new binary big enough to hold old binary\r
+        // We'll step this iterator through the strip ranges as we go through the binary\r
+        decltype(stripRange)::const_iterator strip_it = stripRange.begin();\r
+\r
+        int strippedPos = 0;\r
+        for (unsigned word = 0; word < unsigned(spv.size()); ++word) {\r
+            if (strip_it != stripRange.end() && word >= strip_it->second)\r
+                ++strip_it;\r
+\r
+            if (strip_it == stripRange.end() || word < strip_it->first || word >= strip_it->second)\r
+                spv[strippedPos++] = spv[word];\r
+        }\r
+\r
+        spv.resize(strippedPos);\r
+        stripRange.clear();\r
+    }\r
+\r
+    // Strip a single binary by removing ranges given in stripRange\r
+    void spirvbin_t::remap(std::uint32_t opts)\r
+    {\r
+        options = opts;\r
+\r
+        // Set up opcode tables from SpvDoc\r
+        spv::Parameterize();\r
+\r
+        validate();  // validate header\r
+        buildLocalMaps();\r
+\r
+        msg(3, 4, std::string("ID bound: ") + std::to_string(bound()));\r
+\r
+        if (options & OPT_LOADSTORE) optLoadStore();\r
+        if (options & OPT_FWD_LS)    forwardLoadStores();\r
+        if (options & DCE_FUNCS)     dceFuncs();\r
+        if (options & DCE_VARS)      dceVars();\r
+        if (options & DCE_TYPES)     dceTypes();\r
+        if (options & MAP_TYPES)     mapTypeConst();\r
+        if (options & MAP_NAMES)     mapNames();\r
+        if (options & MAP_FUNCS)     mapFnBodies();\r
+        // if (options & STRIP)         stripDebug();\r
+\r
+        mapRemainder(); // map any unmapped IDs\r
+        applyMap();     // Now remap each shader to the new IDs we've come up with\r
+        strip();        // strip out data we decided to eliminate\r
+\r
+#define EXPERIMENT3 0\r
+#if (EXPERIMENT3)\r
+        // TODO: ... shortcuts for simple single-const access chains and constants,\r
+        // folded into high half of the ID space.\r
+#endif\r
+    }\r
+\r
+    // remap from a memory image\r
+    void spirvbin_t::remap(std::vector<std::uint32_t>& in_spv, std::uint32_t opts)\r
+    {\r
+        spv.swap(in_spv);\r
+        remap(opts);\r
+        spv.swap(in_spv);\r
+    }\r
 \r
 } // namespace SPV\r
-    \r
+\r
 #endif // defined (use_cpp11)\r
-
+\r
index 6d29107..a5592c8 100644 (file)
@@ -10,8 +10,10 @@ else(WIN32)
 endif(WIN32)\r
 \r
 set(SOURCES StandAlone.cpp)\r
+set(REMAPPER_SOURCES spirv-remap.cpp)\r
 \r
 add_executable(glslangValidator ${SOURCES})\r
+add_executable(spirv-remap ${REMAPPER_SOURCES})\r
 \r
 set(LIBRARIES\r
     glslang\r
@@ -26,6 +28,7 @@ elseif(UNIX)
 endif(WIN32)\r
 \r
 target_link_libraries(glslangValidator ${LIBRARIES})\r
+target_link_libraries(spirv-remap ${LIBRARIES})\r
 \r
 if(WIN32)\r
     source_group("Source" FILES ${SOURCES})\r
@@ -33,3 +36,6 @@ endif(WIN32)
 \r
 install(TARGETS glslangValidator\r
         RUNTIME DESTINATION bin)\r
+\r
+install(TARGETS spirv-remap\r
+        RUNTIME DESTINATION bin)\r
diff --git a/StandAlone/spirv-remap.cpp b/StandAlone/spirv-remap.cpp
new file mode 100644 (file)
index 0000000..e14c8b5
--- /dev/null
@@ -0,0 +1,336 @@
+//\r
+//Copyright (C) 2015 LunarG, Inc.\r
+//\r
+//All rights reserved.\r
+//\r
+//Redistribution and use in source and binary forms, with or without\r
+//modification, are permitted provided that the following conditions\r
+//are met:\r
+//\r
+//    Redistributions of source code must retain the above copyright\r
+//    notice, this list of conditions and the following disclaimer.\r
+//\r
+//    Redistributions in binary form must reproduce the above\r
+//    copyright notice, this list of conditions and the following\r
+//    disclaimer in the documentation and/or other materials provided\r
+//    with the distribution.\r
+//\r
+//    Neither the name of 3Dlabs Inc. Ltd. nor the names of its\r
+//    contributors may be used to endorse or promote products derived\r
+//    from this software without specific prior written permission.\r
+//\r
+//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r
+//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\r
+//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r
+//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\r
+//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\r
+//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r
+//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
+//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+//POSSIBILITY OF SUCH DAMAGE.\r
+//\r
+\r
+#include <iostream>
+#include <fstream>
+#include <cstring>
+#include <stdexcept>
+
+#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<SpvWord>& 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<SpvWord>& 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<std::string>& inputFile, const std::string& outputDir,
+        int opts, int verbosity)
+    {
+        for (const auto& filename : inputFile) {
+            std::vector<SpvWord> 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<std::string>& 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; ) {
+            const std::string arg = argv[a];
+
+            if (arg == "--output" || arg == "-o") {
+                // Output directory
+                if (++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<std::string> 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.
+}