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
25namespace spvtools {
26namespace 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.
68class 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.
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.
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'.
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.
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.
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.
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.
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.
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.
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.
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.
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