瀏覽代碼

[spirv] Add structured representaion of SPIR-V entities (#375)

Added classes for various SPIR-V concepts, including instruction,
basic block, module, and function, and others. These will be used
in the module builder.
Lei Zhang 8 年之前
父節點
當前提交
07f760a40c

+ 305 - 0
tools/clang/include/clang/SPIRV/Structure.h

@@ -0,0 +1,305 @@
+//===--- Structure.h - SPIR-V representation structures ------*- C++ -*---===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines several classes for representing SPIR-V basic blocks,
+// functions, and modules. They are not intended to be general representations
+// that can be used for various purposes; instead, they are just special
+// crafted to be provide structured representation of SPIR-V modules in the
+// ModuleBuilder.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SPIRV_STRUCTURE_H
+#define LLVM_CLANG_SPIRV_STRUCTURE_H
+
+#include <string>
+#include <vector>
+
+#include "spirv/1.0/spirv.hpp11"
+#include "clang/SPIRV/InstBuilder.h"
+#include "llvm/ADT/Optional.h"
+
+namespace clang {
+namespace spirv {
+
+// TODO: do some statistics and switch to use SmallVector here if helps.
+/// \brief The class representing a SPIR-V instruction.
+using Instruction = std::vector<uint32_t>;
+
+/// \brief The class representing a SPIR-V basic block.
+class BasicBlock {
+public:
+  /// \brief Default constructs an empty basic block.
+  inline BasicBlock();
+  /// \brief Constructs a basic block with the given label id.
+  inline explicit BasicBlock(uint32_t labelId);
+
+  // Disable copy constructor/assignment until we find they are truly useful.
+  BasicBlock(const BasicBlock &) = delete;
+  BasicBlock &operator=(const BasicBlock &) = delete;
+  // Allow move constructor/assignment since they are efficient.
+  BasicBlock(BasicBlock &&that);
+  BasicBlock &operator=(BasicBlock &&that);
+
+  /// \brief Returns true if this basic block is empty, which has no <label-id>
+  /// assigned and no instructions.
+  inline bool isEmpty() const;
+  /// \brief Clears all instructions in this basic block and turns this basic
+  /// block into an empty basic block.
+  inline void clear();
+
+  /// \brief Serializes this basic block and feeds it to the comsumer in the
+  /// given InstBuilder. After this call, this basic block will be in an empty
+  /// state.
+  void take(InstBuilder *builder);
+
+  /// \brief add an instruction to this basic block.
+  inline void addInstruction(Instruction &&);
+
+private:
+  uint32_t labelId; ///< The label id for this basic block. Zero means invalid.
+  std::vector<Instruction> instructions;
+};
+
+/// \brief The class representing a SPIR-V function.
+class Function {
+public:
+  /// \brief Default constructs an empty SPIR-V function.
+  inline Function();
+  /// \brief Constructs a SPIR-V function with the given parameters.
+  inline Function(uint32_t resultType, uint32_t resultId,
+                  spv::FunctionControlMask control, uint32_t functionType);
+
+  // Disable copy constructor/assignment until we find they are truly useful.
+  Function(const Function &) = delete;
+  Function &operator=(const Function &) = delete;
+
+  // Allow move constructor/assignment since they are efficient.
+  Function(Function &&that);
+  Function &operator=(Function &&that);
+
+  /// \brief Returns true if this function is empty.
+  inline bool isEmpty() const;
+  /// \brief Clears all paramters and basic blocks and turns this function into
+  /// an empty function.
+  void clear();
+
+  /// \brief Serializes this function and feeds it to the comsumer in the given
+  /// InstBuilder. After this call, this function will be in an invalid state.
+  void take(InstBuilder *builder);
+
+  /// \brief Adds a parameter to this function.
+  inline void addParameter(uint32_t paramResultType, uint32_t paramResultId);
+  /// \brief Adds a basic block to this function.
+  inline void addBasicBlock(BasicBlock &&block);
+
+private:
+  uint32_t resultType;
+  uint32_t resultId;
+  spv::FunctionControlMask funcControl;
+  uint32_t funcType;
+  /// Parameter <result-type> and <result-id> pairs.
+  std::vector<std::pair<uint32_t, uint32_t>> parameters;
+  std::vector<BasicBlock> blocks;
+};
+
+/// \brief The class representing a SPIR-V module.
+class SPIRVModule {
+public:
+  /// \brief Default constructs an empty SPIR-V module.
+  inline SPIRVModule();
+
+  // Disable copy constructor/assignment until we find they are truly useful.
+  SPIRVModule(const SPIRVModule &) = delete;
+  SPIRVModule &operator=(const SPIRVModule &) = delete;
+
+  // Allow move constructor/assignment since they are efficient.
+  SPIRVModule(SPIRVModule &&that) = default;
+  SPIRVModule &operator=(SPIRVModule &&that) = default;
+
+  /// \brief Returns true if this module is empty.
+  bool isEmpty() const;
+  /// \brief Clears all instructions and functions and turns this module into
+  /// an empty module.
+  void clear();
+
+  /// \brief Sets the id bound to the given bound.
+  inline void setBound(uint32_t newBound);
+
+  /// \brief Collects all the SPIR-V words in this module and consumes them
+  /// using the consumer within the given InstBuilder. This method is
+  /// destructive; the module will be consumed and cleared after calling it.
+  void take(InstBuilder *builder);
+
+  inline void addCapability(spv::Capability);
+  inline void addExtension(std::string extension);
+  inline void addExtInstSet(uint32_t setId, std::string extInstSet);
+  inline void setAddressingModel(spv::AddressingModel);
+  inline void setMemoryModel(spv::MemoryModel);
+  inline void addEntryPoint(spv::ExecutionModel, uint32_t targetId,
+                            std::string targetName,
+                            std::initializer_list<uint32_t> intefaces);
+  inline void addExecutionMode(Instruction &&);
+  inline void addDebugName(uint32_t targetId,
+                           llvm::Optional<uint32_t> memberIndex,
+                           std::string name);
+  inline void addDecoration(Instruction &&);
+  inline void addType(Instruction &&);
+  inline void addFunction(Function &&);
+
+private:
+  /// \brief The struct representing a SPIR-V module header.
+  struct Header {
+    /// \brief Default constructs a SPIR-V module header with id bound 0.
+    Header();
+
+    /// \brief Feeds the consumer with all the SPIR-V words for this header.
+    void collect(const WordConsumer &consumer);
+
+    uint32_t magicNumber;
+    uint32_t version;
+    uint32_t generator;
+    uint32_t bound;
+    uint32_t reserved;
+  };
+
+  /// \brief The struct representing an entry point.
+  struct EntryPoint {
+    inline EntryPoint(spv::ExecutionModel, uint32_t id, std::string name,
+                      std::initializer_list<uint32_t> interface);
+
+    spv::ExecutionModel executionModel;
+    uint32_t targetId;
+    std::string targetName;
+    std::initializer_list<uint32_t> interfaces;
+  };
+
+  /// \brief The struct representing a debug name.
+  struct DebugName {
+    inline DebugName(uint32_t id, llvm::Optional<uint32_t> index,
+                     std::string targetName);
+
+    uint32_t targetId;
+    llvm::Optional<uint32_t> memberIndex;
+    std::string name;
+  };
+
+  Header header; ///< SPIR-V module header.
+  std::vector<spv::Capability> capabilities;
+  std::vector<std::string> extensions;
+  std::vector<std::pair<uint32_t, std::string>> extInstSets;
+  llvm::Optional<spv::AddressingModel> addressingModel;
+  llvm::Optional<spv::MemoryModel> memoryModel;
+  std::vector<EntryPoint> entryPoints;
+  // XXX: Right now the following are basically vectors of Instructions.
+  // They will be turned into vectors of more full-fledged classes gradually
+  // as we implement more features.
+  std::vector<Instruction> executionModes;
+  // TODO: support other debug instructions
+  std::vector<DebugName> debugNames;
+  std::vector<Instruction> decorations;
+  std::vector<Instruction> typesValues;
+  std::vector<Function> functions;
+};
+
+BasicBlock::BasicBlock() : labelId(0) {}
+BasicBlock::BasicBlock(uint32_t id) : labelId(id) {}
+
+bool BasicBlock::isEmpty() const {
+  return labelId == 0 && instructions.empty();
+}
+void BasicBlock::clear() {
+  labelId = 0;
+  instructions.clear();
+}
+
+void BasicBlock::addInstruction(Instruction &&inst) {
+  instructions.push_back(std::move(inst));
+}
+
+Function::Function()
+    : resultType(0), resultId(0),
+      funcControl(spv::FunctionControlMask::MaskNone), funcType(0) {}
+
+Function::Function(uint32_t rType, uint32_t rId,
+                   spv::FunctionControlMask control, uint32_t fType)
+    : resultType(rType), resultId(rId), funcControl(control), funcType(fType) {}
+
+bool Function::isEmpty() const {
+  return resultType == 0 && resultId == 0 &&
+         funcControl == spv::FunctionControlMask::MaskNone && funcType == 0 &&
+         parameters.empty() && blocks.empty();
+}
+
+void Function::addParameter(uint32_t rType, uint32_t rId) {
+  parameters.emplace_back(rType, rId);
+}
+
+void Function::addBasicBlock(BasicBlock &&block) {
+  blocks.push_back(std::move(block));
+}
+
+SPIRVModule::SPIRVModule()
+    : addressingModel(llvm::None), memoryModel(llvm::None) {}
+
+void SPIRVModule::setBound(uint32_t newBound) { header.bound = newBound; }
+
+void SPIRVModule::addCapability(spv::Capability cap) {
+  capabilities.push_back(cap);
+}
+void SPIRVModule::addExtension(std::string ext) {
+  extensions.push_back(std::move(ext));
+}
+void SPIRVModule::addExtInstSet(uint32_t setId, std::string extInstSet) {
+  extInstSets.emplace_back(setId, extInstSet);
+}
+void SPIRVModule::setAddressingModel(spv::AddressingModel am) {
+  addressingModel = llvm::Optional<spv::AddressingModel>(am);
+}
+void SPIRVModule::setMemoryModel(spv::MemoryModel mm) {
+  memoryModel = llvm::Optional<spv::MemoryModel>(mm);
+}
+void SPIRVModule::addEntryPoint(spv::ExecutionModel em, uint32_t targetId,
+                                std::string name,
+                                std::initializer_list<uint32_t> interfaces) {
+  entryPoints.emplace_back(em, targetId, std::move(name),
+                           std::move(interfaces));
+}
+void SPIRVModule::addExecutionMode(Instruction &&execMode) {
+  executionModes.push_back(std::move(execMode));
+}
+void SPIRVModule::addDebugName(uint32_t targetId,
+                               llvm::Optional<uint32_t> memberIndex,
+                               std::string name) {
+  debugNames.emplace_back(targetId, memberIndex, std::move(name));
+}
+void SPIRVModule::addDecoration(Instruction &&decoration) {
+  decorations.push_back(std::move(decoration));
+}
+void SPIRVModule::addType(Instruction &&type) {
+  typesValues.push_back(std::move(type));
+}
+void SPIRVModule::addFunction(Function &&f) {
+  functions.push_back(std::move(f));
+}
+
+SPIRVModule::EntryPoint::EntryPoint(spv::ExecutionModel em, uint32_t id,
+                                    std::string name,
+                                    std::initializer_list<uint32_t> interface)
+    : executionModel(em), targetId(id), targetName(std::move(name)),
+      interfaces(std::move(interface)) {}
+
+SPIRVModule::DebugName::DebugName(uint32_t id, llvm::Optional<uint32_t> index,
+                                  std::string targetName)
+    : targetId(id), memberIndex(index), name(std::move(targetName)) {}
+
+} // end namespace spirv
+} // end namespace clang
+
+#endif

+ 1 - 0
tools/clang/lib/SPIRV/CMakeLists.txt

@@ -8,6 +8,7 @@ add_clang_library(clangSPIRV
   InstBuilderManual.cpp
   SPIRVBuilder.cpp
   String.cpp
+  Structure.cpp
 
   LINK_LIBS
   clangAST

+ 200 - 0
tools/clang/lib/SPIRV/Structure.cpp

@@ -0,0 +1,200 @@
+//===--- Structure.cpp - SPIR-V representation structures -----*- C++ -*---===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/SPIRV/Structure.h"
+
+namespace clang {
+namespace spirv {
+
+namespace {
+constexpr uint32_t kGeneratorNumber = 14;
+constexpr uint32_t kToolVersion = 0;
+
+bool isTerminator(spv::Op opcode) {
+  switch (opcode) {
+  case spv::Op::OpKill:
+  case spv::Op::OpUnreachable:
+  case spv::Op::OpBranch:
+  case spv::Op::OpBranchConditional:
+  case spv::Op::OpSwitch:
+  case spv::Op::OpReturn:
+  case spv::Op::OpReturnValue:
+    return true;
+  default:
+    return false;
+  }
+}
+} // namespace
+
+BasicBlock::BasicBlock(BasicBlock &&that)
+    : labelId(that.labelId), instructions(std::move(that.instructions)) {
+  that.clear();
+}
+
+BasicBlock &BasicBlock::operator=(BasicBlock &&that) {
+  labelId = that.labelId;
+  instructions = std::move(that.instructions);
+
+  that.clear();
+
+  return *this;
+}
+
+void BasicBlock::take(InstBuilder *builder) {
+  // Make sure we have a terminator instruction at the end.
+  // TODO: This is a little bit ugly. It suggests that we should put the opcode
+  // in the Instruction struct. But fine for now.
+  assert(!instructions.empty() && isTerminator(static_cast<spv::Op>(
+                                      instructions.back().front() & 0xffff)));
+  builder->opLabel(labelId).x();
+  for (auto &inst : instructions) {
+    builder->getConsumer()(std::move(inst));
+  }
+  clear();
+}
+
+Function::Function(Function &&that)
+    : resultType(that.resultType), resultId(that.resultId),
+      funcControl(that.funcControl), funcType(that.funcType),
+      parameters(std::move(that.parameters)), blocks(std::move(that.blocks)) {
+  that.clear();
+}
+
+Function &Function::operator=(Function &&that) {
+  resultType = that.resultType;
+  resultId = that.resultId;
+  funcControl = that.funcControl;
+  funcType = that.funcType;
+  parameters = std::move(that.parameters);
+  blocks = std::move(that.blocks);
+
+  that.clear();
+
+  return *this;
+}
+
+void Function::clear() {
+  resultType = 0;
+  resultId = 0;
+  funcControl = spv::FunctionControlMask::MaskNone;
+  funcType = 0;
+  parameters.clear();
+  blocks.clear();
+}
+
+void Function::take(InstBuilder *builder) {
+  builder->opFunction(resultType, resultId, funcControl, funcType).x();
+  for (auto &param : parameters) {
+    builder->opFunctionParameter(param.first, param.second).x();
+  }
+  for (auto &block : blocks) {
+    block.take(builder);
+  }
+  builder->opFunctionEnd().x();
+  clear();
+}
+
+SPIRVModule::Header::Header()
+    : magicNumber(spv::MagicNumber), version(spv::Version),
+      generator((kGeneratorNumber << 16) | kToolVersion), bound(0),
+      reserved(0) {}
+
+bool SPIRVModule::isEmpty() const {
+  return header.bound == 0 && capabilities.empty() && extensions.empty() &&
+         extInstSets.empty() && !addressingModel.hasValue() &&
+         !memoryModel.hasValue() && entryPoints.empty() &&
+         executionModes.empty() && debugNames.empty() && decorations.empty() &&
+         functions.empty();
+}
+
+void SPIRVModule::clear() {
+  header.bound = 0;
+  capabilities.clear();
+  extensions.clear();
+  extInstSets.clear();
+  addressingModel = llvm::None;
+  memoryModel = llvm::None;
+  entryPoints.clear();
+  executionModes.clear();
+  debugNames.clear();
+  decorations.clear();
+  functions.clear();
+}
+
+void SPIRVModule::take(InstBuilder *builder) {
+  const auto &consumer = builder->getConsumer();
+
+  // Order matters here.
+  header.collect(consumer);
+
+  for (auto &cap : capabilities) {
+    builder->opCapability(cap).x();
+  }
+
+  for (auto &ext : extensions) {
+    builder->opExtension(ext).x();
+  }
+
+  for (auto &inst : extInstSets) {
+    builder->opExtInstImport(inst.first, inst.second).x();
+  }
+
+  if (addressingModel.hasValue() && memoryModel.hasValue()) {
+    builder->opMemoryModel(*addressingModel, *memoryModel).x();
+  }
+
+  for (auto &inst : entryPoints) {
+    builder
+        ->opEntryPoint(inst.executionModel, inst.targetId,
+                       std::move(inst.targetName), std::move(inst.interfaces))
+        .x();
+  }
+
+  for (auto &inst : executionModes) {
+    consumer(std::move(inst));
+  }
+
+  for (auto &inst : debugNames) {
+    if (inst.memberIndex.hasValue()) {
+      builder
+          ->opMemberName(inst.targetId, inst.memberIndex.getValue(),
+                         std::move(inst.name))
+          .x();
+    } else {
+      builder->opName(inst.targetId, std::move(inst.name)).x();
+    }
+  }
+
+  for (auto &inst : decorations) {
+    consumer(std::move(inst));
+  }
+
+  for (auto &inst : typesValues) {
+    consumer(std::move(inst));
+  }
+
+  for (uint32_t i = 0; i < functions.size(); ++i) {
+    functions[i].take(builder);
+  }
+
+  clear();
+}
+
+void SPIRVModule::Header::collect(const WordConsumer &consumer) {
+  std::vector<uint32_t> words;
+  words.push_back(magicNumber);
+  words.push_back(version);
+  words.push_back(generator);
+  words.push_back(bound);
+  words.push_back(reserved);
+  consumer(std::move(words));
+}
+
+} // end namespace spirv
+} // end namespace clang

+ 1 - 0
tools/clang/unittests/SPIRV/CMakeLists.txt

@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
 
 add_clang_unittest(clang-spirv-tests
   InstBuilderTest.cpp
+  StructureTest.cpp
   TestMain.cpp
   StringTest.cpp
   )

+ 184 - 0
tools/clang/unittests/SPIRV/StructureTest.cpp

@@ -0,0 +1,184 @@
+//===- unittests/SPIRV/StructureTest.cpp ------ SPIR-V structures tests ---===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/SPIRV/Structure.h"
+
+#include "SPIRVTestUtils.h"
+
+namespace {
+
+using namespace clang::spirv;
+
+using ::testing::ContainerEq;
+
+TEST(Structure, DefaultConstructedBasicBlockIsEmpty) {
+  auto bb = BasicBlock();
+  EXPECT_TRUE(bb.isEmpty());
+}
+
+TEST(Structure, TakeBasicBlockHaveAllContents) {
+  std::vector<uint32_t> result;
+  auto ib = constructInstBuilder(result);
+
+  auto bb = BasicBlock(42);
+  bb.addInstruction(constructInst(spv::Op::OpReturn, {}));
+  bb.take(&ib);
+
+  std::vector<uint32_t> expected;
+  appendVector(&expected, constructInst(spv::Op::OpLabel, {42}));
+  appendVector(&expected, constructInst(spv::Op::OpReturn, {}));
+
+  EXPECT_THAT(result, ContainerEq(expected));
+  EXPECT_TRUE(bb.isEmpty());
+}
+
+TEST(Structure, AfterClearBasicBlockIsEmpty) {
+  auto bb = BasicBlock(42);
+  bb.addInstruction(constructInst(spv::Op::OpNop, {}));
+  EXPECT_FALSE(bb.isEmpty());
+  bb.clear();
+  EXPECT_TRUE(bb.isEmpty());
+}
+
+TEST(Structure, DefaultConstructedFunctionIsEmpty) {
+  auto f = Function();
+  EXPECT_TRUE(f.isEmpty());
+}
+
+TEST(Structure, TakeFunctionHaveAllContents) {
+  auto f = Function(1, 2, spv::FunctionControlMask::Inline, 3);
+  f.addParameter(1, 42);
+
+  auto bb = BasicBlock(10);
+  bb.addInstruction(constructInst(spv::Op::OpReturn, {}));
+  f.addBasicBlock(std::move(bb));
+
+  std::vector<uint32_t> result;
+  auto ib = constructInstBuilder(result);
+  f.take(&ib);
+
+  std::vector<uint32_t> expected;
+  appendVector(&expected, constructInst(spv::Op::OpFunction, {1, 2, 1, 3}));
+  appendVector(&expected, constructInst(spv::Op::OpFunctionParameter, {1, 42}));
+  appendVector(&expected, constructInst(spv::Op::OpLabel, {10}));
+  appendVector(&expected, constructInst(spv::Op::OpReturn, {}));
+  appendVector(&expected, constructInst(spv::Op::OpFunctionEnd, {}));
+
+  EXPECT_THAT(result, ContainerEq(expected));
+  EXPECT_TRUE(f.isEmpty());
+}
+
+TEST(Structure, AfterClearFunctionIsEmpty) {
+  auto f = Function(1, 2, spv::FunctionControlMask::Inline, 3);
+  f.addParameter(1, 42);
+  EXPECT_FALSE(f.isEmpty());
+  f.clear();
+  EXPECT_TRUE(f.isEmpty());
+}
+
+TEST(Structure, DefaultConstructedModuleIsEmpty) {
+  auto m = SPIRVModule();
+  EXPECT_TRUE(m.isEmpty());
+}
+
+TEST(Structure, AfterClearModuleIsEmpty) {
+  auto m = SPIRVModule();
+  m.setBound(12);
+  EXPECT_FALSE(m.isEmpty());
+  m.clear();
+  EXPECT_TRUE(m.isEmpty());
+}
+
+TEST(Structure, TakeModuleHaveAllContents) {
+  auto m = SPIRVModule();
+  std::vector<uint32_t> expected{spv::MagicNumber, spv::Version,
+                                 /* generator */ 14u << 16, /* bound */ 6, 0};
+
+  m.addCapability(spv::Capability::Shader);
+  appendVector(&expected,
+               constructInst(spv::Op::OpCapability,
+                             {static_cast<uint32_t>(spv::Capability::Shader)}));
+
+  m.addExtension("ext");
+  const uint32_t extWord = 'e' | ('x' << 8) | ('t' << 16);
+  appendVector(&expected, constructInst(spv::Op::OpExtension, {extWord}));
+
+  m.addExtInstSet(5, "gl");
+  const uint32_t glWord = 'g' | ('l' << 8);
+  appendVector(&expected, constructInst(spv::Op::OpExtInstImport, {5, glWord}));
+
+  m.setAddressingModel(spv::AddressingModel::Logical);
+  m.setMemoryModel(spv::MemoryModel::GLSL450);
+  appendVector(
+      &expected,
+      constructInst(spv::Op::OpMemoryModel,
+                    {static_cast<uint32_t>(spv::AddressingModel::Logical),
+                     static_cast<uint32_t>(spv::MemoryModel::GLSL450)}));
+
+  m.addEntryPoint(spv::ExecutionModel::Fragment, 2, "main", {42});
+  const uint32_t mainWord = 'm' | ('a' << 8) | ('i' << 16) | ('n' << 24);
+  appendVector(
+      &expected,
+      constructInst(spv::Op::OpEntryPoint,
+                    {static_cast<uint32_t>(spv::ExecutionModel::Fragment), 2,
+                     mainWord, /* addtional null in name */ 0, 42}));
+
+  m.addExecutionMode(constructInst(
+      spv::Op::OpExecutionMode,
+      {2, static_cast<uint32_t>(spv::ExecutionMode::OriginUpperLeft)}));
+  appendVector(&expected,
+               constructInst(spv::Op::OpExecutionMode,
+                             {2, static_cast<uint32_t>(
+                                     spv::ExecutionMode::OriginUpperLeft)}));
+
+  // TODO: other debug instructions
+
+  m.addDebugName(2, llvm::None, "main");
+  appendVector(&expected,
+               constructInst(spv::Op::OpName,
+                             {2, mainWord, /* additional null in name */ 0}));
+
+  m.addDecoration(constructInst(
+      spv::Op::OpDecorate,
+      {2, static_cast<uint32_t>(spv::Decoration::RelaxedPrecision)}));
+  appendVector(&expected,
+               constructInst(spv::Op::OpDecorate,
+                             {2, static_cast<uint32_t>(
+                                     spv::Decoration::RelaxedPrecision)}));
+
+  m.addType(constructInst(spv::Op::OpTypeVoid, {1}));
+  appendVector(&expected, constructInst(spv::Op::OpTypeVoid, {1}));
+
+  m.addType(constructInst(spv::Op::OpTypeFunction, {3, 1, 1}));
+  appendVector(&expected, constructInst(spv::Op::OpTypeFunction, {3, 1, 1}));
+
+  // TODO: constant
+  // TODO: variable
+
+  auto f = Function(1, 2, spv::FunctionControlMask::MaskNone, 3);
+  auto bb = BasicBlock(4);
+  bb.addInstruction(constructInst(spv::Op::OpReturn, {}));
+  f.addBasicBlock(std::move(bb));
+  m.addFunction(std::move(f));
+  appendVector(&expected, constructInst(spv::Op::OpFunction, {1, 2, 0, 3}));
+  appendVector(&expected, constructInst(spv::Op::OpLabel, {4}));
+  appendVector(&expected, constructInst(spv::Op::OpReturn, {}));
+  appendVector(&expected, constructInst(spv::Op::OpFunctionEnd, {}));
+
+  m.setBound(6);
+
+  std::vector<uint32_t> result;
+  auto ib = constructInstBuilder(result);
+  m.take(&ib);
+
+  EXPECT_THAT(result, ContainerEq(expected));
+  EXPECT_TRUE(m.isEmpty());
+}
+
+} // anonymous namespace