1 | // |
2 | // Copyright (C) 2014 LunarG, Inc. |
3 | // Copyright (C) 2015-2018 Google, Inc. |
4 | // |
5 | // All rights reserved. |
6 | // |
7 | // Redistribution and use in source and binary forms, with or without |
8 | // modification, are permitted provided that the following conditions |
9 | // are met: |
10 | // |
11 | // Redistributions of source code must retain the above copyright |
12 | // notice, this list of conditions and the following disclaimer. |
13 | // |
14 | // Redistributions in binary form must reproduce the above |
15 | // copyright notice, this list of conditions and the following |
16 | // disclaimer in the documentation and/or other materials provided |
17 | // with the distribution. |
18 | // |
19 | // Neither the name of 3Dlabs Inc. Ltd. nor the names of its |
20 | // contributors may be used to endorse or promote products derived |
21 | // from this software without specific prior written permission. |
22 | // |
23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
24 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
25 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
26 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
27 | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
28 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
29 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
30 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
31 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
32 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
33 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
34 | // POSSIBILITY OF SUCH DAMAGE. |
35 | |
36 | // SPIRV-IR |
37 | // |
38 | // Simple in-memory representation (IR) of SPIRV. Just for holding |
39 | // Each function's CFG of blocks. Has this hierarchy: |
40 | // - Module, which is a list of |
41 | // - Function, which is a list of |
42 | // - Block, which is a list of |
43 | // - Instruction |
44 | // |
45 | |
46 | #pragma once |
47 | #ifndef spvIR_H |
48 | #define spvIR_H |
49 | |
50 | #include "spirv.hpp" |
51 | |
52 | #include <algorithm> |
53 | #include <cassert> |
54 | #include <functional> |
55 | #include <iostream> |
56 | #include <memory> |
57 | #include <vector> |
58 | #include <set> |
59 | #include <optional> |
60 | |
61 | namespace spv { |
62 | |
63 | class Block; |
64 | class Function; |
65 | class Module; |
66 | |
67 | const Id NoResult = 0; |
68 | const Id NoType = 0; |
69 | |
70 | const Decoration NoPrecision = DecorationMax; |
71 | |
72 | #ifdef __GNUC__ |
73 | # define POTENTIALLY_UNUSED __attribute__((unused)) |
74 | #else |
75 | # define POTENTIALLY_UNUSED |
76 | #endif |
77 | |
78 | POTENTIALLY_UNUSED |
79 | const MemorySemanticsMask MemorySemanticsAllMemory = |
80 | (MemorySemanticsMask)(MemorySemanticsUniformMemoryMask | |
81 | MemorySemanticsWorkgroupMemoryMask | |
82 | MemorySemanticsAtomicCounterMemoryMask | |
83 | MemorySemanticsImageMemoryMask); |
84 | |
85 | struct IdImmediate { |
86 | bool isId; // true if word is an Id, false if word is an immediate |
87 | unsigned word; |
88 | IdImmediate(bool i, unsigned w) : isId(i), word(w) {} |
89 | }; |
90 | |
91 | // |
92 | // SPIR-V IR instruction. |
93 | // |
94 | |
95 | class Instruction { |
96 | public: |
97 | Instruction(Id resultId, Id typeId, Op opCode) : resultId(resultId), typeId(typeId), opCode(opCode), block(nullptr) { } |
98 | explicit Instruction(Op opCode) : resultId(NoResult), typeId(NoType), opCode(opCode), block(nullptr) { } |
99 | virtual ~Instruction() {} |
100 | void reserveOperands(size_t count) { |
101 | operands.reserve(n: count); |
102 | idOperand.reserve(n: count); |
103 | } |
104 | void addIdOperand(Id id) { |
105 | // ids can't be 0 |
106 | assert(id); |
107 | operands.push_back(x: id); |
108 | idOperand.push_back(x: true); |
109 | } |
110 | void addImmediateOperand(unsigned int immediate) { |
111 | operands.push_back(x: immediate); |
112 | idOperand.push_back(x: false); |
113 | } |
114 | void setImmediateOperand(unsigned idx, unsigned int immediate) { |
115 | assert(!idOperand[idx]); |
116 | operands[idx] = immediate; |
117 | } |
118 | |
119 | void addStringOperand(const char* str) |
120 | { |
121 | unsigned int word = 0; |
122 | unsigned int shiftAmount = 0; |
123 | char c; |
124 | |
125 | do { |
126 | c = *(str++); |
127 | word |= ((unsigned int)c) << shiftAmount; |
128 | shiftAmount += 8; |
129 | if (shiftAmount == 32) { |
130 | addImmediateOperand(immediate: word); |
131 | word = 0; |
132 | shiftAmount = 0; |
133 | } |
134 | } while (c != 0); |
135 | |
136 | // deal with partial last word |
137 | if (shiftAmount > 0) { |
138 | addImmediateOperand(immediate: word); |
139 | } |
140 | } |
141 | bool isIdOperand(int op) const { return idOperand[op]; } |
142 | void setBlock(Block* b) { block = b; } |
143 | Block* getBlock() const { return block; } |
144 | Op getOpCode() const { return opCode; } |
145 | int getNumOperands() const |
146 | { |
147 | assert(operands.size() == idOperand.size()); |
148 | return (int)operands.size(); |
149 | } |
150 | Id getResultId() const { return resultId; } |
151 | Id getTypeId() const { return typeId; } |
152 | Id getIdOperand(int op) const { |
153 | assert(idOperand[op]); |
154 | return operands[op]; |
155 | } |
156 | unsigned int getImmediateOperand(int op) const { |
157 | assert(!idOperand[op]); |
158 | return operands[op]; |
159 | } |
160 | |
161 | // Write out the binary form. |
162 | void dump(std::vector<unsigned int>& out) const |
163 | { |
164 | // Compute the wordCount |
165 | unsigned int wordCount = 1; |
166 | if (typeId) |
167 | ++wordCount; |
168 | if (resultId) |
169 | ++wordCount; |
170 | wordCount += (unsigned int)operands.size(); |
171 | |
172 | // Write out the beginning of the instruction |
173 | out.push_back(x: ((wordCount) << WordCountShift) | opCode); |
174 | if (typeId) |
175 | out.push_back(x: typeId); |
176 | if (resultId) |
177 | out.push_back(x: resultId); |
178 | |
179 | // Write out the operands |
180 | for (int op = 0; op < (int)operands.size(); ++op) |
181 | out.push_back(x: operands[op]); |
182 | } |
183 | |
184 | protected: |
185 | Instruction(const Instruction&); |
186 | Id resultId; |
187 | Id typeId; |
188 | Op opCode; |
189 | std::vector<Id> operands; // operands, both <id> and immediates (both are unsigned int) |
190 | std::vector<bool> idOperand; // true for operands that are <id>, false for immediates |
191 | Block* block; |
192 | }; |
193 | |
194 | // |
195 | // SPIR-V IR block. |
196 | // |
197 | |
198 | struct DebugSourceLocation { |
199 | int line; |
200 | int column; |
201 | spv::Id fileId; |
202 | }; |
203 | |
204 | class Block { |
205 | public: |
206 | Block(Id id, Function& parent); |
207 | virtual ~Block() |
208 | { |
209 | } |
210 | |
211 | Id getId() { return instructions.front()->getResultId(); } |
212 | |
213 | Function& getParent() const { return parent; } |
214 | // Returns true if the source location is actually updated. |
215 | // Note we still need the builder to insert the line marker instruction. This is just a tracker. |
216 | bool updateDebugSourceLocation(int line, int column, spv::Id fileId) { |
217 | if (currentSourceLoc && currentSourceLoc->line == line && currentSourceLoc->column == column && |
218 | currentSourceLoc->fileId == fileId) { |
219 | return false; |
220 | } |
221 | |
222 | currentSourceLoc = DebugSourceLocation{.line: line, .column: column, .fileId: fileId}; |
223 | return true; |
224 | } |
225 | // Returns true if the scope is actually updated. |
226 | // Note we still need the builder to insert the debug scope instruction. This is just a tracker. |
227 | bool updateDebugScope(spv::Id scopeId) { |
228 | assert(scopeId); |
229 | if (currentDebugScope && *currentDebugScope == scopeId) { |
230 | return false; |
231 | } |
232 | |
233 | currentDebugScope = scopeId; |
234 | return true; |
235 | } |
236 | void addInstruction(std::unique_ptr<Instruction> inst); |
237 | void addPredecessor(Block* pred) { predecessors.push_back(x: pred); pred->successors.push_back(x: this);} |
238 | void addLocalVariable(std::unique_ptr<Instruction> inst) { localVariables.push_back(x: std::move(inst)); } |
239 | const std::vector<Block*>& getPredecessors() const { return predecessors; } |
240 | const std::vector<Block*>& getSuccessors() const { return successors; } |
241 | const std::vector<std::unique_ptr<Instruction> >& getInstructions() const { |
242 | return instructions; |
243 | } |
244 | const std::vector<std::unique_ptr<Instruction> >& getLocalVariables() const { return localVariables; } |
245 | void setUnreachable() { unreachable = true; } |
246 | bool isUnreachable() const { return unreachable; } |
247 | // Returns the block's merge instruction, if one exists (otherwise null). |
248 | const Instruction* getMergeInstruction() const { |
249 | if (instructions.size() < 2) return nullptr; |
250 | const Instruction* nextToLast = (instructions.cend() - 2)->get(); |
251 | switch (nextToLast->getOpCode()) { |
252 | case OpSelectionMerge: |
253 | case OpLoopMerge: |
254 | return nextToLast; |
255 | default: |
256 | return nullptr; |
257 | } |
258 | return nullptr; |
259 | } |
260 | |
261 | // Change this block into a canonical dead merge block. Delete instructions |
262 | // as necessary. A canonical dead merge block has only an OpLabel and an |
263 | // OpUnreachable. |
264 | void rewriteAsCanonicalUnreachableMerge() { |
265 | assert(localVariables.empty()); |
266 | // Delete all instructions except for the label. |
267 | assert(instructions.size() > 0); |
268 | instructions.resize(new_size: 1); |
269 | successors.clear(); |
270 | addInstruction(inst: std::unique_ptr<Instruction>(new Instruction(OpUnreachable))); |
271 | } |
272 | // Change this block into a canonical dead continue target branching to the |
273 | // given header ID. Delete instructions as necessary. A canonical dead continue |
274 | // target has only an OpLabel and an unconditional branch back to the corresponding |
275 | // header. |
276 | void rewriteAsCanonicalUnreachableContinue(Block* ) { |
277 | assert(localVariables.empty()); |
278 | // Delete all instructions except for the label. |
279 | assert(instructions.size() > 0); |
280 | instructions.resize(new_size: 1); |
281 | successors.clear(); |
282 | // Add OpBranch back to the header. |
283 | assert(header != nullptr); |
284 | Instruction* branch = new Instruction(OpBranch); |
285 | branch->addIdOperand(id: header->getId()); |
286 | addInstruction(inst: std::unique_ptr<Instruction>(branch)); |
287 | successors.push_back(x: header); |
288 | } |
289 | |
290 | bool isTerminated() const |
291 | { |
292 | switch (instructions.back()->getOpCode()) { |
293 | case OpBranch: |
294 | case OpBranchConditional: |
295 | case OpSwitch: |
296 | case OpKill: |
297 | case OpTerminateInvocation: |
298 | case OpReturn: |
299 | case OpReturnValue: |
300 | case OpUnreachable: |
301 | return true; |
302 | default: |
303 | return false; |
304 | } |
305 | } |
306 | |
307 | void dump(std::vector<unsigned int>& out) const |
308 | { |
309 | instructions[0]->dump(out); |
310 | for (int i = 0; i < (int)localVariables.size(); ++i) |
311 | localVariables[i]->dump(out); |
312 | for (int i = 1; i < (int)instructions.size(); ++i) |
313 | instructions[i]->dump(out); |
314 | } |
315 | |
316 | protected: |
317 | Block(const Block&); |
318 | Block& operator=(Block&); |
319 | |
320 | // To enforce keeping parent and ownership in sync: |
321 | friend Function; |
322 | |
323 | std::vector<std::unique_ptr<Instruction> > instructions; |
324 | std::vector<Block*> predecessors, successors; |
325 | std::vector<std::unique_ptr<Instruction> > localVariables; |
326 | Function& parent; |
327 | |
328 | // Track source location of the last source location marker instruction. |
329 | std::optional<DebugSourceLocation> currentSourceLoc; |
330 | |
331 | // Track scope of the last debug scope instruction. |
332 | std::optional<spv::Id> currentDebugScope; |
333 | |
334 | // track whether this block is known to be uncreachable (not necessarily |
335 | // true for all unreachable blocks, but should be set at least |
336 | // for the extraneous ones introduced by the builder). |
337 | bool unreachable; |
338 | }; |
339 | |
340 | // The different reasons for reaching a block in the inReadableOrder traversal. |
341 | enum ReachReason { |
342 | // Reachable from the entry block via transfers of control, i.e. branches. |
343 | ReachViaControlFlow = 0, |
344 | // A continue target that is not reachable via control flow. |
345 | ReachDeadContinue, |
346 | // A merge block that is not reachable via control flow. |
347 | ReachDeadMerge |
348 | }; |
349 | |
350 | // Traverses the control-flow graph rooted at root in an order suited for |
351 | // readable code generation. Invokes callback at every node in the traversal |
352 | // order. The callback arguments are: |
353 | // - the block, |
354 | // - the reason we reached the block, |
355 | // - if the reason was that block is an unreachable continue or unreachable merge block |
356 | // then the last parameter is the corresponding header block. |
357 | void inReadableOrder(Block* root, std::function<void(Block*, ReachReason, Block* )> callback); |
358 | |
359 | // |
360 | // SPIR-V IR Function. |
361 | // |
362 | |
363 | class Function { |
364 | public: |
365 | Function(Id id, Id resultType, Id functionType, Id firstParam, LinkageType linkage, const std::string& name, Module& parent); |
366 | virtual ~Function() |
367 | { |
368 | for (int i = 0; i < (int)parameterInstructions.size(); ++i) |
369 | delete parameterInstructions[i]; |
370 | |
371 | for (int i = 0; i < (int)blocks.size(); ++i) |
372 | delete blocks[i]; |
373 | } |
374 | Id getId() const { return functionInstruction.getResultId(); } |
375 | Id getParamId(int p) const { return parameterInstructions[p]->getResultId(); } |
376 | Id getParamType(int p) const { return parameterInstructions[p]->getTypeId(); } |
377 | |
378 | void addBlock(Block* block) { blocks.push_back(x: block); } |
379 | void removeBlock(Block* block) |
380 | { |
381 | auto found = find(first: blocks.begin(), last: blocks.end(), val: block); |
382 | assert(found != blocks.end()); |
383 | blocks.erase(position: found); |
384 | delete block; |
385 | } |
386 | |
387 | Module& getParent() const { return parent; } |
388 | Block* getEntryBlock() const { return blocks.front(); } |
389 | Block* getLastBlock() const { return blocks.back(); } |
390 | const std::vector<Block*>& getBlocks() const { return blocks; } |
391 | void addLocalVariable(std::unique_ptr<Instruction> inst); |
392 | Id getReturnType() const { return functionInstruction.getTypeId(); } |
393 | Id getFuncId() const { return functionInstruction.getResultId(); } |
394 | Id getFuncTypeId() const { return functionInstruction.getIdOperand(op: 1); } |
395 | void setReturnPrecision(Decoration precision) |
396 | { |
397 | if (precision == DecorationRelaxedPrecision) |
398 | reducedPrecisionReturn = true; |
399 | } |
400 | Decoration getReturnPrecision() const |
401 | { return reducedPrecisionReturn ? DecorationRelaxedPrecision : NoPrecision; } |
402 | |
403 | void setDebugLineInfo(Id fileName, int line, int column) { |
404 | lineInstruction = std::unique_ptr<Instruction>{new Instruction(OpLine)}; |
405 | lineInstruction->reserveOperands(count: 3); |
406 | lineInstruction->addIdOperand(id: fileName); |
407 | lineInstruction->addImmediateOperand(immediate: line); |
408 | lineInstruction->addImmediateOperand(immediate: column); |
409 | } |
410 | bool hasDebugLineInfo() const { return lineInstruction != nullptr; } |
411 | |
412 | void setImplicitThis() { implicitThis = true; } |
413 | bool hasImplicitThis() const { return implicitThis; } |
414 | |
415 | void addParamPrecision(unsigned param, Decoration precision) |
416 | { |
417 | if (precision == DecorationRelaxedPrecision) |
418 | reducedPrecisionParams.insert(x: param); |
419 | } |
420 | Decoration getParamPrecision(unsigned param) const |
421 | { |
422 | return reducedPrecisionParams.find(x: param) != reducedPrecisionParams.end() ? |
423 | DecorationRelaxedPrecision : NoPrecision; |
424 | } |
425 | |
426 | void dump(std::vector<unsigned int>& out) const |
427 | { |
428 | // OpLine |
429 | if (lineInstruction != nullptr) { |
430 | lineInstruction->dump(out); |
431 | } |
432 | |
433 | // OpFunction |
434 | functionInstruction.dump(out); |
435 | |
436 | // OpFunctionParameter |
437 | for (int p = 0; p < (int)parameterInstructions.size(); ++p) |
438 | parameterInstructions[p]->dump(out); |
439 | |
440 | // Blocks |
441 | inReadableOrder(root: blocks[0], callback: [&out](const Block* b, ReachReason, Block*) { b->dump(out); }); |
442 | Instruction end(0, 0, OpFunctionEnd); |
443 | end.dump(out); |
444 | } |
445 | |
446 | LinkageType getLinkType() const { return linkType; } |
447 | const char* getExportName() const { return exportName.c_str(); } |
448 | |
449 | protected: |
450 | Function(const Function&); |
451 | Function& operator=(Function&); |
452 | |
453 | Module& parent; |
454 | std::unique_ptr<Instruction> lineInstruction; |
455 | Instruction functionInstruction; |
456 | std::vector<Instruction*> parameterInstructions; |
457 | std::vector<Block*> blocks; |
458 | bool implicitThis; // true if this is a member function expecting to be passed a 'this' as the first argument |
459 | bool reducedPrecisionReturn; |
460 | std::set<int> reducedPrecisionParams; // list of parameter indexes that need a relaxed precision arg |
461 | LinkageType linkType; |
462 | std::string exportName; |
463 | }; |
464 | |
465 | // |
466 | // SPIR-V IR Module. |
467 | // |
468 | |
469 | class Module { |
470 | public: |
471 | Module() {} |
472 | virtual ~Module() |
473 | { |
474 | // TODO delete things |
475 | } |
476 | |
477 | void addFunction(Function *fun) { functions.push_back(x: fun); } |
478 | |
479 | void mapInstruction(Instruction *instruction) |
480 | { |
481 | spv::Id resultId = instruction->getResultId(); |
482 | // map the instruction's result id |
483 | if (resultId >= idToInstruction.size()) |
484 | idToInstruction.resize(new_size: resultId + 16); |
485 | idToInstruction[resultId] = instruction; |
486 | } |
487 | |
488 | Instruction* getInstruction(Id id) const { return idToInstruction[id]; } |
489 | const std::vector<Function*>& getFunctions() const { return functions; } |
490 | spv::Id getTypeId(Id resultId) const { |
491 | return idToInstruction[resultId] == nullptr ? NoType : idToInstruction[resultId]->getTypeId(); |
492 | } |
493 | StorageClass getStorageClass(Id typeId) const |
494 | { |
495 | assert(idToInstruction[typeId]->getOpCode() == spv::OpTypePointer); |
496 | return (StorageClass)idToInstruction[typeId]->getImmediateOperand(op: 0); |
497 | } |
498 | |
499 | void dump(std::vector<unsigned int>& out) const |
500 | { |
501 | for (int f = 0; f < (int)functions.size(); ++f) |
502 | functions[f]->dump(out); |
503 | } |
504 | |
505 | protected: |
506 | Module(const Module&); |
507 | std::vector<Function*> functions; |
508 | |
509 | // map from result id to instruction having that result id |
510 | std::vector<Instruction*> idToInstruction; |
511 | |
512 | // map from a result id to its type id |
513 | }; |
514 | |
515 | // |
516 | // Implementation (it's here due to circular type definitions). |
517 | // |
518 | |
519 | // Add both |
520 | // - the OpFunction instruction |
521 | // - all the OpFunctionParameter instructions |
522 | __inline Function::Function(Id id, Id resultType, Id functionType, Id firstParamId, LinkageType linkage, const std::string& name, Module& parent) |
523 | : parent(parent), lineInstruction(nullptr), |
524 | functionInstruction(id, resultType, OpFunction), implicitThis(false), |
525 | reducedPrecisionReturn(false), |
526 | linkType(linkage) |
527 | { |
528 | // OpFunction |
529 | functionInstruction.reserveOperands(count: 2); |
530 | functionInstruction.addImmediateOperand(immediate: FunctionControlMaskNone); |
531 | functionInstruction.addIdOperand(id: functionType); |
532 | parent.mapInstruction(instruction: &functionInstruction); |
533 | parent.addFunction(fun: this); |
534 | |
535 | // OpFunctionParameter |
536 | Instruction* typeInst = parent.getInstruction(id: functionType); |
537 | int numParams = typeInst->getNumOperands() - 1; |
538 | for (int p = 0; p < numParams; ++p) { |
539 | Instruction* param = new Instruction(firstParamId + p, typeInst->getIdOperand(op: p + 1), OpFunctionParameter); |
540 | parent.mapInstruction(instruction: param); |
541 | parameterInstructions.push_back(x: param); |
542 | } |
543 | |
544 | // If importing/exporting, save the function name (without the mangled parameters) for the linkage decoration |
545 | if (linkType != LinkageTypeMax) { |
546 | exportName = name.substr(pos: 0, n: name.find_first_of(c: '(')); |
547 | } |
548 | } |
549 | |
550 | __inline void Function::addLocalVariable(std::unique_ptr<Instruction> inst) |
551 | { |
552 | Instruction* raw_instruction = inst.get(); |
553 | blocks[0]->addLocalVariable(inst: std::move(inst)); |
554 | parent.mapInstruction(instruction: raw_instruction); |
555 | } |
556 | |
557 | __inline Block::Block(Id id, Function& parent) : parent(parent), unreachable(false) |
558 | { |
559 | instructions.push_back(x: std::unique_ptr<Instruction>(new Instruction(id, NoType, OpLabel))); |
560 | instructions.back()->setBlock(this); |
561 | parent.getParent().mapInstruction(instruction: instructions.back().get()); |
562 | } |
563 | |
564 | __inline void Block::addInstruction(std::unique_ptr<Instruction> inst) |
565 | { |
566 | Instruction* raw_instruction = inst.get(); |
567 | instructions.push_back(x: std::move(inst)); |
568 | raw_instruction->setBlock(this); |
569 | if (raw_instruction->getResultId()) |
570 | parent.getParent().mapInstruction(instruction: raw_instruction); |
571 | } |
572 | |
573 | } // end spv namespace |
574 | |
575 | #endif // spvIR_H |
576 | |