1 // Copyright (c) 2016 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef TEST_OPT_ASSEMBLY_BUILDER_H_ 16 #define TEST_OPT_ASSEMBLY_BUILDER_H_ 17 18 #include <algorithm> 19 #include <cstdint> 20 #include <sstream> 21 #include <string> 22 #include <unordered_set> 23 #include <vector> 24 25 namespace spvtools { 26 namespace opt { 27 28 // A simple SPIR-V assembly code builder for test uses. It builds an SPIR-V 29 // assembly module from vectors of assembly strings. It allows users to add 30 // instructions to the main function and the type-constants-globals section 31 // directly. It relies on OpName instructions and friendly-name disassembling 32 // to keep the ID names unchanged after assembling. 33 // 34 // An assembly module is divided into several sections, matching with the 35 // SPIR-V Logical Layout: 36 // Global Preamble: 37 // OpCapability instructions; 38 // OpExtension instructions and OpExtInstImport instructions; 39 // OpMemoryModel instruction; 40 // OpEntryPoint and OpExecutionMode instruction; 41 // OpString, OpSourceExtension, OpSource and OpSourceContinued instructions. 42 // Names: 43 // OpName instructions. 44 // Annotations: 45 // OpDecorate, OpMemberDecorate, OpGroupDecorate, OpGroupMemberDecorate and 46 // OpDecorationGroup. 47 // Types, Constants and Global variables: 48 // Types, constants and global variables declaration instructions. 49 // Main Function: 50 // Main function instructions. 51 // Main Function Postamble: 52 // The return and function end instructions. 53 // 54 // The assembly code is built by concatenating all the strings in the above 55 // sections. 56 // 57 // Users define the contents in section <Type, Constants and Global Variables> 58 // and <Main Function>. The <Names> section is to hold the names for IDs to 59 // keep them unchanged before and after assembling. All defined IDs to be added 60 // to this code builder will be assigned with a global name through OpName 61 // instruction. The name is extracted from the definition instruction. 62 // E.g. adding instruction: %var_a = OpConstant %int 2, will also add an 63 // instruction: OpName %var_a, "var_a". 64 // 65 // Note that the name must not be used on more than one defined IDs and 66 // friendly-name disassembling must be enabled so that OpName instructions will 67 // be respected. 68 class AssemblyBuilder { 69 // The base ID value for spec constants. 70 static const uint32_t SPEC_ID_BASE = 200; 71 72 public: 73 // Initialize a minimal SPIR-V assembly code as the template. The minimal 74 // module contains an empty main function and some predefined names for the 75 // main function. AssemblyBuilder()76 AssemblyBuilder() 77 : spec_id_counter_(SPEC_ID_BASE), 78 global_preamble_({ 79 // clang-format off 80 "OpCapability Shader", 81 "OpCapability Float64", 82 "%1 = OpExtInstImport \"GLSL.std.450\"", 83 "OpMemoryModel Logical GLSL450", 84 "OpEntryPoint Vertex %main \"main\"", 85 // clang-format on 86 }), 87 names_(), 88 annotations_(), 89 types_consts_globals_(), 90 main_func_(), 91 main_func_postamble_({ 92 "OpReturn", 93 "OpFunctionEnd", 94 }) { 95 AppendTypesConstantsGlobals({ 96 "%void = OpTypeVoid", 97 "%main_func_type = OpTypeFunction %void", 98 }); 99 AppendInMain({ 100 "%main = OpFunction %void None %main_func_type", 101 "%main_func_entry_block = OpLabel", 102 }); 103 } 104 105 // Appends OpName instructions to this builder. Instruction strings that do 106 // not start with 'OpName ' will be skipped. Returns the references of this 107 // assembly builder. AppendNames(const std::vector<std::string> & vec_asm_code)108 AssemblyBuilder& AppendNames(const std::vector<std::string>& vec_asm_code) { 109 for (auto& inst_str : vec_asm_code) { 110 if (inst_str.find("OpName ") == 0) { 111 names_.push_back(inst_str); 112 } 113 } 114 return *this; 115 } 116 117 // Appends instructions to the types-constants-globals section and returns 118 // the reference of this assembly builder. IDs defined in the given code will 119 // be added to the Names section and then be registered with OpName 120 // instruction. Corresponding decoration instruction will be added for spec 121 // constants defined with opcode: 'OpSpecConstant'. AppendTypesConstantsGlobals(const std::vector<std::string> & vec_asm_code)122 AssemblyBuilder& AppendTypesConstantsGlobals( 123 const std::vector<std::string>& vec_asm_code) { 124 AddNamesForResultIDsIn(vec_asm_code); 125 // Check spec constants defined with OpSpecConstant. 126 for (auto& inst_str : vec_asm_code) { 127 if (inst_str.find("= OpSpecConstant ") != std::string::npos || 128 inst_str.find("= OpSpecConstantTrue ") != std::string::npos || 129 inst_str.find("= OpSpecConstantFalse ") != std::string::npos) { 130 AddSpecIDFor(GetResultIDName(inst_str)); 131 } 132 } 133 types_consts_globals_.insert(types_consts_globals_.end(), 134 vec_asm_code.begin(), vec_asm_code.end()); 135 return *this; 136 } 137 138 // Appends instructions to the main function block, which is already labelled 139 // with "main_func_entry_block". Returns the reference of this assembly 140 // builder. IDs defined in the given code will be added to the Names section 141 // and then be registered with OpName instruction. AppendInMain(const std::vector<std::string> & vec_asm_code)142 AssemblyBuilder& AppendInMain(const std::vector<std::string>& vec_asm_code) { 143 AddNamesForResultIDsIn(vec_asm_code); 144 main_func_.insert(main_func_.end(), vec_asm_code.begin(), 145 vec_asm_code.end()); 146 return *this; 147 } 148 149 // Appends annotation instructions to the annotation section, and returns the 150 // reference of this assembly builder. AppendAnnotations(const std::vector<std::string> & vec_annotations)151 AssemblyBuilder& AppendAnnotations( 152 const std::vector<std::string>& vec_annotations) { 153 annotations_.insert(annotations_.end(), vec_annotations.begin(), 154 vec_annotations.end()); 155 return *this; 156 } 157 158 // Pre-pends string to the preamble of the module. Useful for EFFCEE checks. PrependPreamble(const std::vector<std::string> & preamble)159 AssemblyBuilder& PrependPreamble(const std::vector<std::string>& preamble) { 160 preamble_.insert(preamble_.end(), preamble.begin(), preamble.end()); 161 return *this; 162 } 163 164 // Get the SPIR-V assembly code as string. GetCode()165 std::string GetCode() const { 166 std::ostringstream ss; 167 for (const auto& line : preamble_) { 168 ss << line << std::endl; 169 } 170 for (const auto& line : global_preamble_) { 171 ss << line << std::endl; 172 } 173 for (const auto& line : names_) { 174 ss << line << std::endl; 175 } 176 for (const auto& line : annotations_) { 177 ss << line << std::endl; 178 } 179 for (const auto& line : types_consts_globals_) { 180 ss << line << std::endl; 181 } 182 for (const auto& line : main_func_) { 183 ss << line << std::endl; 184 } 185 for (const auto& line : main_func_postamble_) { 186 ss << line << std::endl; 187 } 188 return ss.str(); 189 } 190 191 private: 192 // Adds a given name to the Name section with OpName. If the given name has 193 // been added before, does nothing. AddOpNameIfNotExist(const std::string & id_name)194 void AddOpNameIfNotExist(const std::string& id_name) { 195 if (!used_names_.count(id_name)) { 196 std::stringstream opname_inst; 197 opname_inst << "OpName " 198 << "%" << id_name << " \"" << id_name << "\""; 199 names_.emplace_back(opname_inst.str()); 200 used_names_.insert(id_name); 201 } 202 } 203 204 // Adds the names in a vector of assembly code strings to the Names section. 205 // If a '=' sign is found in an instruction, this instruction will be treated 206 // as an ID defining instruction. The ID name used in the instruction will be 207 // extracted and added to the Names section. AddNamesForResultIDsIn(const std::vector<std::string> & vec_asm_code)208 void AddNamesForResultIDsIn(const std::vector<std::string>& vec_asm_code) { 209 for (const auto& line : vec_asm_code) { 210 std::string name = GetResultIDName(line); 211 if (!name.empty()) { 212 AddOpNameIfNotExist(name); 213 } 214 } 215 } 216 217 // Adds an OpDecorate SpecId instruction for the given ID name. AddSpecIDFor(const std::string & id_name)218 void AddSpecIDFor(const std::string& id_name) { 219 std::stringstream decorate_inst; 220 decorate_inst << "OpDecorate " 221 << "%" << id_name << " SpecId " << spec_id_counter_; 222 spec_id_counter_ += 1; 223 annotations_.emplace_back(decorate_inst.str()); 224 } 225 226 // Extracts the ID name from a SPIR-V assembly instruction string. If the 227 // instruction is an ID-defining instruction (has result ID), returns the 228 // name of the result ID in string. If the instruction does not have result 229 // ID, returns an empty string. GetResultIDName(const std::string inst_str)230 std::string GetResultIDName(const std::string inst_str) { 231 std::string name; 232 if (inst_str.find('=') != std::string::npos) { 233 size_t assign_sign = inst_str.find('='); 234 name = inst_str.substr(0, assign_sign); 235 name.erase(remove_if(name.begin(), name.end(), 236 [](char c) { return c == ' ' || c == '%'; }), 237 name.end()); 238 } 239 return name; 240 } 241 242 uint32_t spec_id_counter_; 243 // User-defined preamble. 244 std::vector<std::string> preamble_; 245 // The vector that contains common preambles shared across all test SPIR-V 246 // code. 247 std::vector<std::string> global_preamble_; 248 // The vector that contains OpName instructions. 249 std::vector<std::string> names_; 250 // The vector that contains annotation instructions. 251 std::vector<std::string> annotations_; 252 // The vector that contains the code to declare types, constants and global 253 // variables (aka. the Types-Constants-Globals section). 254 std::vector<std::string> types_consts_globals_; 255 // The vector that contains the code in main function's entry block. 256 std::vector<std::string> main_func_; 257 // The vector that contains the postamble of main function body. 258 std::vector<std::string> main_func_postamble_; 259 // All of the defined variable names. 260 std::unordered_set<std::string> used_names_; 261 }; 262 263 } // namespace opt 264 } // namespace spvtools 265 266 #endif // TEST_OPT_ASSEMBLY_BUILDER_H_ 267