559 lines
20 KiB
C++
559 lines
20 KiB
C++
// 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 SOURCE_OPT_MODULE_H_
|
|
#define SOURCE_OPT_MODULE_H_
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "source/opt/function.h"
|
|
#include "source/opt/instruction.h"
|
|
#include "source/opt/iterator.h"
|
|
|
|
namespace spvtools {
|
|
namespace opt {
|
|
|
|
class IRContext;
|
|
|
|
// A struct for containing the module header information.
|
|
struct ModuleHeader {
|
|
uint32_t magic_number;
|
|
uint32_t version;
|
|
uint32_t generator;
|
|
uint32_t bound;
|
|
uint32_t schema;
|
|
};
|
|
|
|
// A SPIR-V module. It contains all the information for a SPIR-V module and
|
|
// serves as the backbone of optimization transformations.
|
|
class Module {
|
|
public:
|
|
using iterator = UptrVectorIterator<Function>;
|
|
using const_iterator = UptrVectorIterator<Function, true>;
|
|
using inst_iterator = InstructionList::iterator;
|
|
using const_inst_iterator = InstructionList::const_iterator;
|
|
|
|
// Creates an empty module with zero'd header.
|
|
Module() : header_({}), contains_debug_info_(false) {}
|
|
|
|
// Sets the header to the given |header|.
|
|
void SetHeader(const ModuleHeader& header) { header_ = header; }
|
|
|
|
// Sets the Id bound. The Id bound cannot be set to 0.
|
|
void SetIdBound(uint32_t bound) {
|
|
assert(bound != 0);
|
|
header_.bound = bound;
|
|
}
|
|
|
|
// Returns the Id bound.
|
|
uint32_t IdBound() const { return header_.bound; }
|
|
|
|
// Returns the current Id bound and increases it to the next available value.
|
|
// If the id bound has already reached its maximum value, then 0 is returned.
|
|
// The maximum value for the id bound is obtained from the context. If there
|
|
// is none, then the minimum that limit can be according to the spir-v
|
|
// specification.
|
|
// TODO(1841): Update the uses to check for a 0 return value.
|
|
uint32_t TakeNextIdBound();
|
|
|
|
// Appends a capability instruction to this module.
|
|
inline void AddCapability(std::unique_ptr<Instruction> c);
|
|
|
|
// Appends an extension instruction to this module.
|
|
inline void AddExtension(std::unique_ptr<Instruction> e);
|
|
|
|
// Appends an extended instruction set instruction to this module.
|
|
inline void AddExtInstImport(std::unique_ptr<Instruction> e);
|
|
|
|
// Set the memory model for this module.
|
|
inline void SetMemoryModel(std::unique_ptr<Instruction> m);
|
|
|
|
// Set the sampled image addressing mode for this module.
|
|
inline void SetSampledImageAddressMode(std::unique_ptr<Instruction> m);
|
|
|
|
// Appends an entry point instruction to this module.
|
|
inline void AddEntryPoint(std::unique_ptr<Instruction> e);
|
|
|
|
// Appends an execution mode instruction to this module.
|
|
inline void AddExecutionMode(std::unique_ptr<Instruction> e);
|
|
|
|
// Appends a debug 1 instruction (excluding OpLine & OpNoLine) to this module.
|
|
// "debug 1" instructions are the ones in layout section 7.a), see section
|
|
// 2.4 Logical Layout of a Module from the SPIR-V specification.
|
|
inline void AddDebug1Inst(std::unique_ptr<Instruction> d);
|
|
|
|
// Appends a debug 2 instruction (excluding OpLine & OpNoLine) to this module.
|
|
// "debug 2" instructions are the ones in layout section 7.b), see section
|
|
// 2.4 Logical Layout of a Module from the SPIR-V specification.
|
|
inline void AddDebug2Inst(std::unique_ptr<Instruction> d);
|
|
|
|
// Appends a debug 3 instruction (OpModuleProcessed) to this module.
|
|
// This is due to decision by the SPIR Working Group, pending publication.
|
|
inline void AddDebug3Inst(std::unique_ptr<Instruction> d);
|
|
|
|
// Appends a debug info extension (OpenCL.DebugInfo.100,
|
|
// NonSemantic.Shader.DebugInfo.100, or DebugInfo) instruction to this module.
|
|
inline void AddExtInstDebugInfo(std::unique_ptr<Instruction> d);
|
|
|
|
// Appends an annotation instruction to this module.
|
|
inline void AddAnnotationInst(std::unique_ptr<Instruction> a);
|
|
|
|
// Appends a type-declaration instruction to this module.
|
|
inline void AddType(std::unique_ptr<Instruction> t);
|
|
|
|
// Appends a constant, global variable, or OpUndef instruction to this module.
|
|
inline void AddGlobalValue(std::unique_ptr<Instruction> v);
|
|
|
|
// Prepends a function declaration to this module.
|
|
inline void AddFunctionDeclaration(std::unique_ptr<Function> f);
|
|
|
|
// Appends a function to this module.
|
|
inline void AddFunction(std::unique_ptr<Function> f);
|
|
|
|
// Sets |contains_debug_info_| as true.
|
|
inline void SetContainsDebugInfo();
|
|
inline bool ContainsDebugInfo() { return contains_debug_info_; }
|
|
|
|
// Returns a vector of pointers to type-declaration instructions in this
|
|
// module.
|
|
std::vector<Instruction*> GetTypes();
|
|
std::vector<const Instruction*> GetTypes() const;
|
|
// Returns a vector of pointers to constant-creation instructions in this
|
|
// module.
|
|
std::vector<Instruction*> GetConstants();
|
|
std::vector<const Instruction*> GetConstants() const;
|
|
|
|
// Return result id of global value with |opcode|, 0 if not present.
|
|
uint32_t GetGlobalValue(spv::Op opcode) const;
|
|
|
|
// Add global value with |opcode|, |result_id| and |type_id|
|
|
void AddGlobalValue(spv::Op opcode, uint32_t result_id, uint32_t type_id);
|
|
|
|
inline uint32_t id_bound() const { return header_.bound; }
|
|
|
|
inline uint32_t version() const { return header_.version; }
|
|
inline uint32_t generator() const { return header_.generator; }
|
|
inline uint32_t schema() const { return header_.schema; }
|
|
|
|
inline void set_version(uint32_t v) { header_.version = v; }
|
|
|
|
// Iterators for capabilities instructions contained in this module.
|
|
inline inst_iterator capability_begin();
|
|
inline inst_iterator capability_end();
|
|
inline IteratorRange<inst_iterator> capabilities();
|
|
inline IteratorRange<const_inst_iterator> capabilities() const;
|
|
|
|
// Iterators for ext_inst_imports instructions contained in this module.
|
|
inline inst_iterator ext_inst_import_begin();
|
|
inline inst_iterator ext_inst_import_end();
|
|
inline IteratorRange<inst_iterator> ext_inst_imports();
|
|
inline IteratorRange<const_inst_iterator> ext_inst_imports() const;
|
|
|
|
// Return the memory model instruction contained in this module.
|
|
inline Instruction* GetMemoryModel() { return memory_model_.get(); }
|
|
inline const Instruction* GetMemoryModel() const {
|
|
return memory_model_.get();
|
|
}
|
|
|
|
// Return the sampled image address mode instruction contained in this module.
|
|
inline Instruction* GetSampledImageAddressMode() {
|
|
return sampled_image_address_mode_.get();
|
|
}
|
|
inline const Instruction* GetSampledImageAddressMode() const {
|
|
return sampled_image_address_mode_.get();
|
|
}
|
|
|
|
// There are several kinds of debug instructions, according to where they can
|
|
// appear in the logical layout of a module:
|
|
// - Section 7a: OpString, OpSourceExtension, OpSource, OpSourceContinued
|
|
// - Section 7b: OpName, OpMemberName
|
|
// - Section 7c: OpModuleProcessed
|
|
// - Mostly anywhere: OpLine and OpNoLine
|
|
//
|
|
|
|
// Iterators for debug 1 instructions (excluding OpLine & OpNoLine) contained
|
|
// in this module. These are for layout section 7a.
|
|
inline inst_iterator debug1_begin();
|
|
inline inst_iterator debug1_end();
|
|
inline IteratorRange<inst_iterator> debugs1();
|
|
inline IteratorRange<const_inst_iterator> debugs1() const;
|
|
|
|
// Iterators for debug 2 instructions (excluding OpLine & OpNoLine) contained
|
|
// in this module. These are for layout section 7b.
|
|
inline inst_iterator debug2_begin();
|
|
inline inst_iterator debug2_end();
|
|
inline IteratorRange<inst_iterator> debugs2();
|
|
inline IteratorRange<const_inst_iterator> debugs2() const;
|
|
|
|
// Iterators for debug 3 instructions (excluding OpLine & OpNoLine) contained
|
|
// in this module. These are for layout section 7c.
|
|
inline inst_iterator debug3_begin();
|
|
inline inst_iterator debug3_end();
|
|
inline IteratorRange<inst_iterator> debugs3();
|
|
inline IteratorRange<const_inst_iterator> debugs3() const;
|
|
|
|
// Iterators for debug info instructions (excluding OpLine & OpNoLine)
|
|
// contained in this module. These are OpExtInst for DebugInfo extension
|
|
// placed between section 9 and 10.
|
|
inline inst_iterator ext_inst_debuginfo_begin();
|
|
inline inst_iterator ext_inst_debuginfo_end();
|
|
inline IteratorRange<inst_iterator> ext_inst_debuginfo();
|
|
inline IteratorRange<const_inst_iterator> ext_inst_debuginfo() const;
|
|
|
|
// Iterators for entry point instructions contained in this module
|
|
inline IteratorRange<inst_iterator> entry_points();
|
|
inline IteratorRange<const_inst_iterator> entry_points() const;
|
|
|
|
// Iterators for execution_modes instructions contained in this module.
|
|
inline inst_iterator execution_mode_begin();
|
|
inline inst_iterator execution_mode_end();
|
|
inline IteratorRange<inst_iterator> execution_modes();
|
|
inline IteratorRange<const_inst_iterator> execution_modes() const;
|
|
|
|
// Iterators for annotation instructions contained in this module.
|
|
inline inst_iterator annotation_begin();
|
|
inline inst_iterator annotation_end();
|
|
IteratorRange<inst_iterator> annotations();
|
|
IteratorRange<const_inst_iterator> annotations() const;
|
|
|
|
// Iterators for extension instructions contained in this module.
|
|
inline inst_iterator extension_begin();
|
|
inline inst_iterator extension_end();
|
|
IteratorRange<inst_iterator> extensions();
|
|
IteratorRange<const_inst_iterator> extensions() const;
|
|
|
|
// Iterators for types, constants and global variables instructions.
|
|
inline inst_iterator types_values_begin();
|
|
inline inst_iterator types_values_end();
|
|
inline IteratorRange<inst_iterator> types_values();
|
|
inline IteratorRange<const_inst_iterator> types_values() const;
|
|
|
|
// Iterators for functions contained in this module.
|
|
iterator begin() { return iterator(&functions_, functions_.begin()); }
|
|
iterator end() { return iterator(&functions_, functions_.end()); }
|
|
const_iterator begin() const { return cbegin(); }
|
|
const_iterator end() const { return cend(); }
|
|
inline const_iterator cbegin() const;
|
|
inline const_iterator cend() const;
|
|
|
|
// Invokes function |f| on all instructions in this module, and optionally on
|
|
// the debug line instructions that precede them.
|
|
void ForEachInst(const std::function<void(Instruction*)>& f,
|
|
bool run_on_debug_line_insts = false);
|
|
void ForEachInst(const std::function<void(const Instruction*)>& f,
|
|
bool run_on_debug_line_insts = false) const;
|
|
|
|
// Pushes the binary segments for this instruction into the back of *|binary|.
|
|
// If |skip_nop| is true and this is a OpNop, do nothing.
|
|
void ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const;
|
|
|
|
// Returns 1 more than the maximum Id value mentioned in the module.
|
|
uint32_t ComputeIdBound() const;
|
|
|
|
// Returns true if module has capability |cap|
|
|
bool HasExplicitCapability(uint32_t cap);
|
|
|
|
// Returns id for OpExtInst instruction for extension |extstr|.
|
|
// Returns 0 if not found.
|
|
uint32_t GetExtInstImportId(const char* extstr);
|
|
|
|
// Sets the associated context for this module
|
|
void SetContext(IRContext* c) { context_ = c; }
|
|
|
|
// Gets the associated context for this module
|
|
IRContext* context() const { return context_; }
|
|
|
|
// Sets the trailing debug line info to |dbg_line_info|.
|
|
void SetTrailingDbgLineInfo(std::vector<Instruction>&& dbg_line_info) {
|
|
trailing_dbg_line_info_ = std::move(dbg_line_info);
|
|
}
|
|
|
|
std::vector<Instruction>& trailing_dbg_line_info() {
|
|
return trailing_dbg_line_info_;
|
|
}
|
|
|
|
const std::vector<Instruction>& trailing_dbg_line_info() const {
|
|
return trailing_dbg_line_info_;
|
|
}
|
|
|
|
private:
|
|
ModuleHeader header_; // Module header
|
|
|
|
// The following fields respect the "Logical Layout of a Module" in
|
|
// Section 2.4 of the SPIR-V specification.
|
|
IRContext* context_;
|
|
InstructionList capabilities_;
|
|
InstructionList extensions_;
|
|
InstructionList ext_inst_imports_;
|
|
// A module only has one memory model instruction.
|
|
std::unique_ptr<Instruction> memory_model_;
|
|
// A module can only have one optional sampled image addressing mode
|
|
std::unique_ptr<Instruction> sampled_image_address_mode_;
|
|
InstructionList entry_points_;
|
|
InstructionList execution_modes_;
|
|
InstructionList debugs1_;
|
|
InstructionList debugs2_;
|
|
InstructionList debugs3_;
|
|
InstructionList ext_inst_debuginfo_;
|
|
InstructionList annotations_;
|
|
// Type declarations, constants, and global variable declarations.
|
|
InstructionList types_values_;
|
|
std::vector<std::unique_ptr<Function>> functions_;
|
|
|
|
// If the module ends with Op*Line instruction, they will not be attached to
|
|
// any instruction. We record them here, so they will not be lost.
|
|
std::vector<Instruction> trailing_dbg_line_info_;
|
|
|
|
// This module contains DebugScope/DebugNoScope or OpLine/OpNoLine.
|
|
bool contains_debug_info_;
|
|
};
|
|
|
|
// Pretty-prints |module| to |str|. Returns |str|.
|
|
std::ostream& operator<<(std::ostream& str, const Module& module);
|
|
|
|
inline void Module::AddCapability(std::unique_ptr<Instruction> c) {
|
|
capabilities_.push_back(std::move(c));
|
|
}
|
|
|
|
inline void Module::AddExtension(std::unique_ptr<Instruction> e) {
|
|
extensions_.push_back(std::move(e));
|
|
}
|
|
|
|
inline void Module::AddExtInstImport(std::unique_ptr<Instruction> e) {
|
|
ext_inst_imports_.push_back(std::move(e));
|
|
}
|
|
|
|
inline void Module::SetMemoryModel(std::unique_ptr<Instruction> m) {
|
|
memory_model_ = std::move(m);
|
|
}
|
|
|
|
inline void Module::SetSampledImageAddressMode(std::unique_ptr<Instruction> m) {
|
|
sampled_image_address_mode_ = std::move(m);
|
|
}
|
|
|
|
inline void Module::AddEntryPoint(std::unique_ptr<Instruction> e) {
|
|
entry_points_.push_back(std::move(e));
|
|
}
|
|
|
|
inline void Module::AddExecutionMode(std::unique_ptr<Instruction> e) {
|
|
execution_modes_.push_back(std::move(e));
|
|
}
|
|
|
|
inline void Module::AddDebug1Inst(std::unique_ptr<Instruction> d) {
|
|
debugs1_.push_back(std::move(d));
|
|
}
|
|
|
|
inline void Module::AddDebug2Inst(std::unique_ptr<Instruction> d) {
|
|
debugs2_.push_back(std::move(d));
|
|
}
|
|
|
|
inline void Module::AddDebug3Inst(std::unique_ptr<Instruction> d) {
|
|
debugs3_.push_back(std::move(d));
|
|
}
|
|
|
|
inline void Module::AddExtInstDebugInfo(std::unique_ptr<Instruction> d) {
|
|
ext_inst_debuginfo_.push_back(std::move(d));
|
|
}
|
|
|
|
inline void Module::AddAnnotationInst(std::unique_ptr<Instruction> a) {
|
|
annotations_.push_back(std::move(a));
|
|
}
|
|
|
|
inline void Module::AddType(std::unique_ptr<Instruction> t) {
|
|
types_values_.push_back(std::move(t));
|
|
}
|
|
|
|
inline void Module::AddGlobalValue(std::unique_ptr<Instruction> v) {
|
|
types_values_.push_back(std::move(v));
|
|
}
|
|
|
|
inline void Module::AddFunctionDeclaration(std::unique_ptr<Function> f) {
|
|
// function declarations must come before function definitions.
|
|
functions_.emplace(functions_.begin(), std::move(f));
|
|
}
|
|
|
|
inline void Module::AddFunction(std::unique_ptr<Function> f) {
|
|
functions_.emplace_back(std::move(f));
|
|
}
|
|
|
|
inline void Module::SetContainsDebugInfo() { contains_debug_info_ = true; }
|
|
|
|
inline Module::inst_iterator Module::capability_begin() {
|
|
return capabilities_.begin();
|
|
}
|
|
inline Module::inst_iterator Module::capability_end() {
|
|
return capabilities_.end();
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::capabilities() {
|
|
return make_range(capabilities_.begin(), capabilities_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::capabilities() const {
|
|
return make_range(capabilities_.begin(), capabilities_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::ext_inst_import_begin() {
|
|
return ext_inst_imports_.begin();
|
|
}
|
|
inline Module::inst_iterator Module::ext_inst_import_end() {
|
|
return ext_inst_imports_.end();
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::ext_inst_imports() {
|
|
return make_range(ext_inst_imports_.begin(), ext_inst_imports_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::ext_inst_imports()
|
|
const {
|
|
return make_range(ext_inst_imports_.begin(), ext_inst_imports_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::debug1_begin() { return debugs1_.begin(); }
|
|
inline Module::inst_iterator Module::debug1_end() { return debugs1_.end(); }
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::debugs1() {
|
|
return make_range(debugs1_.begin(), debugs1_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::debugs1() const {
|
|
return make_range(debugs1_.begin(), debugs1_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::debug2_begin() { return debugs2_.begin(); }
|
|
inline Module::inst_iterator Module::debug2_end() { return debugs2_.end(); }
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::debugs2() {
|
|
return make_range(debugs2_.begin(), debugs2_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::debugs2() const {
|
|
return make_range(debugs2_.begin(), debugs2_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::debug3_begin() { return debugs3_.begin(); }
|
|
inline Module::inst_iterator Module::debug3_end() { return debugs3_.end(); }
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::debugs3() {
|
|
return make_range(debugs3_.begin(), debugs3_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::debugs3() const {
|
|
return make_range(debugs3_.begin(), debugs3_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::ext_inst_debuginfo_begin() {
|
|
return ext_inst_debuginfo_.begin();
|
|
}
|
|
inline Module::inst_iterator Module::ext_inst_debuginfo_end() {
|
|
return ext_inst_debuginfo_.end();
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::ext_inst_debuginfo() {
|
|
return make_range(ext_inst_debuginfo_.begin(), ext_inst_debuginfo_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::ext_inst_debuginfo()
|
|
const {
|
|
return make_range(ext_inst_debuginfo_.begin(), ext_inst_debuginfo_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::entry_points() {
|
|
return make_range(entry_points_.begin(), entry_points_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::entry_points() const {
|
|
return make_range(entry_points_.begin(), entry_points_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::execution_mode_begin() {
|
|
return execution_modes_.begin();
|
|
}
|
|
inline Module::inst_iterator Module::execution_mode_end() {
|
|
return execution_modes_.end();
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::execution_modes() {
|
|
return make_range(execution_modes_.begin(), execution_modes_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::execution_modes()
|
|
const {
|
|
return make_range(execution_modes_.begin(), execution_modes_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::annotation_begin() {
|
|
return annotations_.begin();
|
|
}
|
|
inline Module::inst_iterator Module::annotation_end() {
|
|
return annotations_.end();
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::annotations() {
|
|
return make_range(annotations_.begin(), annotations_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::annotations() const {
|
|
return make_range(annotations_.begin(), annotations_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::extension_begin() {
|
|
return extensions_.begin();
|
|
}
|
|
inline Module::inst_iterator Module::extension_end() {
|
|
return extensions_.end();
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::extensions() {
|
|
return make_range(extensions_.begin(), extensions_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::extensions() const {
|
|
return make_range(extensions_.begin(), extensions_.end());
|
|
}
|
|
|
|
inline Module::inst_iterator Module::types_values_begin() {
|
|
return types_values_.begin();
|
|
}
|
|
|
|
inline Module::inst_iterator Module::types_values_end() {
|
|
return types_values_.end();
|
|
}
|
|
|
|
inline IteratorRange<Module::inst_iterator> Module::types_values() {
|
|
return make_range(types_values_.begin(), types_values_.end());
|
|
}
|
|
|
|
inline IteratorRange<Module::const_inst_iterator> Module::types_values() const {
|
|
return make_range(types_values_.begin(), types_values_.end());
|
|
}
|
|
|
|
inline Module::const_iterator Module::cbegin() const {
|
|
return const_iterator(&functions_, functions_.cbegin());
|
|
}
|
|
|
|
inline Module::const_iterator Module::cend() const {
|
|
return const_iterator(&functions_, functions_.cend());
|
|
}
|
|
|
|
} // namespace opt
|
|
} // namespace spvtools
|
|
|
|
#endif // SOURCE_OPT_MODULE_H_
|