John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 1 | // |
John Kessenich | 6c292d3 | 2016-02-15 20:58:50 -0700 | [diff] [blame] | 2 | //Copyright (C) 2014-2015 LunarG, Inc. |
| 3 | //Copyright (C) 2015-2016 Google, Inc. |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 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 | // |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 37 | // "Builder" is an interface to fully build SPIR-V IR. Allocate one of |
| 38 | // these to build (a thread safe) internal SPIR-V representation (IR), |
| 39 | // and then dump it as a binary stream according to the SPIR-V specification. |
| 40 | // |
| 41 | // A Builder has a 1:1 relationship with a SPIR-V module. |
| 42 | // |
| 43 | |
| 44 | #pragma once |
| 45 | #ifndef SpvBuilder_H |
| 46 | #define SpvBuilder_H |
| 47 | |
Lei Zhang | 17535f7 | 2016-05-04 15:55:59 -0400 | [diff] [blame] | 48 | #include "Logger.h" |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 49 | #include "spirv.hpp" |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 50 | #include "spvIR.h" |
| 51 | |
| 52 | #include <algorithm> |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 53 | #include <map> |
Lei Zhang | 09caf12 | 2016-05-02 18:11:54 -0400 | [diff] [blame] | 54 | #include <memory> |
John Kessenich | 9218759 | 2016-02-01 13:45:25 -0700 | [diff] [blame] | 55 | #include <set> |
Lei Zhang | 09caf12 | 2016-05-02 18:11:54 -0400 | [diff] [blame] | 56 | #include <sstream> |
| 57 | #include <stack> |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 58 | |
| 59 | namespace spv { |
| 60 | |
| 61 | class Builder { |
| 62 | public: |
Lei Zhang | 17535f7 | 2016-05-04 15:55:59 -0400 | [diff] [blame] | 63 | Builder(unsigned int userNumber, SpvBuildLogger* logger); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 64 | virtual ~Builder(); |
| 65 | |
| 66 | static const int maxMatrixSize = 4; |
| 67 | |
| 68 | void setSource(spv::SourceLanguage lang, int version) |
| 69 | { |
| 70 | source = lang; |
| 71 | sourceVersion = version; |
| 72 | } |
| 73 | void addSourceExtension(const char* ext) { extensions.push_back(ext); } |
| 74 | Id import(const char*); |
| 75 | void setMemoryModel(spv::AddressingModel addr, spv::MemoryModel mem) |
| 76 | { |
| 77 | addressModel = addr; |
| 78 | memoryModel = mem; |
| 79 | } |
| 80 | |
John Kessenich | 9218759 | 2016-02-01 13:45:25 -0700 | [diff] [blame] | 81 | void addCapability(spv::Capability cap) { capabilities.insert(cap); } |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 82 | |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 83 | // To get a new <id> for anything needing a new one. |
| 84 | Id getUniqueId() { return ++uniqueId; } |
| 85 | |
| 86 | // To get a set of new <id>s, e.g., for a set of function parameters |
| 87 | Id getUniqueIds(int numIds) |
| 88 | { |
| 89 | Id id = uniqueId + 1; |
| 90 | uniqueId += numIds; |
| 91 | return id; |
| 92 | } |
| 93 | |
| 94 | // For creating new types (will return old type if the requested one was already made). |
| 95 | Id makeVoidType(); |
| 96 | Id makeBoolType(); |
| 97 | Id makePointer(StorageClass, Id type); |
| 98 | Id makeIntegerType(int width, bool hasSign); // generic |
| 99 | Id makeIntType(int width) { return makeIntegerType(width, true); } |
| 100 | Id makeUintType(int width) { return makeIntegerType(width, false); } |
| 101 | Id makeFloatType(int width); |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 102 | Id makeStructType(const std::vector<Id>& members, const char*); |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 103 | Id makeStructResultType(Id type0, Id type1); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 104 | Id makeVectorType(Id component, int size); |
| 105 | Id makeMatrixType(Id component, int cols, int rows); |
John Kessenich | 6c292d3 | 2016-02-15 20:58:50 -0700 | [diff] [blame] | 106 | Id makeArrayType(Id element, Id sizeId, int stride); // 0 stride means no stride decoration |
John Kessenich | c9a8083 | 2015-09-12 12:17:44 -0600 | [diff] [blame] | 107 | Id makeRuntimeArray(Id element); |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 108 | Id makeFunctionType(Id returnType, const std::vector<Id>& paramTypes); |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 109 | Id makeImageType(Id sampledType, Dim, bool depth, bool arrayed, bool ms, unsigned sampled, ImageFormat format); |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 110 | Id makeSamplerType(); |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 111 | Id makeSampledImageType(Id imageType); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 112 | |
| 113 | // For querying about types. |
| 114 | Id getTypeId(Id resultId) const { return module.getTypeId(resultId); } |
| 115 | Id getDerefTypeId(Id resultId) const; |
| 116 | Op getOpCode(Id id) const { return module.getInstruction(id)->getOpCode(); } |
| 117 | Op getTypeClass(Id typeId) const { return getOpCode(typeId); } |
| 118 | Op getMostBasicTypeClass(Id typeId) const; |
| 119 | int getNumComponents(Id resultId) const { return getNumTypeComponents(getTypeId(resultId)); } |
John Kessenich | 2211835 | 2015-12-21 20:54:09 -0700 | [diff] [blame] | 120 | int getNumTypeConstituents(Id typeId) const; |
| 121 | int getNumTypeComponents(Id typeId) const { return getNumTypeConstituents(typeId); } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 122 | Id getScalarTypeId(Id typeId) const; |
| 123 | Id getContainedTypeId(Id typeId) const; |
| 124 | Id getContainedTypeId(Id typeId, int) const; |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 125 | StorageClass getTypeStorageClass(Id typeId) const { return module.getStorageClass(typeId); } |
John Kessenich | 5d0fa97 | 2016-02-15 11:57:00 -0700 | [diff] [blame] | 126 | ImageFormat getImageTypeFormat(Id typeId) const { return (ImageFormat)module.getInstruction(typeId)->getImmediateOperand(6); } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 127 | |
John Kessenich | 3366145 | 2015-12-08 19:32:47 -0700 | [diff] [blame] | 128 | bool isPointer(Id resultId) const { return isPointerType(getTypeId(resultId)); } |
| 129 | bool isScalar(Id resultId) const { return isScalarType(getTypeId(resultId)); } |
| 130 | bool isVector(Id resultId) const { return isVectorType(getTypeId(resultId)); } |
| 131 | bool isMatrix(Id resultId) const { return isMatrixType(getTypeId(resultId)); } |
| 132 | bool isAggregate(Id resultId) const { return isAggregateType(getTypeId(resultId)); } |
| 133 | bool isSampledImage(Id resultId) const { return isSampledImageType(getTypeId(resultId)); } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 134 | |
John Kessenich | 3366145 | 2015-12-08 19:32:47 -0700 | [diff] [blame] | 135 | bool isBoolType(Id typeId) const { return groupedTypes[OpTypeBool].size() > 0 && typeId == groupedTypes[OpTypeBool].back()->getResultId(); } |
| 136 | bool isPointerType(Id typeId) const { return getTypeClass(typeId) == OpTypePointer; } |
| 137 | bool isScalarType(Id typeId) const { return getTypeClass(typeId) == OpTypeFloat || getTypeClass(typeId) == OpTypeInt || getTypeClass(typeId) == OpTypeBool; } |
| 138 | bool isVectorType(Id typeId) const { return getTypeClass(typeId) == OpTypeVector; } |
| 139 | bool isMatrixType(Id typeId) const { return getTypeClass(typeId) == OpTypeMatrix; } |
| 140 | bool isStructType(Id typeId) const { return getTypeClass(typeId) == OpTypeStruct; } |
| 141 | bool isArrayType(Id typeId) const { return getTypeClass(typeId) == OpTypeArray; } |
| 142 | bool isAggregateType(Id typeId) const { return isArrayType(typeId) || isStructType(typeId); } |
| 143 | bool isImageType(Id typeId) const { return getTypeClass(typeId) == OpTypeImage; } |
| 144 | bool isSamplerType(Id typeId) const { return getTypeClass(typeId) == OpTypeSampler; } |
| 145 | bool isSampledImageType(Id typeId) const { return getTypeClass(typeId) == OpTypeSampledImage; } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 146 | |
John Kessenich | 7163127 | 2015-10-13 10:39:19 -0600 | [diff] [blame] | 147 | bool isConstantOpCode(Op opcode) const; |
qining | 27e04a0 | 2016-04-14 16:40:20 -0400 | [diff] [blame] | 148 | bool isSpecConstantOpCode(Op opcode) const; |
John Kessenich | 7163127 | 2015-10-13 10:39:19 -0600 | [diff] [blame] | 149 | bool isConstant(Id resultId) const { return isConstantOpCode(getOpCode(resultId)); } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 150 | bool isConstantScalar(Id resultId) const { return getOpCode(resultId) == OpConstant; } |
qining | 27e04a0 | 2016-04-14 16:40:20 -0400 | [diff] [blame] | 151 | bool isSpecConstant(Id resultId) const { return isSpecConstantOpCode(getOpCode(resultId)); } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 152 | unsigned int getConstantScalar(Id resultId) const { return module.getInstruction(resultId)->getImmediateOperand(0); } |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 153 | StorageClass getStorageClass(Id resultId) const { return getTypeStorageClass(getTypeId(resultId)); } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 154 | |
| 155 | int getTypeNumColumns(Id typeId) const |
| 156 | { |
| 157 | assert(isMatrixType(typeId)); |
John Kessenich | 2211835 | 2015-12-21 20:54:09 -0700 | [diff] [blame] | 158 | return getNumTypeConstituents(typeId); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 159 | } |
| 160 | int getNumColumns(Id resultId) const { return getTypeNumColumns(getTypeId(resultId)); } |
| 161 | int getTypeNumRows(Id typeId) const |
| 162 | { |
| 163 | assert(isMatrixType(typeId)); |
| 164 | return getNumTypeComponents(getContainedTypeId(typeId)); |
| 165 | } |
| 166 | int getNumRows(Id resultId) const { return getTypeNumRows(getTypeId(resultId)); } |
| 167 | |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 168 | Dim getTypeDimensionality(Id typeId) const |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 169 | { |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 170 | assert(isImageType(typeId)); |
| 171 | return (Dim)module.getInstruction(typeId)->getImmediateOperand(1); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 172 | } |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 173 | Id getImageType(Id resultId) const |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 174 | { |
Rex Xu | 6b86d49 | 2015-09-16 17:48:22 +0800 | [diff] [blame] | 175 | Id typeId = getTypeId(resultId); |
| 176 | assert(isImageType(typeId) || isSampledImageType(typeId)); |
| 177 | return isSampledImageType(typeId) ? module.getInstruction(typeId)->getIdOperand(0) : typeId; |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 178 | } |
| 179 | bool isArrayedImageType(Id typeId) const |
| 180 | { |
| 181 | assert(isImageType(typeId)); |
| 182 | return module.getInstruction(typeId)->getImmediateOperand(3) != 0; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 183 | } |
| 184 | |
| 185 | // For making new constants (will return old constant if the requested one was already made). |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 186 | Id makeBoolConstant(bool b, bool specConstant = false); |
| 187 | Id makeIntConstant(int i, bool specConstant = false) { return makeIntConstant(makeIntType(32), (unsigned)i, specConstant); } |
| 188 | Id makeUintConstant(unsigned u, bool specConstant = false) { return makeIntConstant(makeUintType(32), u, specConstant); } |
Rex Xu | 8ff43de | 2016-04-22 16:51:45 +0800 | [diff] [blame] | 189 | Id makeInt64Constant(long long i, bool specConstant = false) { return makeInt64Constant(makeIntType(64), (unsigned long long)i, specConstant); } |
| 190 | Id makeUint64Constant(unsigned long long u, bool specConstant = false) { return makeInt64Constant(makeUintType(64), u, specConstant); } |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 191 | Id makeFloatConstant(float f, bool specConstant = false); |
| 192 | Id makeDoubleConstant(double d, bool specConstant = false); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 193 | |
| 194 | // Turn the array of constants into a proper spv constant of the requested type. |
John Kessenich | 6c292d3 | 2016-02-15 20:58:50 -0700 | [diff] [blame] | 195 | Id makeCompositeConstant(Id type, std::vector<Id>& comps, bool specConst = false); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 196 | |
| 197 | // Methods for adding information outside the CFG. |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 198 | Instruction* addEntryPoint(ExecutionModel, Function*, const char* name); |
John Kessenich | b56a26a | 2015-09-16 16:04:05 -0600 | [diff] [blame] | 199 | void addExecutionMode(Function*, ExecutionMode mode, int value1 = -1, int value2 = -1, int value3 = -1); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 200 | void addName(Id, const char* name); |
| 201 | void addMemberName(Id, int member, const char* name); |
| 202 | void addLine(Id target, Id fileName, int line, int column); |
| 203 | void addDecoration(Id, Decoration, int num = -1); |
| 204 | void addMemberDecoration(Id, unsigned int member, Decoration, int num = -1); |
| 205 | |
| 206 | // At the end of what block do the next create*() instructions go? |
| 207 | void setBuildPoint(Block* bp) { buildPoint = bp; } |
| 208 | Block* getBuildPoint() const { return buildPoint; } |
| 209 | |
John Kessenich | 4d65ee3 | 2016-03-12 18:17:47 -0700 | [diff] [blame] | 210 | // Make the entry-point function. The returned pointer is only valid |
Andrew Woloszyn | b7946d1 | 2016-01-18 09:23:56 -0500 | [diff] [blame] | 211 | // for the lifetime of this builder. |
John Kessenich | 4d65ee3 | 2016-03-12 18:17:47 -0700 | [diff] [blame] | 212 | Function* makeEntrypoint(const char*); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 213 | |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 214 | // Make a shader-style function, and create its entry block if entry is non-zero. |
| 215 | // Return the function, pass back the entry. |
Andrew Woloszyn | b7946d1 | 2016-01-18 09:23:56 -0500 | [diff] [blame] | 216 | // The returned pointer is only valid for the lifetime of this builder. |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 217 | Function* makeFunctionEntry(Decoration precision, Id returnType, const char* name, const std::vector<Id>& paramTypes, |
| 218 | const std::vector<Decoration>& precisions, Block **entry = 0); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 219 | |
John Kessenich | e770b3e | 2015-09-14 20:58:02 -0600 | [diff] [blame] | 220 | // Create a return. An 'implicit' return is one not appearing in the source |
| 221 | // code. In the case of an implicit return, no post-return block is inserted. |
| 222 | void makeReturn(bool implicit, Id retVal = 0); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 223 | |
| 224 | // Generate all the code needed to finish up a function. |
John Kessenich | e770b3e | 2015-09-14 20:58:02 -0600 | [diff] [blame] | 225 | void leaveFunction(); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 226 | |
| 227 | // Create a discard. |
| 228 | void makeDiscard(); |
| 229 | |
| 230 | // Create a global or function local or IO variable. |
| 231 | Id createVariable(StorageClass, Id type, const char* name = 0); |
| 232 | |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 233 | // Create an intermediate with an undefined value. |
Miro Knejp | 28f9b1c | 2015-08-11 02:45:24 +0200 | [diff] [blame] | 234 | Id createUndefined(Id type); |
| 235 | |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 236 | // Store into an Id and return the l-value |
| 237 | void createStore(Id rValue, Id lValue); |
| 238 | |
| 239 | // Load from an Id and return it |
| 240 | Id createLoad(Id lValue); |
| 241 | |
| 242 | // Create an OpAccessChain instruction |
| 243 | Id createAccessChain(StorageClass, Id base, std::vector<Id>& offsets); |
| 244 | |
John Kessenich | ee21fc9 | 2015-09-21 21:50:29 -0600 | [diff] [blame] | 245 | // Create an OpArrayLength instruction |
| 246 | Id createArrayLength(Id base, unsigned int member); |
| 247 | |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 248 | // Create an OpCompositeExtract instruction |
| 249 | Id createCompositeExtract(Id composite, Id typeId, unsigned index); |
| 250 | Id createCompositeExtract(Id composite, Id typeId, std::vector<unsigned>& indexes); |
| 251 | Id createCompositeInsert(Id object, Id composite, Id typeId, unsigned index); |
| 252 | Id createCompositeInsert(Id object, Id composite, Id typeId, std::vector<unsigned>& indexes); |
| 253 | |
| 254 | Id createVectorExtractDynamic(Id vector, Id typeId, Id componentIndex); |
| 255 | Id createVectorInsertDynamic(Id vector, Id typeId, Id component, Id componentIndex); |
| 256 | |
| 257 | void createNoResultOp(Op); |
| 258 | void createNoResultOp(Op, Id operand); |
Rex Xu | fc61891 | 2015-09-09 16:42:49 +0800 | [diff] [blame] | 259 | void createNoResultOp(Op, const std::vector<Id>& operands); |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 260 | void createControlBarrier(Scope execution, Scope memory, MemorySemanticsMask); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 261 | void createMemoryBarrier(unsigned executionScope, unsigned memorySemantics); |
| 262 | Id createUnaryOp(Op, Id typeId, Id operand); |
| 263 | Id createBinOp(Op, Id typeId, Id operand1, Id operand2); |
| 264 | Id createTriOp(Op, Id typeId, Id operand1, Id operand2, Id operand3); |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 265 | Id createOp(Op, Id typeId, const std::vector<Id>& operands); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 266 | Id createFunctionCall(spv::Function*, std::vector<spv::Id>&); |
qining | 1354520 | 2016-03-21 09:51:37 -0400 | [diff] [blame] | 267 | Id createSpecConstantOp(Op, Id typeId, const std::vector<spv::Id>& operands, const std::vector<unsigned>& literals); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 268 | |
| 269 | // Take an rvalue (source) and a set of channels to extract from it to |
| 270 | // make a new rvalue, which is returned. |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 271 | Id createRvalueSwizzle(Decoration precision, Id typeId, Id source, std::vector<unsigned>& channels); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 272 | |
| 273 | // Take a copy of an lvalue (target) and a source of components, and set the |
| 274 | // source components into the lvalue where the 'channels' say to put them. |
| 275 | // An updated version of the target is returned. |
| 276 | // (No true lvalue or stores are used.) |
| 277 | Id createLvalueSwizzle(Id typeId, Id target, Id source, std::vector<unsigned>& channels); |
| 278 | |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 279 | // If both the id and precision are valid, the id |
| 280 | // gets tagged with the requested precision. |
| 281 | // The passed in id is always the returned id, to simplify use patterns. |
| 282 | Id setPrecision(Id id, Decoration precision) |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 283 | { |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 284 | if (precision != NoPrecision && id != NoResult) |
| 285 | addDecoration(id, precision); |
| 286 | |
| 287 | return id; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 288 | } |
| 289 | |
| 290 | // Can smear a scalar to a vector for the following forms: |
| 291 | // - promoteScalar(scalar, vector) // smear scalar to width of vector |
| 292 | // - promoteScalar(vector, scalar) // smear scalar to width of vector |
| 293 | // - promoteScalar(pointer, scalar) // smear scalar to width of what pointer points to |
| 294 | // - promoteScalar(scalar, scalar) // do nothing |
| 295 | // Other forms are not allowed. |
| 296 | // |
John Kessenich | 76f7139 | 2015-12-09 19:08:42 -0700 | [diff] [blame] | 297 | // Generally, the type of 'scalar' does not need to be the same type as the components in 'vector'. |
| 298 | // The type of the created vector is a vector of components of the same type as the scalar. |
| 299 | // |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 300 | // Note: One of the arguments will change, with the result coming back that way rather than |
| 301 | // through the return value. |
| 302 | void promoteScalar(Decoration precision, Id& left, Id& right); |
| 303 | |
John Kessenich | 76f7139 | 2015-12-09 19:08:42 -0700 | [diff] [blame] | 304 | // Make a value by smearing the scalar to fill the type. |
| 305 | // vectorType should be the correct type for making a vector of scalarVal. |
| 306 | // (No conversions are done.) |
| 307 | Id smearScalar(Decoration precision, Id scalarVal, Id vectorType); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 308 | |
| 309 | // Create a call to a built-in function. |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 310 | Id createBuiltinCall(Id resultType, Id builtins, int entryPoint, std::vector<Id>& args); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 311 | |
| 312 | // List of parameters used to create a texture operation |
| 313 | struct TextureParameters { |
| 314 | Id sampler; |
| 315 | Id coords; |
| 316 | Id bias; |
| 317 | Id lod; |
| 318 | Id Dref; |
| 319 | Id offset; |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 320 | Id offsets; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 321 | Id gradX; |
| 322 | Id gradY; |
John Kessenich | 5e4b124 | 2015-08-06 22:53:06 -0600 | [diff] [blame] | 323 | Id sample; |
John Kessenich | 76d4dfc | 2016-06-16 12:43:23 -0600 | [diff] [blame] | 324 | Id component; |
Rex Xu | 48edadf | 2015-12-31 16:11:41 +0800 | [diff] [blame] | 325 | Id texelOut; |
| 326 | Id lodClamp; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 327 | }; |
| 328 | |
| 329 | // Select the correct texture operation based on all inputs, and emit the correct instruction |
John Kessenich | 019f08f | 2016-02-15 15:40:42 -0700 | [diff] [blame] | 330 | Id createTextureCall(Decoration precision, Id resultType, bool sparse, bool fetch, bool proj, bool gather, bool noImplicit, const TextureParameters&); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 331 | |
| 332 | // Emit the OpTextureQuery* instruction that was passed in. |
| 333 | // Figure out the right return value and type, and return it. |
| 334 | Id createTextureQueryCall(Op, const TextureParameters&); |
| 335 | |
| 336 | Id createSamplePositionCall(Decoration precision, Id, Id); |
| 337 | |
| 338 | Id createBitFieldExtractCall(Decoration precision, Id, Id, Id, bool isSigned); |
| 339 | Id createBitFieldInsertCall(Decoration precision, Id, Id, Id, Id); |
| 340 | |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 341 | // Reduction comparison for composites: For equal and not-equal resulting in a scalar. |
John Kessenich | 2211835 | 2015-12-21 20:54:09 -0700 | [diff] [blame] | 342 | Id createCompositeCompare(Decoration precision, Id, Id, bool /* true if for equal, false if for not-equal */); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 343 | |
| 344 | // OpCompositeConstruct |
| 345 | Id createCompositeConstruct(Id typeId, std::vector<Id>& constituents); |
| 346 | |
| 347 | // vector or scalar constructor |
| 348 | Id createConstructor(Decoration precision, const std::vector<Id>& sources, Id resultTypeId); |
| 349 | |
| 350 | // matrix constructor |
| 351 | Id createMatrixConstructor(Decoration precision, const std::vector<Id>& sources, Id constructee); |
| 352 | |
| 353 | // Helper to use for building nested control flow with if-then-else. |
| 354 | class If { |
| 355 | public: |
| 356 | If(Id condition, Builder& builder); |
| 357 | ~If() {} |
| 358 | |
| 359 | void makeBeginElse(); |
| 360 | void makeEndIf(); |
| 361 | |
| 362 | private: |
| 363 | If(const If&); |
| 364 | If& operator=(If&); |
| 365 | |
| 366 | Builder& builder; |
| 367 | Id condition; |
| 368 | Function* function; |
| 369 | Block* headerBlock; |
| 370 | Block* thenBlock; |
| 371 | Block* elseBlock; |
| 372 | Block* mergeBlock; |
| 373 | }; |
| 374 | |
| 375 | // Make a switch statement. A switch has 'numSegments' of pieces of code, not containing |
| 376 | // any case/default labels, all separated by one or more case/default labels. Each possible |
| 377 | // case value v is a jump to the caseValues[v] segment. The defaultSegment is also in this |
| 378 | // number space. How to compute the value is given by 'condition', as in switch(condition). |
| 379 | // |
| 380 | // The SPIR-V Builder will maintain the stack of post-switch merge blocks for nested switches. |
| 381 | // |
| 382 | // Use a defaultSegment < 0 if there is no default segment (to branch to post switch). |
| 383 | // |
| 384 | // Returns the right set of basic blocks to start each code segment with, so that the caller's |
| 385 | // recursion stack can hold the memory for it. |
| 386 | // |
| 387 | void makeSwitch(Id condition, int numSegments, std::vector<int>& caseValues, std::vector<int>& valueToSegment, int defaultSegment, |
| 388 | std::vector<Block*>& segmentBB); // return argument |
| 389 | |
| 390 | // Add a branch to the innermost switch's merge block. |
| 391 | void addSwitchBreak(); |
| 392 | |
| 393 | // Move to the next code segment, passing in the return argument in makeSwitch() |
| 394 | void nextSwitchSegment(std::vector<Block*>& segmentBB, int segment); |
| 395 | |
| 396 | // Finish off the innermost switch. |
| 397 | void endSwitch(std::vector<Block*>& segmentBB); |
| 398 | |
Dejan Mircevski | 9c6734c | 2016-01-10 12:15:13 -0500 | [diff] [blame] | 399 | struct LoopBlocks { |
John Kessenich | 7f349c7 | 2016-07-08 22:09:10 -0600 | [diff] [blame^] | 400 | LoopBlocks(Block& head, Block& body, Block& merge, Block& continue_target) : |
| 401 | head(head), body(body), merge(merge), continue_target(continue_target) { } |
Dejan Mircevski | 832c65c | 2016-01-11 15:57:11 -0500 | [diff] [blame] | 402 | Block &head, &body, &merge, &continue_target; |
John Kessenich | 7f349c7 | 2016-07-08 22:09:10 -0600 | [diff] [blame^] | 403 | private: |
| 404 | LoopBlocks(); |
| 405 | LoopBlocks& operator=(const LoopBlocks&); |
Dejan Mircevski | 9c6734c | 2016-01-10 12:15:13 -0500 | [diff] [blame] | 406 | }; |
Dejan Mircevski | 9c6734c | 2016-01-10 12:15:13 -0500 | [diff] [blame] | 407 | |
Dejan Mircevski | 7819bee | 2016-01-11 09:35:22 -0500 | [diff] [blame] | 408 | // Start a new loop and prepare the builder to generate code for it. Until |
| 409 | // closeLoop() is called for this loop, createLoopContinue() and |
| 410 | // createLoopExit() will target its corresponding blocks. |
| 411 | LoopBlocks& makeNewLoop(); |
| 412 | |
| 413 | // Create a new block in the function containing the build point. Memory is |
| 414 | // owned by the function object. |
Dejan Mircevski | 9c6734c | 2016-01-10 12:15:13 -0500 | [diff] [blame] | 415 | Block& makeNewBlock(); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 416 | |
Dejan Mircevski | 7819bee | 2016-01-11 09:35:22 -0500 | [diff] [blame] | 417 | // Add a branch to the continue_target of the current (innermost) loop. |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 418 | void createLoopContinue(); |
| 419 | |
Dejan Mircevski | 7819bee | 2016-01-11 09:35:22 -0500 | [diff] [blame] | 420 | // Add an exit (e.g. "break") from the innermost loop that we're currently |
| 421 | // in. |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 422 | void createLoopExit(); |
| 423 | |
| 424 | // Close the innermost loop that you're in |
| 425 | void closeLoop(); |
| 426 | |
| 427 | // |
| 428 | // Access chain design for an R-Value vs. L-Value: |
| 429 | // |
| 430 | // There is a single access chain the builder is building at |
| 431 | // any particular time. Such a chain can be used to either to a load or |
| 432 | // a store, when desired. |
| 433 | // |
| 434 | // Expressions can be r-values, l-values, or both, or only r-values: |
| 435 | // a[b.c].d = .... // l-value |
| 436 | // ... = a[b.c].d; // r-value, that also looks like an l-value |
| 437 | // ++a[b.c].d; // r-value and l-value |
| 438 | // (x + y)[2]; // r-value only, can't possibly be l-value |
| 439 | // |
| 440 | // Computing an r-value means generating code. Hence, |
| 441 | // r-values should only be computed when they are needed, not speculatively. |
| 442 | // |
| 443 | // Computing an l-value means saving away information for later use in the compiler, |
| 444 | // no code is generated until the l-value is later dereferenced. It is okay |
| 445 | // to speculatively generate an l-value, just not okay to speculatively dereference it. |
| 446 | // |
| 447 | // The base of the access chain (the left-most variable or expression |
| 448 | // from which everything is based) can be set either as an l-value |
| 449 | // or as an r-value. Most efficient would be to set an l-value if one |
| 450 | // is available. If an expression was evaluated, the resulting r-value |
| 451 | // can be set as the chain base. |
| 452 | // |
| 453 | // The users of this single access chain can save and restore if they |
| 454 | // want to nest or manage multiple chains. |
| 455 | // |
| 456 | |
| 457 | struct AccessChain { |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 458 | Id base; // for l-values, pointer to the base object, for r-values, the base object |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 459 | std::vector<Id> indexChain; |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 460 | Id instr; // cache the instruction that generates this access chain |
| 461 | std::vector<unsigned> swizzle; // each std::vector element selects the next GLSL component number |
| 462 | Id component; // a dynamic component index, can coexist with a swizzle, done after the swizzle, NoResult if not present |
| 463 | Id preSwizzleBaseType; // dereferenced type, before swizzle or component is applied; NoType unless a swizzle or component is present |
| 464 | bool isRValue; // true if 'base' is an r-value, otherwise, base is an l-value |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 465 | }; |
| 466 | |
| 467 | // |
| 468 | // the SPIR-V builder maintains a single active chain that |
| 469 | // the following methods operated on |
| 470 | // |
| 471 | |
| 472 | // for external save and restore |
| 473 | AccessChain getAccessChain() { return accessChain; } |
| 474 | void setAccessChain(AccessChain newChain) { accessChain = newChain; } |
| 475 | |
| 476 | // clear accessChain |
| 477 | void clearAccessChain(); |
| 478 | |
| 479 | // set new base as an l-value base |
| 480 | void setAccessChainLValue(Id lValue) |
| 481 | { |
| 482 | assert(isPointer(lValue)); |
| 483 | accessChain.base = lValue; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 484 | } |
| 485 | |
| 486 | // set new base value as an r-value |
| 487 | void setAccessChainRValue(Id rValue) |
| 488 | { |
| 489 | accessChain.isRValue = true; |
| 490 | accessChain.base = rValue; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 491 | } |
| 492 | |
| 493 | // push offset onto the end of the chain |
John Kessenich | fa668da | 2015-09-13 14:46:30 -0600 | [diff] [blame] | 494 | void accessChainPush(Id offset) |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 495 | { |
| 496 | accessChain.indexChain.push_back(offset); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 497 | } |
| 498 | |
| 499 | // push new swizzle onto the end of any existing swizzle, merging into a single swizzle |
John Kessenich | fa668da | 2015-09-13 14:46:30 -0600 | [diff] [blame] | 500 | void accessChainPushSwizzle(std::vector<unsigned>& swizzle, Id preSwizzleBaseType); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 501 | |
| 502 | // push a variable component selection onto the access chain; supporting only one, so unsided |
John Kessenich | fa668da | 2015-09-13 14:46:30 -0600 | [diff] [blame] | 503 | void accessChainPushComponent(Id component, Id preSwizzleBaseType) |
| 504 | { |
| 505 | accessChain.component = component; |
| 506 | if (accessChain.preSwizzleBaseType == NoType) |
| 507 | accessChain.preSwizzleBaseType = preSwizzleBaseType; |
| 508 | } |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 509 | |
| 510 | // use accessChain and swizzle to store value |
| 511 | void accessChainStore(Id rvalue); |
| 512 | |
| 513 | // use accessChain and swizzle to load an r-value |
John Kessenich | 32cfd49 | 2016-02-02 12:37:46 -0700 | [diff] [blame] | 514 | Id accessChainLoad(Decoration precision, Id ResultType); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 515 | |
| 516 | // get the direct pointer for an l-value |
| 517 | Id accessChainGetLValue(); |
| 518 | |
John Kessenich | 103bef9 | 2016-02-08 21:38:15 -0700 | [diff] [blame] | 519 | // Get the inferred SPIR-V type of the result of the current access chain, |
| 520 | // based on the type of the base and the chain of dereferences. |
| 521 | Id accessChainGetInferredType(); |
| 522 | |
qining | da39733 | 2016-03-09 19:54:03 -0500 | [diff] [blame] | 523 | // Remove OpDecorate instructions whose operands are defined in unreachable |
| 524 | // blocks. |
| 525 | void eliminateDeadDecorations(); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 526 | void dump(std::vector<unsigned int>&) const; |
| 527 | |
Dejan Mircevski | 9c6734c | 2016-01-10 12:15:13 -0500 | [diff] [blame] | 528 | void createBranch(Block* block); |
| 529 | void createConditionalBranch(Id condition, Block* thenBlock, Block* elseBlock); |
| 530 | void createLoopMerge(Block* mergeBlock, Block* continueBlock, unsigned int control); |
| 531 | |
qining | 1354520 | 2016-03-21 09:51:37 -0400 | [diff] [blame] | 532 | // Sets to generate opcode for specialization constants. |
| 533 | void setToSpecConstCodeGenMode() { generatingOpCodeForSpecConst = true; } |
| 534 | // Sets to generate opcode for non-specialization constants (normal mode). |
| 535 | void setToNormalCodeGenMode() { generatingOpCodeForSpecConst = false; } |
| 536 | // Check if the builder is generating code for spec constants. |
| 537 | bool isInSpecConstCodeGenMode() { return generatingOpCodeForSpecConst; } |
| 538 | |
Dejan Mircevski | 9c6734c | 2016-01-10 12:15:13 -0500 | [diff] [blame] | 539 | protected: |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 540 | Id makeIntConstant(Id typeId, unsigned value, bool specConstant); |
Rex Xu | 8ff43de | 2016-04-22 16:51:45 +0800 | [diff] [blame] | 541 | Id makeInt64Constant(Id typeId, unsigned long long value, bool specConstant); |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 542 | Id findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned value) const; |
| 543 | Id findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned v1, unsigned v2) const; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 544 | Id findCompositeConstant(Op typeClass, std::vector<Id>& comps) const; |
| 545 | Id collapseAccessChain(); |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 546 | void transferAccessChainSwizzle(bool dynamic); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 547 | void simplifyAccessChainSwizzle(); |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 548 | void createAndSetNoPredecessorBlock(const char*); |
John Kessenich | 55e7d11 | 2015-11-15 21:33:39 -0700 | [diff] [blame] | 549 | void createSelectionMerge(Block* mergeBlock, unsigned int control); |
Andrew Woloszyn | b7946d1 | 2016-01-18 09:23:56 -0500 | [diff] [blame] | 550 | void dumpInstructions(std::vector<unsigned int>&, const std::vector<std::unique_ptr<Instruction> >&) const; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 551 | |
| 552 | SourceLanguage source; |
| 553 | int sourceVersion; |
| 554 | std::vector<const char*> extensions; |
| 555 | AddressingModel addressModel; |
| 556 | MemoryModel memoryModel; |
John Kessenich | 9218759 | 2016-02-01 13:45:25 -0700 | [diff] [blame] | 557 | std::set<spv::Capability> capabilities; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 558 | int builderNumber; |
| 559 | Module module; |
| 560 | Block* buildPoint; |
| 561 | Id uniqueId; |
| 562 | Function* mainFunction; |
qining | 1354520 | 2016-03-21 09:51:37 -0400 | [diff] [blame] | 563 | bool generatingOpCodeForSpecConst; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 564 | AccessChain accessChain; |
| 565 | |
| 566 | // special blocks of instructions for output |
Andrew Woloszyn | b7946d1 | 2016-01-18 09:23:56 -0500 | [diff] [blame] | 567 | std::vector<std::unique_ptr<Instruction> > imports; |
| 568 | std::vector<std::unique_ptr<Instruction> > entryPoints; |
| 569 | std::vector<std::unique_ptr<Instruction> > executionModes; |
| 570 | std::vector<std::unique_ptr<Instruction> > names; |
| 571 | std::vector<std::unique_ptr<Instruction> > lines; |
| 572 | std::vector<std::unique_ptr<Instruction> > decorations; |
| 573 | std::vector<std::unique_ptr<Instruction> > constantsTypesGlobals; |
| 574 | std::vector<std::unique_ptr<Instruction> > externals; |
| 575 | std::vector<std::unique_ptr<Function> > functions; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 576 | |
| 577 | // not output, internally used for quick & dirty canonical (unique) creation |
| 578 | std::vector<Instruction*> groupedConstants[OpConstant]; // all types appear before OpConstant |
| 579 | std::vector<Instruction*> groupedTypes[OpConstant]; |
| 580 | |
| 581 | // stack of switches |
| 582 | std::stack<Block*> switchMerges; |
| 583 | |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 584 | // Our loop stack. |
Dejan Mircevski | 7819bee | 2016-01-11 09:35:22 -0500 | [diff] [blame] | 585 | std::stack<LoopBlocks> loops; |
Lei Zhang | 09caf12 | 2016-05-02 18:11:54 -0400 | [diff] [blame] | 586 | |
| 587 | // The stream for outputing warnings and errors. |
Lei Zhang | 17535f7 | 2016-05-04 15:55:59 -0400 | [diff] [blame] | 588 | SpvBuildLogger* logger; |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 589 | }; // end Builder class |
| 590 | |
John Kessenich | 140f3df | 2015-06-26 16:58:36 -0600 | [diff] [blame] | 591 | }; // end spv namespace |
| 592 | |
| 593 | #endif // SpvBuilder_H |