Browse Source

[spirv] Add SPIRVContext and flesh out ModuleBuilder (#378)

* Add SPIRVContext for <result-id> assignment and holding future
  objects.
* Rename SPIRVBuilder to ModuleBuilder and add methods for
  creating functions and basic blocks.
Lei Zhang 8 years ago
parent
commit
344fe2fa44

+ 75 - 0
tools/clang/include/clang/SPIRV/ModuleBuilder.h

@@ -0,0 +1,75 @@
+//===-- ModuleBuilder.h - SPIR-V builder ----------------------*- C++ -*---===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_SPIRV_MODULEBUILDER_H
+#define LLVM_CLANG_SPIRV_MODULEBUILDER_H
+
+#include "clang/SPIRV/InstBuilder.h"
+#include "clang/SPIRV/SPIRVContext.h"
+#include "clang/SPIRV/Structure.h"
+#include "llvm/ADT/Optional.h"
+
+namespace clang {
+namespace spirv {
+
+/// \brief SPIR-V module builder.
+///
+/// This class exports API for constructing SPIR-V binary interactively.
+/// Call beginModule() to start building a SPIR-V module and endModule()
+/// to complete building a module. Call takeModule() to get the final
+/// SPIR-V binary.
+class ModuleBuilder {
+public:
+  enum class Status {
+    Success,
+    ErrNestedModule,       ///< Tried to create module inside module
+    ErrNestedFunction,     ///< Tried to create function inside function
+    ErrNestedBasicBlock,   ///< Tried to crate basic block inside basic block
+    ErrDetachedBasicBlock, ///< Tried to create basic block out of function
+    ErrNoActiveFunction,   ///< Tried to finish building non existing function
+    ErrActiveBasicBlock,   ///< Tried to finish building function when there are
+                           ///< active basic block
+    ErrNoActiveBasicBlock, ///< Tried to insert instructions without active
+                           ///< basic block
+  };
+
+  /// \brief Constructs a ModuleBuilder with the given SPIR-V context.
+  explicit ModuleBuilder(SPIRVContext *);
+
+  /// \brief Begins building a SPIR-V module.
+  Status beginModule();
+  /// \brief Ends building the current module.
+  Status endModule();
+  /// \brief Begins building a SPIR-V function.
+  Status beginFunction(uint32_t funcType, uint32_t returnType);
+  /// \brief Ends building the current function.
+  Status endFunction();
+  /// \brief Begins building a SPIR-V basic block.
+  Status beginBasicBlock();
+  /// \brief Ends building the current SPIR-V basic block with OpReturn.
+  Status endBasicBlockWithReturn();
+
+  /// \brief Takes the SPIR-V module under building.
+  std::vector<uint32_t> takeModule();
+
+private:
+  /// \brief Ends building the current basic block.
+  Status endBasicBlock();
+
+  SPIRVContext &theContext;                 ///< The SPIR-V context.
+  SPIRVModule theModule;                    ///< The module under building.
+  llvm::Optional<Function> theFunction;     ///< The function under building.
+  llvm::Optional<BasicBlock> theBasicBlock; ///< The basic block under building.
+  std::vector<uint32_t> constructSite;      ///< InstBuilder construction site.
+  InstBuilder instBuilder;
+};
+
+} // end namespace spirv
+} // end namespace clang
+
+#endif

+ 0 - 26
tools/clang/include/clang/SPIRV/SPIRVBuilder.h

@@ -1,26 +0,0 @@
-//===-- SPIRVBuilder.h - SPIR-V builder --*- C++ -*------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SPIRV_SPIRVBUILDER_H
-#define LLVM_CLANG_SPIRV_SPIRVBUILDER_H
-
-namespace clang {
-namespace spirv {
-
-class SPIRVBuilder {
-public:
-  SPIRVBuilder() : NextID(0) {}
-
-private:
-  uint32_t NextID;
-};
-
-} // end namespace spirv
-} // end namespace clang
-
-#endif

+ 47 - 0
tools/clang/include/clang/SPIRV/SPIRVContext.h

@@ -0,0 +1,47 @@
+//===-- SPIRVContext.h - Context holding SPIR-V codegen data ----*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_SPIRV_SPIRVCONTEXT_H
+#define LLVM_CLANG_SPIRV_SPIRVCONTEXT_H
+
+#include <unordered_map>
+
+namespace clang {
+namespace spirv {
+
+/// \brief A class for holding various data needed in SPIR-V codegen.
+/// It should outlive all SPIR-V codegen components that requires/allocates
+/// data.
+class SPIRVContext {
+public:
+  /// \brief Constructs a default SPIR-V context.
+  inline SPIRVContext();
+
+  // Disable copy/move constructors/assignments.
+  SPIRVContext(const SPIRVContext &) = delete;
+  SPIRVContext(SPIRVContext &&) = delete;
+  SPIRVContext &operator=(const SPIRVContext &) = delete;
+  SPIRVContext &operator=(SPIRVContext &&) = delete;
+
+  /// \brief Returns the next unused <result-id>.
+  inline uint32_t getNextId() const;
+  /// \brief Consumes the next unused <result-id>.
+  inline uint32_t takeNextId();
+
+private:
+  uint32_t nextId;
+};
+
+SPIRVContext::SPIRVContext() : nextId(1) {}
+uint32_t SPIRVContext::getNextId() const { return nextId; }
+uint32_t SPIRVContext::takeNextId() { return nextId++; }
+
+} // end namespace spirv
+} // end namespace clang
+
+#endif

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

@@ -6,7 +6,7 @@ add_clang_library(clangSPIRV
   EmitSPIRVAction.cpp
   InstBuilderAuto.cpp
   InstBuilderManual.cpp
-  SPIRVBuilder.cpp
+  ModuleBuilder.cpp
   String.cpp
   Structure.cpp
 

+ 9 - 2
tools/clang/lib/SPIRV/EmitSPIRVAction.cpp

@@ -17,6 +17,7 @@
 #include "clang/Basic/FileManager.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Frontend/CompilerInstance.h"
+#include "clang/SPIRV/ModuleBuilder.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -25,14 +26,20 @@ namespace {
 class SPIRVEmitter : public ASTConsumer,
                      public RecursiveASTVisitor<SPIRVEmitter> {
 public:
-  explicit SPIRVEmitter(raw_ostream *Out) : OutStream(*Out) {}
+  explicit SPIRVEmitter(raw_ostream *Out)
+      : OutStream(*Out), TheContext(), Builder(&TheContext) {}
 
   void HandleTranslationUnit(ASTContext &Context) override {
-    OutStream << "SPIR-V";
+    Builder.beginModule();
+    Builder.endModule();
+    std::vector<uint32_t> M = Builder.takeModule();
+    OutStream.write(reinterpret_cast<const char *>(M.data()), M.size() * 4);
   }
 
 private:
   raw_ostream &OutStream;
+  spirv::SPIRVContext TheContext;
+  spirv::ModuleBuilder Builder;
 };
 }
 

+ 103 - 0
tools/clang/lib/SPIRV/ModuleBuilder.cpp

@@ -0,0 +1,103 @@
+//===--- ModuleBuilder.cpp - SPIR-V builder implementation ----*- 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/ModuleBuilder.h"
+
+#include "clang/SPIRV/InstBuilder.h"
+#include "llvm/llvm_assert/assert.h"
+#include "spirv/1.0//spirv.hpp11"
+
+namespace clang {
+namespace spirv {
+
+ModuleBuilder::ModuleBuilder(SPIRVContext *C)
+    : theContext(*C), theModule(), theFunction(llvm::None),
+      theBasicBlock(llvm::None), instBuilder(nullptr) {
+  instBuilder.setConsumer([this](std::vector<uint32_t> &&words) {
+    this->constructSite = std::move(words);
+  });
+}
+
+ModuleBuilder::Status ModuleBuilder::beginModule() {
+  if (!theModule.isEmpty() || theFunction.hasValue() ||
+      theBasicBlock.hasValue())
+    return Status::ErrNestedModule;
+
+  return Status::Success;
+}
+
+ModuleBuilder::Status ModuleBuilder::endModule() {
+  theModule.setBound(theContext.getNextId());
+  return Status::Success;
+}
+
+ModuleBuilder::Status ModuleBuilder::beginFunction(uint32_t funcType,
+                                                   uint32_t returnType) {
+  if (theFunction.hasValue())
+    return Status::ErrNestedFunction;
+
+  theFunction = llvm::Optional<Function>(
+      Function(returnType, theContext.takeNextId(),
+               spv::FunctionControlMask::MaskNone, funcType));
+
+  return Status::Success;
+}
+
+ModuleBuilder::Status ModuleBuilder::endFunction() {
+  if (theBasicBlock.hasValue())
+    return Status::ErrActiveBasicBlock;
+  if (!theFunction.hasValue())
+    return Status::ErrNoActiveFunction;
+
+  theModule.addFunction(std::move(theFunction.getValue()));
+  theFunction.reset();
+
+  return Status::Success;
+}
+
+ModuleBuilder::Status ModuleBuilder::beginBasicBlock() {
+  if (theBasicBlock.hasValue())
+    return Status::ErrNestedBasicBlock;
+  if (!theFunction.hasValue())
+    return Status::ErrDetachedBasicBlock;
+
+  theBasicBlock =
+      llvm::Optional<BasicBlock>(BasicBlock(theContext.takeNextId()));
+
+  return Status::Success;
+}
+
+ModuleBuilder::Status ModuleBuilder::endBasicBlockWithReturn() {
+  if (!theBasicBlock.hasValue())
+    return Status::ErrNoActiveBasicBlock;
+
+  instBuilder.opReturn().x();
+  theBasicBlock.getValue().addInstruction(std::move(constructSite));
+
+  return endBasicBlock();
+}
+
+ModuleBuilder::Status ModuleBuilder::endBasicBlock() {
+  theFunction.getValue().addBasicBlock(std::move(theBasicBlock.getValue()));
+  theBasicBlock.reset();
+  return Status::Success;
+}
+
+std::vector<uint32_t> ModuleBuilder::takeModule() {
+  std::vector<uint32_t> binary;
+  auto ib = InstBuilder([&binary](std::vector<uint32_t> &&words) {
+    binary.insert(binary.end(), words.begin(), words.end());
+  });
+
+  theModule.take(&ib);
+  return std::move(binary);
+}
+
+} // end namespace spirv
+} // end namespace clang

+ 0 - 12
tools/clang/lib/SPIRV/SPIRVBuilder.cpp

@@ -1,12 +0,0 @@
-//===--- SPIRVBuilder.cpp - SPIR-V builder implementation -----------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-namespace clang {
-namespace spirv {} // end namespace spirv
-} // end namespace clang

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

@@ -6,6 +6,8 @@ set(LLVM_LINK_COMPONENTS
 
 add_clang_unittest(clang-spirv-tests
   InstBuilderTest.cpp
+  ModuleBuilderTest.cpp
+  SPIRVContextTest.cpp
   StructureTest.cpp
   TestMain.cpp
   StringTest.cpp

+ 155 - 0
tools/clang/unittests/SPIRV/ModuleBuilderTest.cpp

@@ -0,0 +1,155 @@
+//===- unittests/SPIRV/ModuleBuilderTest.cpp ------ ModuleBuilder 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/ModuleBuilder.h"
+#include "spirv/1.0/spirv.hpp11"
+
+#include "SPIRVTestUtils.h"
+
+namespace {
+
+using namespace clang::spirv;
+
+using ::testing::ContainerEq;
+using ::testing::ElementsAre;
+
+void expectBuildSuccess(ModuleBuilder::Status status) {
+  EXPECT_EQ(ModuleBuilder::Status::Success, status);
+}
+
+TEST(ModuleBuilder, BeginAndThenEndModuleCreatesHeader) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  expectBuildSuccess(builder.endModule());
+  std::vector<uint32_t> spvModule = builder.takeModule();
+
+  // At the very least, running BeginModule() and EndModule() should
+  // create the SPIR-V Header. The header is exactly 5 words long.
+  EXPECT_EQ(spvModule.size(), 5u);
+  EXPECT_THAT(spvModule,
+              ElementsAre(spv::MagicNumber, spv::Version, 14u << 16, 1u, 0u));
+}
+
+TEST(ModuleBuilder, BeginEndFunctionCreatesFunction) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  const auto rType = context.takeNextId();
+  const auto fType = context.takeNextId();
+  const auto fId = context.getNextId();
+  expectBuildSuccess(builder.beginFunction(fType, rType));
+  expectBuildSuccess(builder.endFunction());
+  expectBuildSuccess(builder.endModule());
+  const auto result = builder.takeModule();
+
+  auto expected = getModuleHeader(context.getNextId());
+  appendVector(&expected,
+               constructInst(spv::Op::OpFunction, {rType, fId, 0, fType}));
+  appendVector(&expected, constructInst(spv::Op::OpFunctionEnd, {}));
+  EXPECT_THAT(result, ContainerEq(expected));
+}
+
+TEST(ModuleBuilder, BeginEndBasicBlockCreatesBasicBlock) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  const auto rType = context.takeNextId();
+  const auto fType = context.takeNextId();
+  const auto fId = context.getNextId();
+  expectBuildSuccess(builder.beginFunction(fType, rType));
+  const auto labelId = context.getNextId();
+  expectBuildSuccess(builder.beginBasicBlock());
+  expectBuildSuccess(builder.endBasicBlockWithReturn());
+  expectBuildSuccess(builder.endFunction());
+  expectBuildSuccess(builder.endModule());
+  const auto result = builder.takeModule();
+
+  auto expected = getModuleHeader(context.getNextId());
+  appendVector(&expected,
+               constructInst(spv::Op::OpFunction, {rType, fId, 0, fType}));
+  appendVector(&expected, constructInst(spv::Op::OpLabel, {labelId}));
+  appendVector(&expected, constructInst(spv::Op::OpReturn, {}));
+  appendVector(&expected, constructInst(spv::Op::OpFunctionEnd, {}));
+
+  EXPECT_THAT(result, ContainerEq(expected));
+}
+
+TEST(ModuleBuilder, NestedModuleResultsInError) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  expectBuildSuccess(builder.beginModule());
+  expectBuildSuccess(builder.beginFunction(1, 2));
+  EXPECT_EQ(ModuleBuilder::Status::ErrNestedModule, builder.beginModule());
+}
+
+TEST(ModuleBuilder, NestedFunctionResultsInError) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  expectBuildSuccess(builder.beginFunction(1, 2));
+  EXPECT_EQ(ModuleBuilder::Status::ErrNestedFunction,
+            builder.beginFunction(3, 4));
+}
+
+TEST(ModuleBuilder, NestedBasicBlockResultsInError) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  expectBuildSuccess(builder.beginFunction(1, 2));
+  expectBuildSuccess(builder.beginBasicBlock());
+  EXPECT_EQ(ModuleBuilder::Status::ErrNestedBasicBlock,
+            builder.beginBasicBlock());
+}
+
+TEST(ModuleBuilder, BasicBlockWoFunctionResultsInError) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  EXPECT_EQ(ModuleBuilder::Status::ErrDetachedBasicBlock,
+            builder.beginBasicBlock());
+}
+
+TEST(ModuleBuilder, EndFunctionWoBeginFunctionResultsInError) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  EXPECT_EQ(ModuleBuilder::Status::ErrNoActiveFunction, builder.endFunction());
+}
+
+TEST(ModuleBuilder, EndFunctionWActiveBasicBlockResultsInError) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  expectBuildSuccess(builder.beginFunction(1, 2));
+  expectBuildSuccess(builder.beginBasicBlock());
+  EXPECT_EQ(ModuleBuilder::Status::ErrActiveBasicBlock, builder.endFunction());
+}
+
+TEST(ModuleBuilder, ReturnWActiveBasicBlockResultsInError) {
+  SPIRVContext context;
+  ModuleBuilder builder(&context);
+
+  expectBuildSuccess(builder.beginModule());
+  expectBuildSuccess(builder.beginFunction(1, 2));
+  EXPECT_EQ(ModuleBuilder::Status::ErrNoActiveBasicBlock,
+            builder.endBasicBlockWithReturn());
+}
+
+} // anonymous namespace

+ 35 - 0
tools/clang/unittests/SPIRV/SPIRVContextTest.cpp

@@ -0,0 +1,35 @@
+//===- unittests/SPIRV/SPIRVContextTest.cpp ----- SPIRVContext tests ------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "gmock/gmock.h"
+#include "clang/SPIRV/SPIRVContext.h"
+#include "gtest/gtest.h"
+
+using namespace clang::spirv;
+
+namespace {
+
+TEST(ValidateSPIRVContext, ValidateGetNextId) {
+  SPIRVContext context;
+  // Check that the first ID is 1.
+  EXPECT_EQ(context.getNextId(), 1u);
+  // Check that calling getNextId() multiple times does not increment the ID
+  EXPECT_EQ(context.getNextId(), 1u);
+}
+
+TEST(ValidateSPIRVContext, ValidateTakeNextId) {
+  SPIRVContext context;
+  EXPECT_EQ(context.takeNextId(), 1u);
+  EXPECT_EQ(context.takeNextId(), 2u);
+  EXPECT_EQ(context.getNextId(), 3u);
+}
+
+// TODO: Add more SPIRVContext tests
+
+} // anonymous namespace