1 | // |
2 | // Copyright (C) 2015 LunarG, Inc. |
3 | // |
4 | // All rights reserved. |
5 | // |
6 | // Redistribution and use in source and binary forms, with or without |
7 | // modification, are permitted provided that the following conditions |
8 | // are met: |
9 | // |
10 | // Redistributions of source code must retain the above copyright |
11 | // notice, this list of conditions and the following disclaimer. |
12 | // |
13 | // Redistributions in binary form must reproduce the above |
14 | // copyright notice, this list of conditions and the following |
15 | // disclaimer in the documentation and/or other materials provided |
16 | // with the distribution. |
17 | // |
18 | // Neither the name of 3Dlabs Inc. Ltd. nor the names of its |
19 | // contributors may be used to endorse or promote products derived |
20 | // from this software without specific prior written permission. |
21 | // |
22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
25 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
26 | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
27 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
28 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
29 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
30 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
31 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
32 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
33 | // POSSIBILITY OF SUCH DAMAGE. |
34 | // |
35 | |
36 | #ifndef SPIRVREMAPPER_H |
37 | #define SPIRVREMAPPER_H |
38 | |
39 | #include <string> |
40 | #include <vector> |
41 | #include <cstdlib> |
42 | #include <exception> |
43 | |
44 | namespace spv { |
45 | |
46 | class spirvbin_base_t |
47 | { |
48 | public: |
49 | enum Options { |
50 | NONE = 0, |
51 | STRIP = (1<<0), |
52 | MAP_TYPES = (1<<1), |
53 | MAP_NAMES = (1<<2), |
54 | MAP_FUNCS = (1<<3), |
55 | DCE_FUNCS = (1<<4), |
56 | DCE_VARS = (1<<5), |
57 | DCE_TYPES = (1<<6), |
58 | OPT_LOADSTORE = (1<<7), |
59 | OPT_FWD_LS = (1<<8), // EXPERIMENTAL: PRODUCES INVALID SCHEMA-0 SPIRV |
60 | MAP_ALL = (MAP_TYPES | MAP_NAMES | MAP_FUNCS), |
61 | DCE_ALL = (DCE_FUNCS | DCE_VARS | DCE_TYPES), |
62 | OPT_ALL = (OPT_LOADSTORE), |
63 | |
64 | ALL_BUT_STRIP = (MAP_ALL | DCE_ALL | OPT_ALL), |
65 | DO_EVERYTHING = (STRIP | ALL_BUT_STRIP) |
66 | }; |
67 | }; |
68 | |
69 | } // namespace SPV |
70 | |
71 | #include <functional> |
72 | #include <cstdint> |
73 | #include <unordered_map> |
74 | #include <unordered_set> |
75 | #include <map> |
76 | #include <set> |
77 | #include <cassert> |
78 | |
79 | #include "spirv.hpp" |
80 | #include "spvIR.h" |
81 | |
82 | namespace spv { |
83 | |
84 | // class to hold SPIR-V binary data for remapping, DCE, and debug stripping |
85 | class spirvbin_t : public spirvbin_base_t |
86 | { |
87 | public: |
88 | spirvbin_t(int verbose = 0) : entryPoint(spv::NoResult), largestNewId(0), verbose(verbose), errorLatch(false) |
89 | { } |
90 | |
91 | virtual ~spirvbin_t() { } |
92 | |
93 | // remap on an existing binary in memory |
94 | void remap(std::vector<std::uint32_t>& spv, const std::vector<std::string>& whiteListStrings, |
95 | std::uint32_t opts = DO_EVERYTHING); |
96 | |
97 | // remap on an existing binary in memory - legacy interface without white list |
98 | void remap(std::vector<std::uint32_t>& spv, std::uint32_t opts = DO_EVERYTHING); |
99 | |
100 | // Type for error/log handler functions |
101 | typedef std::function<void(const std::string&)> errorfn_t; |
102 | typedef std::function<void(const std::string&)> logfn_t; |
103 | |
104 | // Register error/log handling functions (can be lambda fn / functor / etc) |
105 | static void registerErrorHandler(errorfn_t handler) { errorHandler = handler; } |
106 | static void registerLogHandler(logfn_t handler) { logHandler = handler; } |
107 | |
108 | protected: |
109 | // This can be overridden to provide other message behavior if needed |
110 | virtual void msg(int minVerbosity, int indent, const std::string& txt) const; |
111 | |
112 | private: |
113 | // Local to global, or global to local ID map |
114 | typedef std::unordered_map<spv::Id, spv::Id> idmap_t; |
115 | typedef std::unordered_set<spv::Id> idset_t; |
116 | typedef std::unordered_map<spv::Id, int> blockmap_t; |
117 | |
118 | void remap(std::uint32_t opts = DO_EVERYTHING); |
119 | |
120 | // Map of names to IDs |
121 | typedef std::unordered_map<std::string, spv::Id> namemap_t; |
122 | |
123 | typedef std::uint32_t spirword_t; |
124 | |
125 | typedef std::pair<unsigned, unsigned> range_t; |
126 | typedef std::function<void(spv::Id&)> idfn_t; |
127 | typedef std::function<bool(spv::Op, unsigned start)> instfn_t; |
128 | |
129 | // Special Values for ID map: |
130 | static const spv::Id unmapped; // unchanged from default value |
131 | static const spv::Id unused; // unused ID |
132 | static const int ; // SPIR header = 5 words |
133 | |
134 | class id_iterator_t; |
135 | |
136 | // For mapping type entries between different shaders |
137 | typedef std::vector<spirword_t> typeentry_t; |
138 | typedef std::map<spv::Id, typeentry_t> globaltypes_t; |
139 | |
140 | // A set that preserves position order, and a reverse map |
141 | typedef std::set<int> posmap_t; |
142 | typedef std::unordered_map<spv::Id, int> posmap_rev_t; |
143 | |
144 | // Maps and ID to the size of its base type, if known. |
145 | typedef std::unordered_map<spv::Id, unsigned> typesize_map_t; |
146 | |
147 | // handle error |
148 | void error(const std::string& txt) const { errorLatch = true; errorHandler(txt); } |
149 | |
150 | bool isConstOp(spv::Op opCode) const; |
151 | bool isTypeOp(spv::Op opCode) const; |
152 | bool isStripOp(spv::Op opCode) const; |
153 | bool isFlowCtrl(spv::Op opCode) const; |
154 | range_t literalRange(spv::Op opCode) const; |
155 | range_t typeRange(spv::Op opCode) const; |
156 | range_t constRange(spv::Op opCode) const; |
157 | unsigned typeSizeInWords(spv::Id id) const; |
158 | unsigned idTypeSizeInWords(spv::Id id) const; |
159 | |
160 | bool isStripOp(spv::Op opCode, unsigned start) const; |
161 | |
162 | spv::Id& asId(unsigned word) { return spv[word]; } |
163 | const spv::Id& asId(unsigned word) const { return spv[word]; } |
164 | spv::Op asOpCode(unsigned word) const { return opOpCode(spv[word]); } |
165 | std::uint32_t asOpCodeHash(unsigned word); |
166 | spv::Decoration asDecoration(unsigned word) const { return spv::Decoration(spv[word]); } |
167 | unsigned asWordCount(unsigned word) const { return opWordCount(spv[word]); } |
168 | spv::Id asTypeConstId(unsigned word) const { return asId(word + (isTypeOp(asOpCode(word)) ? 1 : 2)); } |
169 | unsigned idPos(spv::Id id) const; |
170 | |
171 | static unsigned opWordCount(spirword_t data) { return data >> spv::WordCountShift; } |
172 | static spv::Op opOpCode(spirword_t data) { return spv::Op(data & spv::OpCodeMask); } |
173 | |
174 | // Header access & set methods |
175 | spirword_t magic() const { return spv[0]; } // return magic number |
176 | spirword_t bound() const { return spv[3]; } // return Id bound from header |
177 | spirword_t bound(spirword_t b) { return spv[3] = b; } |
178 | spirword_t genmagic() const { return spv[2]; } // generator magic |
179 | spirword_t genmagic(spirword_t m) { return spv[2] = m; } |
180 | spirword_t schemaNum() const { return spv[4]; } // schema number from header |
181 | |
182 | // Mapping fns: get |
183 | spv::Id localId(spv::Id id) const { return idMapL[id]; } |
184 | |
185 | // Mapping fns: set |
186 | inline spv::Id localId(spv::Id id, spv::Id newId); |
187 | void countIds(spv::Id id); |
188 | |
189 | // Return next unused new local ID. |
190 | // NOTE: boost::dynamic_bitset would be more efficient due to find_next(), |
191 | // which std::vector<bool> doens't have. |
192 | inline spv::Id nextUnusedId(spv::Id id); |
193 | |
194 | void buildLocalMaps(); |
195 | std::string literalString(unsigned word) const; // Return literal as a std::string |
196 | int literalStringWords(const std::string& str) const { return (int(str.size())+4)/4; } |
197 | |
198 | bool isNewIdMapped(spv::Id newId) const { return isMapped(newId); } |
199 | bool isOldIdUnmapped(spv::Id oldId) const { return localId(oldId) == unmapped; } |
200 | bool isOldIdUnused(spv::Id oldId) const { return localId(oldId) == unused; } |
201 | bool isOldIdMapped(spv::Id oldId) const { return !isOldIdUnused(oldId) && !isOldIdUnmapped(oldId); } |
202 | bool isFunction(spv::Id oldId) const { return fnPos.find(oldId) != fnPos.end(); } |
203 | |
204 | // bool matchType(const globaltypes_t& globalTypes, spv::Id lt, spv::Id gt) const; |
205 | // spv::Id findType(const globaltypes_t& globalTypes, spv::Id lt) const; |
206 | std::uint32_t hashType(unsigned typeStart) const; |
207 | |
208 | spirvbin_t& process(instfn_t, idfn_t, unsigned begin = 0, unsigned end = 0); |
209 | int processInstruction(unsigned word, instfn_t, idfn_t); |
210 | |
211 | void validate() const; |
212 | void mapTypeConst(); |
213 | void mapFnBodies(); |
214 | void optLoadStore(); |
215 | void dceFuncs(); |
216 | void dceVars(); |
217 | void dceTypes(); |
218 | void mapNames(); |
219 | void foldIds(); // fold IDs to smallest space |
220 | void forwardLoadStores(); // load store forwarding (EXPERIMENTAL) |
221 | void offsetIds(); // create relative offset IDs |
222 | |
223 | void applyMap(); // remap per local name map |
224 | void mapRemainder(); // map any IDs we haven't touched yet |
225 | void stripDebug(); // strip all debug info |
226 | void stripDeadRefs(); // strips debug info for now-dead references after DCE |
227 | void strip(); // remove debug symbols |
228 | |
229 | std::vector<spirword_t> spv; // SPIR words |
230 | |
231 | std::vector<std::string> stripWhiteList; |
232 | |
233 | namemap_t nameMap; // ID names from OpName |
234 | |
235 | // Since we want to also do binary ops, we can't use std::vector<bool>. we could use |
236 | // boost::dynamic_bitset, but we're trying to avoid a boost dependency. |
237 | typedef std::uint64_t bits_t; |
238 | std::vector<bits_t> mapped; // which new IDs have been mapped |
239 | static const int mBits = sizeof(bits_t) * 4; |
240 | |
241 | bool isMapped(spv::Id id) const { return id < maxMappedId() && ((mapped[id/mBits] & (1LL<<(id%mBits))) != 0); } |
242 | void setMapped(spv::Id id) { resizeMapped(id); mapped[id/mBits] |= (1LL<<(id%mBits)); } |
243 | void resizeMapped(spv::Id id) { if (id >= maxMappedId()) mapped.resize(id/mBits+1, 0); } |
244 | size_t maxMappedId() const { return mapped.size() * mBits; } |
245 | |
246 | // Add a strip range for a given instruction starting at 'start' |
247 | // Note: avoiding brace initializers to please older versions os MSVC. |
248 | void stripInst(unsigned start) { stripRange.push_back(range_t(start, start + asWordCount(start))); } |
249 | |
250 | // Function start and end. use unordered_map because we'll have |
251 | // many fewer functions than IDs. |
252 | std::unordered_map<spv::Id, range_t> fnPos; |
253 | |
254 | // Which functions are called, anywhere in the module, with a call count |
255 | std::unordered_map<spv::Id, int> fnCalls; |
256 | |
257 | posmap_t typeConstPos; // word positions that define types & consts (ordered) |
258 | posmap_rev_t idPosR; // reverse map from IDs to positions |
259 | typesize_map_t idTypeSizeMap; // maps each ID to its type size, if known. |
260 | |
261 | std::vector<spv::Id> idMapL; // ID {M}ap from {L}ocal to {G}lobal IDs |
262 | |
263 | spv::Id entryPoint; // module entry point |
264 | spv::Id largestNewId; // biggest new ID we have mapped anything to |
265 | |
266 | // Sections of the binary to strip, given as [begin,end) |
267 | std::vector<range_t> stripRange; |
268 | |
269 | // processing options: |
270 | std::uint32_t options; |
271 | int verbose; // verbosity level |
272 | |
273 | // Error latch: this is set if the error handler is ever executed. It would be better to |
274 | // use a try/catch block and throw, but that's not desired for certain environments, so |
275 | // this is the alternative. |
276 | mutable bool errorLatch; |
277 | |
278 | static errorfn_t errorHandler; |
279 | static logfn_t logHandler; |
280 | }; |
281 | |
282 | } // namespace SPV |
283 | |
284 | #endif // SPIRVREMAPPER_H |
285 | |