• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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