// Copyright (c) 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef TEST_OPT_ASSEMBLY_BUILDER_H_
#define TEST_OPT_ASSEMBLY_BUILDER_H_
#include <algorithm>
#include <cstdint>
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>
namespace spvtools {
namespace opt {
// A simple SPIR-V assembly code builder for test uses. It builds an SPIR-V
// assembly module from vectors of assembly strings. It allows users to add
// instructions to the main function and the type-constants-globals section
// directly. It relies on OpName instructions and friendly-name disassembling
// to keep the ID names unchanged after assembling.
//
// An assembly module is divided into several sections, matching with the
// SPIR-V Logical Layout:
// Global Preamble:
// OpCapability instructions;
// OpExtension instructions and OpExtInstImport instructions;
// OpMemoryModel instruction;
// OpEntryPoint and OpExecutionMode instruction;
// OpString, OpSourceExtension, OpSource and OpSourceContinued instructions.
// Names:
// OpName instructions.
// Annotations:
// OpDecorate, OpMemberDecorate, OpGroupDecorate, OpGroupMemberDecorate and
// OpDecorationGroup.
// Types, Constants and Global variables:
// Types, constants and global variables declaration instructions.
// Main Function:
// Main function instructions.
// Main Function Postamble:
// The return and function end instructions.
//
// The assembly code is built by concatenating all the strings in the above
// sections.
//
// Users define the contents in section <Type, Constants and Global Variables>
// and <Main Function>. The <Names> section is to hold the names for IDs to
// keep them unchanged before and after assembling. All defined IDs to be added
// to this code builder will be assigned with a global name through OpName
// instruction. The name is extracted from the definition instruction.
// E.g. adding instruction: %var_a = OpConstant %int 2, will also add an
// instruction: OpName %var_a, "var_a".
//
// Note that the name must not be used on more than one defined IDs and
// friendly-name disassembling must be enabled so that OpName instructions will
// be respected.
class AssemblyBuilder {
// The base ID value for spec constants.
static const uint32_t SPEC_ID_BASE = 200;
public:
// Initalize a minimal SPIR-V assembly code as the template. The minimal
// module contains an empty main function and some predefined names for the
// main function.
AssemblyBuilder()
: spec_id_counter_(SPEC_ID_BASE),
global_preamble_({
// clang-format off
"OpCapability Shader",
"OpCapability Float64",
"%1 = OpExtInstImport \"GLSL.std.450\"",
"OpMemoryModel Logical GLSL450",
"OpEntryPoint Vertex %main \"main\"",
// clang-format on
}),
names_(),
annotations_(),
types_consts_globals_(),
main_func_(),
main_func_postamble_({
"OpReturn",
"OpFunctionEnd",
}) {
AppendTypesConstantsGlobals({
"%void = OpTypeVoid",
"%main_func_type = OpTypeFunction %void",
});
AppendInMain({
"%main = OpFunction %void None %main_func_type",
"%main_func_entry_block = OpLabel",
});
}
// Appends OpName instructions to this builder. Instrcution strings that do
// not start with 'OpName ' will be skipped. Returns the references of this
// assembly builder.
AssemblyBuilder& AppendNames(const std::vector<std::string>& vec_asm_code) {
for (auto& inst_str : vec_asm_code) {
if (inst_str.find("OpName ") == 0) {
names_.push_back(inst_str);
}
}
return *this;
}
// Appends instructions to the types-constants-globals section and returns
// the reference of this assembly builder. IDs defined in the given code will
// be added to the Names section and then be registered with OpName
// instruction. Corresponding decoration instruction will be added for spec
// constants defined with opcode: 'OpSpecConstant'.
AssemblyBuilder& AppendTypesConstantsGlobals(
const std::vector<std::string>& vec_asm_code) {
AddNamesForResultIDsIn(vec_asm_code);
// Check spec constants defined with OpSpecConstant.
for (auto& inst_str : vec_asm_code) {
if (inst_str.find("= OpSpecConstant ") != std::string::npos ||
inst_str.find("= OpSpecConstantTrue ") != std::string::npos ||
inst_str.find("= OpSpecConstantFalse ") != std::string::npos) {
AddSpecIDFor(GetResultIDName(inst_str));
}
}
types_consts_globals_.insert(types_consts_globals_.end(),
vec_asm_code.begin(), vec_asm_code.end());
return *this;
}
// Appends instructions to the main function block, which is already labelled
// with "main_func_entry_block". Returns the reference of this assembly
// builder. IDs defined in the given code will be added to the Names section
// and then be registered with OpName instruction.
AssemblyBuilder& AppendInMain(const std::vector<std::string>& vec_asm_code) {
AddNamesForResultIDsIn(vec_asm_code);
main_func_.insert(main_func_.end(), vec_asm_code.begin(),
vec_asm_code.end());
return *this;
}
// Appends annotation instructions to the annotation section, and returns the
// reference of this assembly builder.
AssemblyBuilder& AppendAnnotations(
const std::vector<std::string>& vec_annotations) {
annotations_.insert(annotations_.end(), vec_annotations.begin(),
vec_annotations.end());
return *this;
}
// Pre-pends string to the preamble of the module. Useful for EFFCEE checks.
AssemblyBuilder& PrependPreamble(const std::vector<std::string>& preamble) {
preamble_.insert(preamble_.end(), preamble.begin(), preamble.end());
return *this;
}
// Get the SPIR-V assembly code as string.
std::string GetCode() const {
std::ostringstream ss;
for (const auto& line : preamble_) {
ss << line << std::endl;
}
for (const auto& line : global_preamble_) {
ss << line << std::endl;
}
for (const auto& line : names_) {
ss << line << std::endl;
}
for (const auto& line : annotations_) {
ss << line << std::endl;
}
for (const auto& line : types_consts_globals_) {
ss << line << std::endl;
}
for (const auto& line : main_func_) {
ss << line << std::endl;
}
for (const auto& line : main_func_postamble_) {
ss << line << std::endl;
}
return ss.str();
}
private:
// Adds a given name to the Name section with OpName. If the given name has
// been added before, does nothing.
void AddOpNameIfNotExist(const std::string& id_name) {
if (!used_names_.count(id_name)) {
std::stringstream opname_inst;
opname_inst << "OpName "
<< "%" << id_name << " \"" << id_name << "\"";
names_.emplace_back(opname_inst.str());
used_names_.insert(id_name);
}
}
// Adds the names in a vector of assembly code strings to the Names section.
// If a '=' sign is found in an instruction, this instruction will be treated
// as an ID defining instruction. The ID name used in the instruction will be
// extracted and added to the Names section.
void AddNamesForResultIDsIn(const std::vector<std::string>& vec_asm_code) {
for (const auto& line : vec_asm_code) {
std::string name = GetResultIDName(line);
if (!name.empty()) {
AddOpNameIfNotExist(name);
}
}
}
// Adds an OpDecorate SpecId instruction for the given ID name.
void AddSpecIDFor(const std::string& id_name) {
std::stringstream decorate_inst;
decorate_inst << "OpDecorate "
<< "%" << id_name << " SpecId " << spec_id_counter_;
spec_id_counter_ += 1;
annotations_.emplace_back(decorate_inst.str());
}
// Extracts the ID name from a SPIR-V assembly instruction string. If the
// instruction is an ID-defining instruction (has result ID), returns the
// name of the result ID in string. If the instruction does not have result
// ID, returns an empty string.
std::string GetResultIDName(const std::string inst_str) {
std::string name;
if (inst_str.find('=') != std::string::npos) {
size_t assign_sign = inst_str.find('=');
name = inst_str.substr(0, assign_sign);
name.erase(remove_if(name.begin(), name.end(),
[](char c) { return c == ' ' || c == '%'; }),
name.end());
}
return name;
}
uint32_t spec_id_counter_;
// User-defined preamble.
std::vector<std::string> preamble_;
// The vector that contains common preambles shared across all test SPIR-V
// code.
std::vector<std::string> global_preamble_;
// The vector that contains OpName instructions.
std::vector<std::string> names_;
// The vector that contains annotation instructions.
std::vector<std::string> annotations_;
// The vector that contains the code to declare types, constants and global
// variables (aka. the Types-Constants-Globals section).
std::vector<std::string> types_consts_globals_;
// The vector that contains the code in main function's entry block.
std::vector<std::string> main_func_;
// The vector that contains the postamble of main function body.
std::vector<std::string> main_func_postamble_;
// All of the defined variable names.
std::unordered_set<std::string> used_names_;
};
} // namespace opt
} // namespace spvtools
#endif // TEST_OPT_ASSEMBLY_BUILDER_H_