Create SPIR-V writer skeleton.

This CL creates the skeleton for the SPIR-V writer.

Bug: tint:5
Change-Id: I849d8766d32d48314a51096710272f9821e2c1c4
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/16641
Reviewed-by: David Neto <dneto@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d6c60a2..bb60a1a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,6 +48,8 @@
   include_directories("${PROJECT_SOURCE_DIR}/third_party/spirv-tools/include")
 endif()
 
+include_directories("${PROJECT_SOURCE_DIR}/third_party/spirv-headers/include")
+
 if(("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") OR
     ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") OR
     (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") AND
diff --git a/samples/main.cc b/samples/main.cc
index dc30009..708ab75 100644
--- a/samples/main.cc
+++ b/samples/main.cc
@@ -22,7 +22,7 @@
 #include "src/reader/wgsl/parser.h"
 #include "src/type_determiner.h"
 #include "src/validator.h"
-#include "src/writer/spv/generator.h"
+#include "src/writer/spirv/generator.h"
 #include "src/writer/wgsl/generator.h"
 #include "src/writer/writer.h"
 
@@ -280,7 +280,8 @@
 
   std::unique_ptr<tint::writer::Writer> writer;
   if (options.format == Format::kSpirv || options.format == Format::kSpvAsm) {
-    writer = std::make_unique<tint::writer::spv::Generator>(std::move(module));
+    writer =
+        std::make_unique<tint::writer::spirv::Generator>(std::move(module));
   } else if (options.format == Format::kWgsl) {
     writer = std::make_unique<tint::writer::wgsl::Generator>(std::move(module));
   } else {
@@ -294,12 +295,12 @@
   }
 
   if (options.format == Format::kSpvAsm) {
-    auto w = static_cast<tint::writer::spv::Generator*>(writer.get());
+    auto w = static_cast<tint::writer::spirv::Generator*>(writer.get());
     auto str = Disassemble(w->result());
     // TODO(dsinclair): Write to file if output_file given
     std::cout << str << std::endl;
   } else if (options.format == Format::kSpirv) {
-    // auto w = static_cast<tint::writer::spv::Generator*>(writer.get());
+    // auto w = static_cast<tint::writer::spirv::Generator*>(writer.get());
     // TODO(dsincliair): Write to to file
   } else if (options.format == Format::kWgsl) {
     auto w = static_cast<tint::writer::wgsl::Generator*>(writer.get());
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c9f494c..6501ac1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -177,8 +177,16 @@
   validator_impl.cc
   validator_impl.h
   # TODO(dsinclair): The writers should all be optional
-  writer/spv/generator.cc
-  writer/spv/generator.h
+  writer/spirv/binary_writer.cc
+  writer/spirv/binary_writer.h
+  writer/spirv/builder.cc
+  writer/spirv/builder.h
+  writer/spirv/generator.cc
+  writer/spirv/generator.h
+  writer/spirv/instruction.cc
+  writer/spirv/instruction.h
+  writer/spirv/operand.cc
+  writer/spirv/operand.h
   writer/wgsl/generator.cc
   writer/wgsl/generator.h
   writer/wgsl/generator_impl.cc
@@ -319,6 +327,10 @@
   reader/wgsl/token_test.cc
   type_manager_test.cc
   validator_impl_import_test.cc
+  writer/spirv/binary_writer_test.cc
+  writer/spirv/builder_test.cc
+  writer/spirv/instruction_test.cc
+  writer/spirv/operand_test.cc
   writer/wgsl/generator_impl_test.cc
 )
 
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 2c5e17f..2c9ae57 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/reader/spv/parser_impl.h"
+
 #include <cstring>
 
 #include "source/opt/build_module.h"
 #include "spirv-tools/libspirv.hpp"
-#include "src/reader/spirv/parser_impl.h"
 
 namespace tint {
 namespace reader {
diff --git a/src/reader/spirv/parser_impl_import_test.cc b/src/reader/spirv/parser_impl_import_test.cc
index e1823c8..772c299 100644
--- a/src/reader/spirv/parser_impl_import_test.cc
+++ b/src/reader/spirv/parser_impl_import_test.cc
@@ -12,12 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/reader/spirv/parser_impl.h"
-
 #include <memory>
 #include <sstream>
 
 #include "gmock/gmock.h"
+#include "src/reader/spirv/parser_impl.h"
 #include "src/reader/spirv/spirv_tools_helpers_test.h"
 
 namespace tint {
diff --git a/src/writer/spirv/binary_writer.cc b/src/writer/spirv/binary_writer.cc
new file mode 100644
index 0000000..a5d8a3f
--- /dev/null
+++ b/src/writer/spirv/binary_writer.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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.
+
+#include "src/writer/spirv/binary_writer.h"
+
+#include <cstring>
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+// TODO(dsinclair): Reserve a generator ID for Tint.
+// https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/spir-v.xml#L75
+const uint32_t kGeneratorId = 0;
+
+}  // namespace
+
+BinaryWriter::BinaryWriter() = default;
+
+BinaryWriter::~BinaryWriter() = default;
+
+bool BinaryWriter::Write(const Builder& builder) {
+  out_.resize(builder.total_size(), 0);
+
+  out_[idx_++] = spv::MagicNumber;
+  out_[idx_++] = 0x00010300;  // Version 1.3
+  out_[idx_++] = kGeneratorId;
+  out_[idx_++] = builder.id_bound();
+  out_[idx_++] = 0;
+
+  builder.iterate([this](const Instruction& inst) {
+    out_[idx_++] =
+        inst.word_length() << 16 | static_cast<uint32_t>(inst.opcode());
+
+    for (const auto& op : inst.operands()) {
+      process_op(op);
+    }
+  });
+  return true;
+}
+
+void BinaryWriter::process_op(const Operand& op) {
+  if (op.IsFloat()) {
+    auto f = op.to_f();
+    memcpy(out_.data() + idx_, &f, 4);
+  } else if (op.IsInt()) {
+    out_[idx_] = op.to_i();
+  } else {
+    const auto& str = op.to_s();
+    // This depends on the vector being initialized to 0 values so the string
+    // is correctly padded.
+    memcpy(out_.data() + idx_, str.c_str(), str.size() + 1);
+  }
+  idx_ += op.length();
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/spirv/binary_writer.h b/src/writer/spirv/binary_writer.h
new file mode 100644
index 0000000..972f001
--- /dev/null
+++ b/src/writer/spirv/binary_writer.h
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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 SRC_WRITER_SPIRV_BINARY_WRITER_H_
+#define SRC_WRITER_SPIRV_BINARY_WRITER_H_
+
+#include <vector>
+
+#include "src/writer/spirv/builder.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// Writer to convert from builder to SPIR-V binary
+class BinaryWriter {
+ public:
+  /// Constructor
+  BinaryWriter();
+  ~BinaryWriter();
+
+  /// Writes the given builder data into a binary
+  /// @param builder the builder to assemble from
+  /// @returns true on success
+  bool Write(const Builder& builder);
+
+  /// @returns the assembled SPIR-V
+  const std::vector<uint32_t>& result() const { return out_; }
+
+ private:
+  void process_op(const Operand& op);
+
+  /// Word index of the next word to fill.
+  size_t idx_ = 0;
+  std::vector<uint32_t> out_;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_WRITER_SPIRV_BINARY_WRITER_H_
diff --git a/src/writer/spirv/binary_writer_test.cc b/src/writer/spirv/binary_writer_test.cc
new file mode 100644
index 0000000..6e6265b
--- /dev/null
+++ b/src/writer/spirv/binary_writer_test.cc
@@ -0,0 +1,117 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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.
+
+#include "src/writer/spirv/binary_writer.h"
+
+#include <cstring>
+
+#include "gtest/gtest.h"
+#include "spirv/unified1/spirv.hpp11"
+#include "src/writer/spirv/builder.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+using BinaryWriterTest = testing::Test;
+
+TEST_F(BinaryWriterTest, Preamble) {
+  Builder b;
+  BinaryWriter bw;
+  ASSERT_TRUE(bw.Write(b));
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 5);
+  EXPECT_EQ(res[0], spv::MagicNumber);
+  EXPECT_EQ(res[1], 0x00010300);  // SPIR-V 1.3
+  EXPECT_EQ(res[2], 0);           // Generator ID
+  EXPECT_EQ(res[3], 1);           // ID Bound
+  EXPECT_EQ(res[4], 0);           // Reserved
+}
+
+TEST_F(BinaryWriterTest, Float) {
+  Builder b;
+  b.push_preamble(spv::Op::OpNop, {Operand::Float(2.4f)});
+  BinaryWriter bw;
+  ASSERT_TRUE(bw.Write(b));
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 7);
+  float f;
+  memcpy(&f, res.data() + 6, 4);
+  EXPECT_EQ(f, 2.4f);
+}
+
+TEST_F(BinaryWriterTest, Int) {
+  Builder b;
+  b.push_preamble(spv::Op::OpNop, {Operand::Int(2)});
+  BinaryWriter bw;
+  ASSERT_TRUE(bw.Write(b));
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 7);
+  EXPECT_EQ(res[6], 2);
+}
+
+TEST_F(BinaryWriterTest, String) {
+  Builder b;
+  b.push_preamble(spv::Op::OpNop, {Operand::String("my_string")});
+  BinaryWriter bw;
+  ASSERT_TRUE(bw.Write(b));
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 9);
+
+  uint8_t* v = reinterpret_cast<uint8_t*>(res.data()) + (6 * 4);
+  EXPECT_EQ(v[0], 'm');
+  EXPECT_EQ(v[1], 'y');
+  EXPECT_EQ(v[2], '_');
+  EXPECT_EQ(v[3], 's');
+  EXPECT_EQ(v[4], 't');
+  EXPECT_EQ(v[5], 'r');
+  EXPECT_EQ(v[6], 'i');
+  EXPECT_EQ(v[7], 'n');
+  EXPECT_EQ(v[8], 'g');
+  EXPECT_EQ(v[9], '\0');
+  EXPECT_EQ(v[10], '\0');
+  EXPECT_EQ(v[11], '\0');
+}
+
+TEST_F(BinaryWriterTest, String_Multiple4Length) {
+  Builder b;
+  b.push_preamble(spv::Op::OpNop, {Operand::String("mystring")});
+  BinaryWriter bw;
+  ASSERT_TRUE(bw.Write(b));
+
+  auto res = bw.result();
+  ASSERT_EQ(res.size(), 9);
+
+  uint8_t* v = reinterpret_cast<uint8_t*>(res.data()) + (6 * 4);
+  EXPECT_EQ(v[0], 'm');
+  EXPECT_EQ(v[1], 'y');
+  EXPECT_EQ(v[2], 's');
+  EXPECT_EQ(v[3], 't');
+  EXPECT_EQ(v[4], 'r');
+  EXPECT_EQ(v[5], 'i');
+  EXPECT_EQ(v[6], 'n');
+  EXPECT_EQ(v[7], 'g');
+  EXPECT_EQ(v[8], '\0');
+  EXPECT_EQ(v[9], '\0');
+  EXPECT_EQ(v[10], '\0');
+  EXPECT_EQ(v[11], '\0');
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
new file mode 100644
index 0000000..4c6f0bd
--- /dev/null
+++ b/src/writer/spirv/builder.cc
@@ -0,0 +1,85 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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.
+
+#include "src/writer/spirv/builder.h"
+
+#include "spirv/unified1/spirv.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+namespace {
+
+uint32_t size_of(const std::vector<Instruction>& instructions) {
+  uint32_t size = 0;
+  for (const auto& inst : instructions)
+    size += inst.word_length();
+
+  return size;
+}
+
+}  // namespace
+
+Builder::Builder() = default;
+
+Builder::~Builder() = default;
+
+bool Builder::Build(const ast::Module&) {
+  push_preamble(spv::Op::OpCapability, {Operand::Int(SpvCapabilityShader)});
+  push_preamble(spv::Op::OpExtInstImport,
+                {result_op(), Operand::String("GLSL.std.450")});
+  push_preamble(spv::Op::OpMemoryModel,
+                {Operand::Int(SpvAddressingModelLogical),
+                 Operand::Int(SpvMemoryModelVulkanKHR)});
+  return true;
+}
+
+Operand Builder::result_op() {
+  return Operand::Int(next_id());
+}
+
+uint32_t Builder::total_size() const {
+  // The 5 covers the magic, version, generator, id bound and reserved.
+  uint32_t size = 5;
+
+  size += size_of(preamble_);
+  size += size_of(debug_);
+  size += size_of(annotations_);
+  size += size_of(types_);
+  size += size_of(instructions_);
+
+  return size;
+}
+
+void Builder::iterate(std::function<void(const Instruction&)> cb) const {
+  for (const auto& inst : preamble_) {
+    cb(inst);
+  }
+  for (const auto& inst : debug_) {
+    cb(inst);
+  }
+  for (const auto& inst : annotations_) {
+    cb(inst);
+  }
+  for (const auto& inst : types_) {
+    cb(inst);
+  }
+  for (const auto& inst : instructions_) {
+    cb(inst);
+  }
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
new file mode 100644
index 0000000..7b27b18
--- /dev/null
+++ b/src/writer/spirv/builder.h
@@ -0,0 +1,112 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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 SRC_WRITER_SPIRV_BUILDER_H_
+#define SRC_WRITER_SPIRV_BUILDER_H_
+
+#include <functional>
+#include <vector>
+
+#include "src/ast/module.h"
+#include "src/writer/spirv/instruction.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// Builder class to create SPIR-V instructions from a module.
+class Builder {
+ public:
+  /// Constructor
+  Builder();
+  ~Builder();
+
+  /// Generates the SPIR-V instructions for the given module
+  /// @param module the module to generate from
+  /// @returns true if the SPIR-V was successfully built
+  bool Build(const ast::Module& module);
+
+  /// @returns the number of uint32_t's needed to make up the results
+  uint32_t total_size() const;
+
+  /// @returns the id bound for this module
+  uint32_t id_bound() const { return next_id_; }
+
+  /// Iterates over all the instructions in the correct order and calls the
+  /// given callback
+  /// @param cb the callback to execute
+  void iterate(std::function<void(const Instruction&)> cb) const;
+
+  /// Adds an instruction to the preamble
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_preamble(spv::Op op, const std::vector<Operand>& operands) {
+    preamble_.push_back(Instruction{op, operands});
+  }
+  /// @returns the preamble
+  const std::vector<Instruction>& preamble() const { return preamble_; }
+  /// Adds an instruction to the debug
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_debug(spv::Op op, const std::vector<Operand>& operands) {
+    debug_.push_back(Instruction{op, operands});
+  }
+  /// @returns the debug instructions
+  const std::vector<Instruction>& debug() const { return debug_; }
+  /// Adds an instruction to the types
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_type(spv::Op op, const std::vector<Operand>& operands) {
+    types_.push_back(Instruction{op, operands});
+  }
+  /// @returns the type instructions
+  const std::vector<Instruction>& type() const { return types_; }
+  /// Adds an instruction to the instruction list
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_inst(spv::Op op, const std::vector<Operand>& operands) {
+    instructions_.push_back(Instruction{op, operands});
+  }
+  /// @returns the instruction list
+  const std::vector<Instruction>& inst() const { return instructions_; }
+  /// Adds an instruction to the annotations
+  /// @param op the op to set
+  /// @param operands the operands for the instruction
+  void push_annot(spv::Op op, const std::vector<Operand>& operands) {
+    annotations_.push_back(Instruction{op, operands});
+  }
+  /// @returns the annotations
+  const std::vector<Instruction>& annot() const { return annotations_; }
+
+ private:
+  Operand result_op();
+  uint32_t next_id() {
+    auto id = next_id_;
+    next_id_ += 1;
+    return id;
+  }
+
+  uint32_t next_id_ = 1;
+  std::vector<Instruction> preamble_;
+  std::vector<Instruction> debug_;
+  std::vector<Instruction> types_;
+  std::vector<Instruction> instructions_;
+  std::vector<Instruction> annotations_;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_WRITER_SPIRV_BUILDER_H_
diff --git a/src/writer/spirv/builder_test.cc b/src/writer/spirv/builder_test.cc
new file mode 100644
index 0000000..5e44c73
--- /dev/null
+++ b/src/writer/spirv/builder_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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.
+
+#include "src/writer/spirv/builder.h"
+
+#include "gtest/gtest.h"
+#include "spirv/unified1/spirv.hpp11"
+#include "src/ast/module.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+using BuilderTest = testing::Test;
+
+TEST_F(BuilderTest, InsertsPreamble) {
+  ast::Module m;
+  Builder b;
+  ASSERT_TRUE(b.Build(m));
+  ASSERT_EQ(b.preamble().size(), 3);
+  auto pre = b.preamble();
+  EXPECT_EQ(pre[0].opcode(), spv::Op::OpCapability);
+  EXPECT_EQ(pre[1].opcode(), spv::Op::OpExtInstImport);
+  EXPECT_EQ(pre[2].opcode(), spv::Op::OpMemoryModel);
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/spv/generator.cc b/src/writer/spirv/generator.cc
similarity index 83%
rename from src/writer/spv/generator.cc
rename to src/writer/spirv/generator.cc
index 8d15522..b47838c 100644
--- a/src/writer/spv/generator.cc
+++ b/src/writer/spirv/generator.cc
@@ -12,22 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/writer/spv/generator.h"
+#include "src/writer/spirv/generator.h"
 
 #include <utility>
 
 namespace tint {
 namespace writer {
-namespace spv {
+namespace spirv {
 
 Generator::Generator(ast::Module module) : writer::Writer(std::move(module)) {}
 
 Generator::~Generator() = default;
 
 bool Generator::Generate() {
-  return true;
+  if (!builder_.Build(module_))
+    return false;
+
+  return writer_.Write(builder_);
 }
 
-}  // namespace spv
+}  // namespace spirv
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/spv/generator.h b/src/writer/spirv/generator.h
similarity index 74%
rename from src/writer/spv/generator.h
rename to src/writer/spirv/generator.h
index e677019..3a14ec5 100644
--- a/src/writer/spv/generator.h
+++ b/src/writer/spirv/generator.h
@@ -12,16 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_WRITER_SPV_GENERATOR_H_
-#define SRC_WRITER_SPV_GENERATOR_H_
+#ifndef SRC_WRITER_SPIRV_GENERATOR_H_
+#define SRC_WRITER_SPIRV_GENERATOR_H_
 
 #include <vector>
 
+#include "src/ast/module.h"
+#include "src/writer/spirv/binary_writer.h"
+#include "src/writer/spirv/builder.h"
 #include "src/writer/writer.h"
 
 namespace tint {
 namespace writer {
-namespace spv {
+namespace spirv {
 
 /// Class to generate SPIR-V from a Tint module
 class Generator : public writer::Writer {
@@ -36,14 +39,15 @@
   bool Generate() override;
 
   /// @returns the result data
-  const std::vector<uint32_t>& result() const { return result_; }
+  const std::vector<uint32_t>& result() const { return writer_.result(); }
 
  private:
-  std::vector<uint32_t> result_;
+  Builder builder_;
+  BinaryWriter writer_;
 };
 
-}  // namespace spv
+}  // namespace spirv
 }  // namespace writer
 }  // namespace tint
 
-#endif  // SRC_WRITER_SPV_GENERATOR_H_
+#endif  // SRC_WRITER_SPIRV_GENERATOR_H_
diff --git a/src/writer/spv/generator.cc b/src/writer/spirv/instruction.cc
similarity index 62%
copy from src/writer/spv/generator.cc
copy to src/writer/spirv/instruction.cc
index 8d15522..d4994d6 100644
--- a/src/writer/spv/generator.cc
+++ b/src/writer/spirv/instruction.cc
@@ -12,22 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/writer/spv/generator.h"
+#include "src/writer/spirv/instruction.h"
 
 #include <utility>
 
 namespace tint {
 namespace writer {
-namespace spv {
+namespace spirv {
 
-Generator::Generator(ast::Module module) : writer::Writer(std::move(module)) {}
+Instruction::Instruction(spv::Op op, std::vector<Operand> operands)
+    : op_(op), operands_(std::move(operands)) {}
 
-Generator::~Generator() = default;
+Instruction::~Instruction() = default;
 
-bool Generator::Generate() {
-  return true;
+uint32_t Instruction::word_length() const {
+  uint32_t size = 1;  // Initial 1 for the op and size
+  for (const auto& op : operands_) {
+    size += op.length();
+  }
+  return size;
 }
 
-}  // namespace spv
+}  // namespace spirv
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/spirv/instruction.h b/src/writer/spirv/instruction.h
new file mode 100644
index 0000000..4632eeb
--- /dev/null
+++ b/src/writer/spirv/instruction.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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 SRC_WRITER_SPIRV_INSTRUCTION_H_
+#define SRC_WRITER_SPIRV_INSTRUCTION_H_
+
+#include <vector>
+
+#include "spirv/unified1/spirv.hpp11"
+#include "src/writer/spirv/operand.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// A single SPIR-V instruction
+class Instruction {
+ public:
+  /// Constructor
+  /// @param op the op to generate
+  /// @param operands the operand values for the instruction
+  Instruction(spv::Op op, std::vector<Operand> operands);
+  /// Copy Constructor
+  Instruction(const Instruction&) = default;
+  ~Instruction();
+
+  /// @returns the instructions op
+  spv::Op opcode() const { return op_; }
+
+  /// @returns the instructions operands
+  const std::vector<Operand>& operands() const { return operands_; }
+
+  /// @returns the number of uint32_t's needed to hold the instruction
+  uint32_t word_length() const;
+
+ private:
+  spv::Op op_ = spv::Op::OpNop;
+  std::vector<Operand> operands_;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_WRITER_SPIRV_INSTRUCTION_H_
diff --git a/src/writer/spirv/instruction_test.cc b/src/writer/spirv/instruction_test.cc
new file mode 100644
index 0000000..21b6e20
--- /dev/null
+++ b/src/writer/spirv/instruction_test.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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.
+
+#include "src/writer/spirv/instruction.h"
+
+#include "gtest/gtest.h"
+#include "src/writer/spirv/operand.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+using InstructionTest = testing::Test;
+
+TEST_F(InstructionTest, Create) {
+  Instruction i(spv::Op::OpEntryPoint, {Operand::Float(1.2f), Operand::Int(1),
+                                        Operand::String("my_str")});
+  EXPECT_EQ(i.opcode(), spv::Op::OpEntryPoint);
+  ASSERT_EQ(i.operands().size(), 3);
+
+  const auto& ops = i.operands();
+  EXPECT_TRUE(ops[0].IsFloat());
+  EXPECT_FLOAT_EQ(ops[0].to_f(), 1.2);
+
+  EXPECT_TRUE(ops[1].IsInt());
+  EXPECT_EQ(ops[1].to_i(), 1);
+
+  EXPECT_TRUE(ops[2].IsString());
+  EXPECT_EQ(ops[2].to_s(), "my_str");
+}
+
+TEST_F(InstructionTest, Length) {
+  Instruction i(spv::Op::OpEntryPoint, {Operand::Float(1.2f), Operand::Int(1),
+                                        Operand::String("my_str")});
+  EXPECT_EQ(i.word_length(), 5);
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/spirv/operand.cc b/src/writer/spirv/operand.cc
new file mode 100644
index 0000000..1a991ad
--- /dev/null
+++ b/src/writer/spirv/operand.cc
@@ -0,0 +1,66 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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.
+
+#include "src/writer/spirv/operand.h"
+
+#include <cmath>
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+// static
+Operand Operand::Float(float val) {
+  Operand o(Kind::kFloat);
+  o.set_float(val);
+  return o;
+}
+
+// static
+Operand Operand::Int(uint32_t val) {
+  Operand o(Kind::kInt);
+  o.set_int(val);
+  return o;
+}
+
+// static
+Operand Operand::String(const std::string& val) {
+  Operand o(Kind::kString);
+  o.set_string(val);
+  return o;
+}
+
+Operand::Operand(Kind kind) : kind_(kind) {}
+
+Operand::~Operand() = default;
+
+uint32_t Operand::length() const {
+  uint32_t val = 0;
+  switch (kind_) {
+    case Kind::kFloat:
+    case Kind::kInt:
+      val = 1;
+      break;
+    case Kind::kString:
+      // SPIR-V always nul-terminates strings. The length is rounded up to a
+      // multiple of 4 bytes with 0 bytes padding the end.
+      val = static_cast<uint32_t>(ceil((str_val_.length() + 1) / 4.0));
+      break;
+  }
+  return val;
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/spirv/operand.h b/src/writer/spirv/operand.h
new file mode 100644
index 0000000..161f263
--- /dev/null
+++ b/src/writer/spirv/operand.h
@@ -0,0 +1,90 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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 SRC_WRITER_SPIRV_OPERAND_H_
+#define SRC_WRITER_SPIRV_OPERAND_H_
+
+#include <string>
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+/// A single SPIR-V instruction operand
+class Operand {
+ public:
+  /// The kind of the operand
+  // Note, the `kInt` will cover most cases as things like IDs in SPIR-V are
+  // just ints for the purpose of converting to binary.
+  enum Kind { kInt = 0, kFloat, kString };
+
+  /// Creates a float operand
+  /// @param val the float value
+  /// @returns the operand
+  static Operand Float(float val);
+  /// Creates an int operand
+  /// @param val the int value
+  /// @returns the operand
+  static Operand Int(uint32_t val);
+  /// Creates a string operand
+  /// @param val the string value
+  /// @returns the operand
+  static Operand String(const std::string& val);
+
+  /// Constructor
+  /// @param kind the type of operand
+  explicit Operand(Kind kind);
+  /// Copy Constructor
+  Operand(const Operand&) = default;
+  ~Operand();
+
+  /// Sets the float value
+  /// @param val the value to set
+  void set_float(float val) { float_val_ = val; }
+  /// Sets the int value
+  /// @param val the value to set
+  void set_int(uint32_t val) { int_val_ = val; }
+  /// Sets the string value
+  /// @param val the value to set
+  void set_string(const std::string& val) { str_val_ = val; }
+
+  /// @returns true if this is a float operand
+  bool IsFloat() const { return kind_ == Kind::kFloat; }
+  /// @returns true if this is an integer operand
+  bool IsInt() const { return kind_ == Kind::kInt; }
+  /// @returns true if this is a string operand
+  bool IsString() const { return kind_ == Kind::kString; }
+
+  /// @returns the number of uint32_t's needed for this operand
+  uint32_t length() const;
+
+  /// @returns the float value
+  float to_f() const { return float_val_; }
+  /// @returns the int value
+  uint32_t to_i() const { return int_val_; }
+  /// @returns the string value
+  const std::string& to_s() const { return str_val_; }
+
+ private:
+  Kind kind_ = Kind::kInt;
+  float float_val_ = 0.0;
+  uint32_t int_val_ = 0;
+  std::string str_val_;
+};
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_WRITER_SPIRV_OPERAND_H_
diff --git a/src/writer/spirv/operand_test.cc b/src/writer/spirv/operand_test.cc
new file mode 100644
index 0000000..04db16f
--- /dev/null
+++ b/src/writer/spirv/operand_test.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 The Tint Authors.
+//
+// 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.
+
+#include "src/writer/spirv/operand.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+using OperandTest = testing::Test;
+
+TEST_F(OperandTest, CreateFloat) {
+  auto o = Operand::Float(1.2f);
+  EXPECT_TRUE(o.IsFloat());
+  EXPECT_FLOAT_EQ(o.to_f(), 1.2);
+}
+
+TEST_F(OperandTest, CreateInt) {
+  auto o = Operand::Int(1);
+  EXPECT_TRUE(o.IsInt());
+  EXPECT_EQ(o.to_i(), 1);
+}
+
+TEST_F(OperandTest, CreateString) {
+  auto o = Operand::String("my string");
+  EXPECT_TRUE(o.IsString());
+  EXPECT_EQ(o.to_s(), "my string");
+}
+
+TEST_F(OperandTest, Length_Float) {
+  auto o = Operand::Float(1.2f);
+  EXPECT_EQ(o.length(), 1);
+}
+
+TEST_F(OperandTest, Length_Int) {
+  auto o = Operand::Int(1);
+  EXPECT_EQ(o.length(), 1);
+}
+
+TEST_F(OperandTest, Length_String) {
+  auto o = Operand::String("my string");
+  EXPECT_EQ(o.length(), 3);
+}
+
+TEST_F(OperandTest, Length_String_Empty) {
+  auto o = Operand::String("");
+  EXPECT_EQ(o.length(), 1);
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint