[hlsl-writer] Refactor output emission.

This CL updates the HLSL backend to take the output stream as a
parameter. This is needed because there are cases where we have to
generate the resulting stream out of order. This will allow that to
happen.

Bug: tint:7
Change-Id: Id1877a07e536a84da0555f207d1030588d44c034
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/27440
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 460221e..ca2f5e5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1088,6 +1088,8 @@
     "src/writer/hlsl/generator_impl_unary_op_test.cc",
     "src/writer/hlsl/generator_impl_variable_decl_statement_test.cc",
     "src/writer/hlsl/namer_test.cc",
+    "src/writer/hlsl/test_helper.cc",
+    "src/writer/hlsl/test_helper.h",
   ]
 
   configs += [
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 825b1fc..1451fb9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -599,6 +599,8 @@
     writer/hlsl/generator_impl_unary_op_test.cc
     writer/hlsl/generator_impl_variable_decl_statement_test.cc
     writer/hlsl/namer_test.cc
+    writer/hlsl/test_helper.cc
+    writer/hlsl/test_helper.h
   )
 endif()
 
diff --git a/src/type_determiner.cc b/src/type_determiner.cc
index 6af10e0..a559d8b 100644
--- a/src/type_determiner.cc
+++ b/src/type_determiner.cc
@@ -831,6 +831,7 @@
     const ast::ExpressionList& params,
     uint32_t* id) {
   if (path != "GLSL.std.450") {
+    set_error(source, "unknown import path " + path);
     return nullptr;
   }
 
diff --git a/src/writer/hlsl/generator.cc b/src/writer/hlsl/generator.cc
index 03d68ac..84c6999 100644
--- a/src/writer/hlsl/generator.cc
+++ b/src/writer/hlsl/generator.cc
@@ -26,7 +26,7 @@
 Generator::~Generator() = default;
 
 bool Generator::Generate() {
-  auto ret = impl_.Generate();
+  auto ret = impl_.Generate(out_);
   if (!ret) {
     error_ = impl_.error();
   }
@@ -34,7 +34,7 @@
 }
 
 std::string Generator::result() const {
-  return impl_.result();
+  return out_.str();
 }
 
 std::string Generator::error() const {
diff --git a/src/writer/hlsl/generator.h b/src/writer/hlsl/generator.h
index c3cf473..9820fab 100644
--- a/src/writer/hlsl/generator.h
+++ b/src/writer/hlsl/generator.h
@@ -15,6 +15,7 @@
 #ifndef SRC_WRITER_HLSL_GENERATOR_H_
 #define SRC_WRITER_HLSL_GENERATOR_H_
 
+#include <sstream>
 #include <string>
 
 #include "src/writer/hlsl/generator_impl.h"
@@ -43,6 +44,7 @@
   std::string error() const;
 
  private:
+  std::ostringstream out_;
   GeneratorImpl impl_;
 };
 
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index cb9b824..c2b90a3 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -102,44 +102,50 @@
 
 GeneratorImpl::~GeneratorImpl() = default;
 
-bool GeneratorImpl::Generate() {
+void GeneratorImpl::make_indent(std::ostream& out) {
+  for (size_t i = 0; i < indent_; i++) {
+    out << " ";
+  }
+}
+
+bool GeneratorImpl::Generate(std::ostream& out) {
   for (const auto& global : module_->global_variables()) {
     register_global(global.get());
   }
 
   for (auto* const alias : module_->alias_types()) {
-    if (!EmitAliasType(alias)) {
+    if (!EmitAliasType(out, alias)) {
       return false;
     }
   }
   if (!module_->alias_types().empty()) {
-    out_ << std::endl;
+    out << std::endl;
   }
 
   for (const auto& var : module_->global_variables()) {
     if (!var->is_const()) {
       continue;
     }
-    if (!EmitProgramConstVariable(var.get())) {
+    if (!EmitProgramConstVariable(out, var.get())) {
       return false;
     }
   }
 
   for (const auto& ep : module_->entry_points()) {
-    if (!EmitEntryPointData(ep.get())) {
+    if (!EmitEntryPointData(out, ep.get())) {
       return false;
     }
   }
   for (const auto& func : module_->functions()) {
-    if (!EmitFunction(func.get())) {
+    if (!EmitFunction(out, func.get())) {
       return false;
     }
   }
   for (const auto& ep : module_->entry_points()) {
-    if (!EmitEntryPointFunction(ep.get())) {
+    if (!EmitEntryPointFunction(out, ep.get())) {
       return false;
     }
-    out_ << std::endl;
+    out << std::endl;
   }
 
   return true;
@@ -171,9 +177,9 @@
       break;
     }
     case VarType::kOut: {
-      auto out_it = ep_name_to_out_data_.find(current_ep_name_);
-      if (out_it != ep_name_to_out_data_.end()) {
-        name = out_it->second.var_name;
+      auto outit = ep_name_to_out_data_.find(current_ep_name_);
+      if (outit != ep_name_to_out_data_.end()) {
+        name = outit->second.var_name;
       }
       break;
     }
@@ -181,120 +187,123 @@
   return name;
 }
 
-bool GeneratorImpl::EmitAliasType(const ast::type::AliasType* alias) {
-  make_indent();
+bool GeneratorImpl::EmitAliasType(std::ostream& out,
+                                  const ast::type::AliasType* alias) {
+  make_indent(out);
 
   if (alias->type()->IsStruct()) {
-    if (!EmitType(alias->type(), namer_.NameFor(alias->name()))) {
+    if (!EmitType(out, alias->type(), namer_.NameFor(alias->name()))) {
       return false;
     }
-    out_ << ";" << std::endl;
+    out << ";" << std::endl;
   } else {
-    out_ << "typedef ";
-    if (!EmitType(alias->type(), "")) {
+    out << "typedef ";
+    if (!EmitType(out, alias->type(), "")) {
       return false;
     }
-    out_ << " " << namer_.NameFor(alias->name()) << ";" << std::endl;
+    out << " " << namer_.NameFor(alias->name()) << ";" << std::endl;
   }
 
   return true;
 }
 
-bool GeneratorImpl::EmitArrayAccessor(ast::ArrayAccessorExpression* expr) {
+bool GeneratorImpl::EmitArrayAccessor(std::ostream& out,
+                                      ast::ArrayAccessorExpression* expr) {
   // Handle writing into a storage buffer array
   if (is_storage_buffer_access(expr)) {
-    return EmitStorageBufferAccessor(expr, nullptr);
+    return EmitStorageBufferAccessor(out, expr, nullptr);
   }
 
-  if (!EmitExpression(expr->array())) {
+  if (!EmitExpression(out, expr->array())) {
     return false;
   }
-  out_ << "[";
+  out << "[";
 
-  if (!EmitExpression(expr->idx_expr())) {
+  if (!EmitExpression(out, expr->idx_expr())) {
     return false;
   }
-  out_ << "]";
+  out << "]";
 
   return true;
 }
 
-bool GeneratorImpl::EmitAs(ast::AsExpression* expr) {
+bool GeneratorImpl::EmitAs(std::ostream& out, ast::AsExpression* expr) {
   if (!expr->type()->IsF32() && !expr->type()->IsI32() &&
       !expr->type()->IsU32()) {
     error_ = "Unable to do as cast to type " + expr->type()->type_name();
     return false;
   }
 
-  out_ << "as";
-  if (!EmitType(expr->type(), "")) {
+  out << "as";
+  if (!EmitType(out, expr->type(), "")) {
     return false;
   }
-  out_ << "(";
-  if (!EmitExpression(expr->expr())) {
+  out << "(";
+  if (!EmitExpression(out, expr->expr())) {
     return false;
   }
-  out_ << ")";
+  out << ")";
   return true;
 }
 
-bool GeneratorImpl::EmitAssign(ast::AssignmentStatement* stmt) {
-  make_indent();
+bool GeneratorImpl::EmitAssign(std::ostream& out,
+                               ast::AssignmentStatement* stmt) {
+  make_indent(out);
 
   // If the LHS is an accessor into a storage buffer then we have to
   // emit a Store operation instead of an ='s.
   if (stmt->lhs()->IsMemberAccessor()) {
     auto* mem = stmt->lhs()->AsMemberAccessor();
     if (is_storage_buffer_access(mem)) {
-      if (!EmitStorageBufferAccessor(mem, stmt->rhs())) {
+      if (!EmitStorageBufferAccessor(out, mem, stmt->rhs())) {
         return false;
       }
-      out_ << ";" << std::endl;
+      out << ";" << std::endl;
       return true;
     }
   } else if (stmt->lhs()->IsArrayAccessor()) {
     auto* ary = stmt->lhs()->AsArrayAccessor();
     if (is_storage_buffer_access(ary)) {
-      if (!EmitStorageBufferAccessor(ary, stmt->rhs())) {
+      if (!EmitStorageBufferAccessor(out, ary, stmt->rhs())) {
         return false;
       }
-      out_ << ";" << std::endl;
+      out << ";" << std::endl;
       return true;
     }
   }
 
-  if (!EmitExpression(stmt->lhs())) {
+  if (!EmitExpression(out, stmt->lhs())) {
     return false;
   }
 
-  out_ << " = ";
+  out << " = ";
 
-  if (!EmitExpression(stmt->rhs())) {
+  if (!EmitExpression(out, stmt->rhs())) {
     return false;
   }
 
-  out_ << ";" << std::endl;
+  out << ";" << std::endl;
 
   return true;
 }
 
-bool GeneratorImpl::EmitBinary(ast::BinaryExpression* expr) {
-  out_ << "(";
+bool GeneratorImpl::EmitBinary(std::ostream& out, ast::BinaryExpression* expr) {
+  out << "(";
 
-  if (!EmitExpression(expr->lhs())) {
+  if (!EmitExpression(out, expr->lhs())) {
     return false;
   }
-  out_ << " ";
+  out << " ";
 
   switch (expr->op()) {
     case ast::BinaryOp::kAnd:
-      out_ << "&";
+      out << "&";
       break;
     case ast::BinaryOp::kOr:
-      out_ << "|";
+      out << "|";
       break;
     case ast::BinaryOp::kXor:
-      out_ << "^";
+      out << "^";
       break;
     case ast::BinaryOp::kLogicalAnd:
       // TODO(dsinclair): Implement support ...
@@ -305,100 +314,103 @@
       error_ = "|| not supported yet";
       return false;
     case ast::BinaryOp::kEqual:
-      out_ << "==";
+      out << "==";
       break;
     case ast::BinaryOp::kNotEqual:
-      out_ << "!=";
+      out << "!=";
       break;
     case ast::BinaryOp::kLessThan:
-      out_ << "<";
+      out << "<";
       break;
     case ast::BinaryOp::kGreaterThan:
-      out_ << ">";
+      out << ">";
       break;
     case ast::BinaryOp::kLessThanEqual:
-      out_ << "<=";
+      out << "<=";
       break;
     case ast::BinaryOp::kGreaterThanEqual:
-      out_ << ">=";
+      out << ">=";
       break;
     case ast::BinaryOp::kShiftLeft:
-      out_ << "<<";
+      out << "<<";
       break;
     case ast::BinaryOp::kShiftRight:
       // TODO(dsinclair): MSL is based on C++14, and >> in C++14 has
       // implementation-defined behaviour for negative LHS.  We may have to
       // generate extra code to implement WGSL-specified behaviour for negative
       // LHS.
-      out_ << R"(>>)";
+      out << R"(>>)";
       break;
 
     case ast::BinaryOp::kAdd:
-      out_ << "+";
+      out << "+";
       break;
     case ast::BinaryOp::kSubtract:
-      out_ << "-";
+      out << "-";
       break;
     case ast::BinaryOp::kMultiply:
-      out_ << "*";
+      out << "*";
       break;
     case ast::BinaryOp::kDivide:
-      out_ << "/";
+      out << "/";
       break;
     case ast::BinaryOp::kModulo:
-      out_ << "%";
+      out << "%";
       break;
     case ast::BinaryOp::kNone:
       error_ = "missing binary operation type";
       return false;
   }
-  out_ << " ";
+  out << " ";
 
-  if (!EmitExpression(expr->rhs())) {
+  if (!EmitExpression(out, expr->rhs())) {
     return false;
   }
 
-  out_ << ")";
+  out << ")";
   return true;
 }
 
-bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
-  out_ << "{" << std::endl;
+bool GeneratorImpl::EmitBlock(std::ostream& out,
+                              const ast::BlockStatement* stmt) {
+  out << "{" << std::endl;
   increment_indent();
 
   for (const auto& s : *stmt) {
-    if (!EmitStatement(s.get())) {
+    if (!EmitStatement(out, s.get())) {
       return false;
     }
   }
 
   decrement_indent();
-  make_indent();
-  out_ << "}";
+  make_indent(out);
+  out << "}";
 
   return true;
 }
 
-bool GeneratorImpl::EmitBlockAndNewline(const ast::BlockStatement* stmt) {
-  const bool result = EmitBlock(stmt);
+bool GeneratorImpl::EmitBlockAndNewline(std::ostream& out,
+                                        const ast::BlockStatement* stmt) {
+  const bool result = EmitBlock(out, stmt);
   if (result) {
-    out_ << std::endl;
+    out << std::endl;
   }
   return result;
 }
 
-bool GeneratorImpl::EmitIndentedBlockAndNewline(ast::BlockStatement* stmt) {
-  make_indent();
-  const bool result = EmitBlock(stmt);
+bool GeneratorImpl::EmitIndentedBlockAndNewline(std::ostream& out,
+                                                ast::BlockStatement* stmt) {
+  make_indent(out);
+  const bool result = EmitBlock(out, stmt);
   if (result) {
-    out_ << std::endl;
+    out << std::endl;
   }
   return result;
 }
 
-bool GeneratorImpl::EmitBreak(ast::BreakStatement*) {
-  make_indent();
-  out_ << "break;" << std::endl;
+bool GeneratorImpl::EmitBreak(std::ostream& out, ast::BreakStatement*) {
+  make_indent(out);
+  out << "break;" << std::endl;
   return true;
 }
 
@@ -445,7 +457,7 @@
   return "";
 }
 
-bool GeneratorImpl::EmitCall(ast::CallExpression* expr) {
+bool GeneratorImpl::EmitCall(std::ostream& out, ast::CallExpression* expr) {
   if (!expr->func()->IsIdentifier()) {
     error_ = "invalid function name";
     return 0;
@@ -482,11 +494,11 @@
       // auto* param1 = param[1].get();
       // auto* name1 = generate_name("outer_product_expr_1");
 
-      // make_indent();
-      // if (!EmitType(expr->result_type(), "")) {
+      // make_indent(out);
+      // if (!EmitType(out, expr->result_type(), "")) {
       //   return false;
       // }
-      // out_ << "(";
+      // out << "(";
 
       // auto param1_type = params[1]->result_type()->UnwrapPtrIfNeeded();
       // if (!param1_type->IsVector()) {
@@ -497,21 +509,21 @@
 
       // for (uint32_t i = 0; i < param1_type->AsVector()->size(); ++i) {
       //   if (i > 0) {
-      //     out_ << ", ";
+      //     out << ", ";
       //   }
 
-      //   if (!EmitExpression(params[0].get())) {
+      //   if (!EmitExpression(out, params[0].get())) {
       //     return false;
       //   }
-      //   out_ << " * ";
+      //   out << " * ";
 
-      //   if (!EmitExpression(params[1].get())) {
+      //   if (!EmitExpression(out, params[1].get())) {
       //     return false;
       //   }
-      //   out_ << "[" << i << "]";
+      //   out << "[" << i << "]";
       // }
 
-      // out_ << ")";
+      // out << ")";
     } else {
       auto name = generate_intrinsic_name(ident->name());
       if (name.empty()) {
@@ -520,22 +532,22 @@
         return false;
       }
 
-      make_indent();
-      out_ << name << "(";
+      make_indent(out);
+      out << name << "(";
 
       bool first = true;
       for (const auto& param : params) {
         if (!first) {
-          out_ << ", ";
+          out << ", ";
         }
         first = false;
 
-        if (!EmitExpression(param.get())) {
+        if (!EmitExpression(out, param.get())) {
           return false;
         }
       }
 
-      out_ << ")";
+      out << ")";
     }
     return true;
   }
@@ -553,13 +565,13 @@
       return false;
     }
 
-    out_ << name << "(";
+    out << name << "(";
 
     bool first = true;
     if (has_referenced_in_var_needing_struct(func)) {
       auto var_name = current_ep_var_name(VarType::kIn);
       if (!var_name.empty()) {
-        out_ << var_name;
+        out << var_name;
         first = false;
       }
     }
@@ -567,33 +579,34 @@
       auto var_name = current_ep_var_name(VarType::kOut);
       if (!var_name.empty()) {
         if (!first) {
-          out_ << ", ";
+          out << ", ";
         }
         first = false;
-        out_ << var_name;
+        out << var_name;
       }
     }
 
     const auto& params = expr->params();
     for (const auto& param : params) {
       if (!first) {
-        out_ << ", ";
+        out << ", ";
       }
       first = false;
 
-      if (!EmitExpression(param.get())) {
+      if (!EmitExpression(out, param.get())) {
         return false;
       }
     }
 
-    out_ << ")";
+    out << ")";
   } else {
-    return EmitImportFunction(expr);
+    return EmitImportFunction(out, expr);
   }
   return true;
 }
 
-bool GeneratorImpl::EmitImportFunction(ast::CallExpression* expr) {
+bool GeneratorImpl::EmitImportFunction(std::ostream& out,
+                                       ast::CallExpression* expr) {
   auto* ident = expr->func()->AsIdentifier();
 
   auto* imp = module_->FindImportByName(ident->path());
@@ -639,45 +652,45 @@
     case GLSLstd450Tan:
     case GLSLstd450Tanh:
     case GLSLstd450Trunc:
-      out_ << ident->name();
+      out << ident->name();
       break;
     case GLSLstd450Fract:
-      out_ << "frac";
+      out << "frac";
       break;
     case GLSLstd450InterpolateAtCentroid:
-      out_ << "EvaluateAttributeAtCentroid";
+      out << "EvaluateAttributeAtCentroid";
       break;
     case GLSLstd450InverseSqrt:
-      out_ << "rsqrt";
+      out << "rsqrt";
       break;
     case GLSLstd450FMix:
-      out_ << "mix";
+      out << "mix";
       break;
     case GLSLstd450SSign:
     case GLSLstd450FSign:
-      out_ << "sign";
+      out << "sign";
       break;
     case GLSLstd450FAbs:
     case GLSLstd450SAbs:
-      out_ << "abs";
+      out << "abs";
       break;
     case GLSLstd450FMax:
     case GLSLstd450NMax:
     case GLSLstd450SMax:
     case GLSLstd450UMax:
-      out_ << "max";
+      out << "max";
       break;
     case GLSLstd450FMin:
     case GLSLstd450NMin:
     case GLSLstd450SMin:
     case GLSLstd450UMin:
-      out_ << "min";
+      out << "min";
       break;
     case GLSLstd450FClamp:
     case GLSLstd450SClamp:
     case GLSLstd450NClamp:
     case GLSLstd450UClamp:
-      out_ << "clamp";
+      out << "clamp";
       break;
     // TODO(dsinclair): Determine mappings for the following
     case GLSLstd450Atanh:
@@ -692,172 +705,175 @@
       return false;
   }
 
-  out_ << "(";
+  out << "(";
   bool first = true;
   const auto& params = expr->params();
   for (const auto& param : params) {
     if (!first) {
-      out_ << ", ";
+      out << ", ";
     }
     first = false;
 
-    if (!EmitExpression(param.get())) {
+    if (!EmitExpression(out, param.get())) {
       return false;
     }
   }
-  out_ << ")";
+  out << ")";
 
   return true;
 }
 
-bool GeneratorImpl::EmitCast(ast::CastExpression* expr) {
-  if (!EmitType(expr->type(), "")) {
+bool GeneratorImpl::EmitCast(std::ostream& out, ast::CastExpression* expr) {
+  if (!EmitType(out, expr->type(), "")) {
     return false;
   }
 
-  out_ << "(";
-  if (!EmitExpression(expr->expr())) {
+  out << "(";
+  if (!EmitExpression(out, expr->expr())) {
     return false;
   }
-  out_ << ")";
+  out << ")";
   return true;
 }
 
-bool GeneratorImpl::EmitCase(ast::CaseStatement* stmt) {
-  make_indent();
+bool GeneratorImpl::EmitCase(std::ostream& out, ast::CaseStatement* stmt) {
+  make_indent(out);
 
   if (stmt->IsDefault()) {
-    out_ << "default:";
+    out << "default:";
   } else {
     bool first = true;
     for (const auto& selector : stmt->selectors()) {
       if (!first) {
-        out_ << std::endl;
-        make_indent();
+        out << std::endl;
+        make_indent(out);
       }
       first = false;
 
-      out_ << "case ";
-      if (!EmitLiteral(selector.get())) {
+      out << "case ";
+      if (!EmitLiteral(out, selector.get())) {
         return false;
       }
-      out_ << ":";
+      out << ":";
     }
   }
 
-  out_ << " {" << std::endl;
+  out << " {" << std::endl;
 
   increment_indent();
 
   for (const auto& s : *(stmt->body())) {
-    if (!EmitStatement(s.get())) {
+    if (!EmitStatement(out, s.get())) {
       return false;
     }
   }
 
   if (!last_is_break_or_fallthrough(stmt->body())) {
-    make_indent();
-    out_ << "break;" << std::endl;
+    make_indent(out);
+    out << "break;" << std::endl;
   }
 
   decrement_indent();
-  make_indent();
-  out_ << "}" << std::endl;
+  make_indent(out);
+  out << "}" << std::endl;
 
   return true;
 }
 
-bool GeneratorImpl::EmitConstructor(ast::ConstructorExpression* expr) {
+bool GeneratorImpl::EmitConstructor(std::ostream& out,
+                                    ast::ConstructorExpression* expr) {
   if (expr->IsScalarConstructor()) {
-    return EmitScalarConstructor(expr->AsScalarConstructor());
+    return EmitScalarConstructor(out, expr->AsScalarConstructor());
   }
-  return EmitTypeConstructor(expr->AsTypeConstructor());
+  return EmitTypeConstructor(out, expr->AsTypeConstructor());
 }
 
 bool GeneratorImpl::EmitScalarConstructor(
+    std::ostream& out,
     ast::ScalarConstructorExpression* expr) {
-  return EmitLiteral(expr->literal());
+  return EmitLiteral(out, expr->literal());
 }
 
-bool GeneratorImpl::EmitTypeConstructor(ast::TypeConstructorExpression* expr) {
+bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
+                                        ast::TypeConstructorExpression* expr) {
   if (expr->type()->IsArray()) {
-    out_ << "{";
+    out << "{";
   } else {
-    if (!EmitType(expr->type(), "")) {
+    if (!EmitType(out, expr->type(), "")) {
       return false;
     }
-    out_ << "(";
+    out << "(";
   }
 
   // If the type constructor is empty then we need to construct with the zero
   // value for all components.
   if (expr->values().empty()) {
-    if (!EmitZeroValue(expr->type())) {
+    if (!EmitZeroValue(out, expr->type())) {
       return false;
     }
   } else {
     bool first = true;
     for (const auto& e : expr->values()) {
       if (!first) {
-        out_ << ", ";
+        out << ", ";
       }
       first = false;
 
-      if (!EmitExpression(e.get())) {
+      if (!EmitExpression(out, e.get())) {
         return false;
       }
     }
   }
 
   if (expr->type()->IsArray()) {
-    out_ << "}";
+    out << "}";
   } else {
-    out_ << ")";
+    out << ")";
   }
   return true;
 }
 
-bool GeneratorImpl::EmitContinue(ast::ContinueStatement*) {
-  make_indent();
-  out_ << "continue;" << std::endl;
+bool GeneratorImpl::EmitContinue(std::ostream& out, ast::ContinueStatement*) {
+  make_indent(out);
+  out << "continue;" << std::endl;
   return true;
 }
 
-bool GeneratorImpl::EmitDiscard(ast::DiscardStatement*) {
-  make_indent();
+bool GeneratorImpl::EmitDiscard(std::ostream& out, ast::DiscardStatement*) {
+  make_indent(out);
   // TODO(dsinclair): Verify this is correct when the discard semantics are
   // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
-  out_ << "discard;" << std::endl;
+  out << "discard;" << std::endl;
   return true;
 }
 
-bool GeneratorImpl::EmitExpression(ast::Expression* expr) {
+bool GeneratorImpl::EmitExpression(std::ostream& out, ast::Expression* expr) {
   if (expr->IsAs()) {
-    return EmitAs(expr->AsAs());
+    return EmitAs(out, expr->AsAs());
   }
   if (expr->IsArrayAccessor()) {
-    return EmitArrayAccessor(expr->AsArrayAccessor());
+    return EmitArrayAccessor(out, expr->AsArrayAccessor());
   }
   if (expr->IsBinary()) {
-    return EmitBinary(expr->AsBinary());
+    return EmitBinary(out, expr->AsBinary());
   }
   if (expr->IsCall()) {
-    return EmitCall(expr->AsCall());
+    return EmitCall(out, expr->AsCall());
   }
   if (expr->IsCast()) {
-    return EmitCast(expr->AsCast());
+    return EmitCast(out, expr->AsCast());
   }
   if (expr->IsConstructor()) {
-    return EmitConstructor(expr->AsConstructor());
+    return EmitConstructor(out, expr->AsConstructor());
   }
   if (expr->IsIdentifier()) {
-    return EmitIdentifier(expr->AsIdentifier());
+    return EmitIdentifier(out, expr->AsIdentifier());
   }
   if (expr->IsMemberAccessor()) {
-    return EmitMemberAccessor(expr->AsMemberAccessor());
+    return EmitMemberAccessor(out, expr->AsMemberAccessor());
   }
   if (expr->IsUnaryOp()) {
-    return EmitUnaryOp(expr->AsUnaryOp());
+    return EmitUnaryOp(out, expr->AsUnaryOp());
   }
 
   error_ = "unknown expression type: " + expr->str();
@@ -872,7 +888,8 @@
           var->storage_class() == ast::StorageClass::kOutput);
 }
 
-bool GeneratorImpl::EmitIdentifier(ast::IdentifierExpression* expr) {
+bool GeneratorImpl::EmitIdentifier(std::ostream& out,
+                                   ast::IdentifierExpression* expr) {
   auto* ident = expr->AsIdentifier();
   if (ident->has_path()) {
     // TODO(dsinclair): Handle identifier with path
@@ -891,49 +908,49 @@
         error_ = "unable to find entry point data for variable";
         return false;
       }
-      out_ << name << ".";
+      out << name << ".";
     }
   }
-  out_ << namer_.NameFor(ident->name());
+  out << namer_.NameFor(ident->name());
 
   return true;
 }
 
-bool GeneratorImpl::EmitIf(ast::IfStatement* stmt) {
-  make_indent();
+bool GeneratorImpl::EmitIf(std::ostream& out, ast::IfStatement* stmt) {
+  make_indent(out);
 
-  out_ << "if (";
-  if (!EmitExpression(stmt->condition())) {
+  out << "if (";
+  if (!EmitExpression(out, stmt->condition())) {
     return false;
   }
-  out_ << ") ";
+  out << ") ";
 
-  if (!EmitBlock(stmt->body())) {
+  if (!EmitBlock(out, stmt->body())) {
     return false;
   }
 
   for (const auto& e : stmt->else_statements()) {
-    if (!EmitElse(e.get())) {
+    if (!EmitElse(out, e.get())) {
       return false;
     }
   }
-  out_ << std::endl;
+  out << std::endl;
 
   return true;
 }
 
-bool GeneratorImpl::EmitElse(ast::ElseStatement* stmt) {
+bool GeneratorImpl::EmitElse(std::ostream& out, ast::ElseStatement* stmt) {
   if (stmt->HasCondition()) {
-    out_ << " else if (";
-    if (!EmitExpression(stmt->condition())) {
+    out << " else if (";
+    if (!EmitExpression(out, stmt->condition())) {
       return false;
     }
-    out_ << ") ";
+    out << ") ";
   } else {
-    out_ << " else ";
+    out << " else ";
   }
 
-  return EmitBlock(stmt->body());
+  return EmitBlock(out, stmt->body());
 }
 
 bool GeneratorImpl::has_referenced_in_var_needing_struct(ast::Function* func) {
@@ -989,8 +1006,8 @@
   return false;
 }
 
-bool GeneratorImpl::EmitFunction(ast::Function* func) {
-  make_indent();
+bool GeneratorImpl::EmitFunction(std::ostream& out, ast::Function* func) {
+  make_indent(out);
 
   // Entry points will be emitted later, skip for now.
   if (module_->IsFunctionEntryPoint(func->name())) {
@@ -1005,32 +1022,33 @@
 
   if (emit_duplicate_functions) {
     for (const auto& ep_name : func->ancestor_entry_points()) {
-      if (!EmitFunctionInternal(func, emit_duplicate_functions, ep_name)) {
+      if (!EmitFunctionInternal(out, func, emit_duplicate_functions, ep_name)) {
         return false;
       }
-      out_ << std::endl;
+      out << std::endl;
     }
   } else {
     // Emit as non-duplicated
-    if (!EmitFunctionInternal(func, false, "")) {
+    if (!EmitFunctionInternal(out, func, false, "")) {
       return false;
     }
-    out_ << std::endl;
+    out << std::endl;
   }
 
   return true;
 }
 
-bool GeneratorImpl::EmitFunctionInternal(ast::Function* func,
+bool GeneratorImpl::EmitFunctionInternal(std::ostream& out,
+                                         ast::Function* func,
                                          bool emit_duplicate_functions,
                                          const std::string& ep_name) {
   auto name = func->name();
 
-  if (!EmitType(func->return_type(), "")) {
+  if (!EmitType(out, func->return_type(), "")) {
     return false;
   }
 
-  out_ << " ";
+  out << " ";
 
   if (emit_duplicate_functions) {
     name = generate_name(name + "_" + ep_name);
@@ -1039,7 +1057,7 @@
     name = namer_.NameFor(name);
   }
 
-  out_ << name << "(";
+  out << name << "(";
 
   bool first = true;
 
@@ -1050,42 +1068,42 @@
   if (emit_duplicate_functions) {
     auto in_it = ep_name_to_in_data_.find(ep_name);
     if (in_it != ep_name_to_in_data_.end()) {
-      out_ << "in " << in_it->second.struct_name << " "
-           << in_it->second.var_name;
+      out << "in " << in_it->second.struct_name << " "
+          << in_it->second.var_name;
       first = false;
     }
 
-    auto out_it = ep_name_to_out_data_.find(ep_name);
-    if (out_it != ep_name_to_out_data_.end()) {
+    auto outit = ep_name_to_out_data_.find(ep_name);
+    if (outit != ep_name_to_out_data_.end()) {
       if (!first) {
-        out_ << ", ";
+        out << ", ";
       }
-      out_ << "out " << out_it->second.struct_name << " "
-           << out_it->second.var_name;
+      out << "out " << outit->second.struct_name << " "
+          << outit->second.var_name;
       first = false;
     }
   }
 
   for (const auto& v : func->params()) {
     if (!first) {
-      out_ << ", ";
+      out << ", ";
     }
     first = false;
 
-    if (!EmitType(v->type(), v->name())) {
+    if (!EmitType(out, v->type(), v->name())) {
       return false;
     }
     // Array name is output as part of the type
     if (!v->type()->IsArray()) {
-      out_ << " " << v->name();
+      out << " " << v->name();
     }
   }
 
-  out_ << ") ";
+  out << ") ";
 
   current_ep_name_ = ep_name;
 
-  if (!EmitBlockAndNewline(func->body())) {
+  if (!EmitBlockAndNewline(out, func->body())) {
     return false;
   }
 
@@ -1094,7 +1112,7 @@
   return true;
 }
 
-bool GeneratorImpl::EmitEntryPointData(ast::EntryPoint* ep) {
+bool GeneratorImpl::EmitEntryPointData(std::ostream& out, ast::EntryPoint* ep) {
   auto* func = module_->FindFunctionByName(ep->function_name());
   if (func == nullptr) {
     error_ = "Unable to find entry point function: " + ep->function_name();
@@ -1102,8 +1120,7 @@
   }
 
   std::vector<std::pair<ast::Variable*, ast::VariableDecoration*>> in_variables;
-  std::vector<std::pair<ast::Variable*, ast::VariableDecoration*>>
-      out_variables;
+  std::vector<std::pair<ast::Variable*, ast::VariableDecoration*>> outvariables;
   for (auto data : func->referenced_location_variables()) {
     auto* var = data.first;
     auto* deco = data.second;
@@ -1111,7 +1128,7 @@
     if (var->storage_class() == ast::StorageClass::kInput) {
       in_variables.push_back({var, deco});
     } else if (var->storage_class() == ast::StorageClass::kOutput) {
-      out_variables.push_back({var, deco});
+      outvariables.push_back({var, deco});
     }
   }
 
@@ -1122,7 +1139,7 @@
     if (var->storage_class() == ast::StorageClass::kInput) {
       in_variables.push_back({var, deco});
     } else if (var->storage_class() == ast::StorageClass::kOutput) {
-      out_variables.push_back({var, deco});
+      outvariables.push_back({var, deco});
     }
   }
 
@@ -1143,8 +1160,8 @@
     if (type->IsStruct()) {
       auto* strct = type->AsStruct();
 
-      out_ << "ConstantBuffer<" << strct->name() << "> " << var->name()
-           << " : register(b" << binding->value() << ");" << std::endl;
+      out << "ConstantBuffer<" << strct->name() << "> " << var->name()
+          << " : register(b" << binding->value() << ");" << std::endl;
     } else {
       // TODO(dsinclair): There is outstanding spec work to require all uniform
       // buffers to be [[block]] decorated, which means structs. This is
@@ -1152,22 +1169,22 @@
       // is not a block.
       // Relevant: https://github.com/gpuweb/gpuweb/issues/1004
       //           https://github.com/gpuweb/gpuweb/issues/1008
-      out_ << "cbuffer : register(b" << binding->value() << ") {" << std::endl;
+      out << "cbuffer : register(b" << binding->value() << ") {" << std::endl;
 
       increment_indent();
-      make_indent();
-      if (!EmitType(type, "")) {
+      make_indent(out);
+      if (!EmitType(out, type, "")) {
         return false;
       }
-      out_ << " " << var->name() << ";" << std::endl;
+      out << " " << var->name() << ";" << std::endl;
       decrement_indent();
-      out_ << "};" << std::endl;
+      out << "};" << std::endl;
     }
 
     emitted_uniform = true;
   }
   if (emitted_uniform) {
-    out_ << std::endl;
+    out << std::endl;
   }
 
   bool emitted_storagebuffer = false;
@@ -1175,12 +1192,12 @@
     auto* var = data.first;
     auto* binding = data.second.binding;
 
-    out_ << "RWByteAddressBuffer " << var->name() << " : register(u"
-         << binding->value() << ");" << std::endl;
+    out << "RWByteAddressBuffer " << var->name() << " : register(u"
+        << binding->value() << ");" << std::endl;
     emitted_storagebuffer = true;
   }
   if (emitted_storagebuffer) {
-    out_ << std::endl;
+    out << std::endl;
   }
 
   auto ep_name = ep->name();
@@ -1198,8 +1215,8 @@
     auto in_var_name = generate_name(kTintStructInVarPrefix);
     ep_name_to_in_data_[ep_name] = {in_struct_name, in_var_name};
 
-    make_indent();
-    out_ << "struct " << in_struct_name << " {" << std::endl;
+    make_indent(out);
+    out << "struct " << in_struct_name << " {" << std::endl;
 
     increment_indent();
 
@@ -1207,63 +1224,63 @@
       auto* var = data.first;
       auto* deco = data.second;
 
-      make_indent();
-      if (!EmitType(var->type(), var->name())) {
+      make_indent(out);
+      if (!EmitType(out, var->type(), var->name())) {
         return false;
       }
 
-      out_ << " " << var->name() << " : ";
+      out << " " << var->name() << " : ";
       if (deco->IsLocation()) {
         if (ep->stage() == ast::PipelineStage::kCompute) {
           error_ = "invalid location variable for pipeline stage";
           return false;
         }
-        out_ << "TEXCOORD" << deco->AsLocation()->value();
+        out << "TEXCOORD" << deco->AsLocation()->value();
       } else if (deco->IsBuiltin()) {
         auto attr = builtin_to_attribute(deco->AsBuiltin()->value());
         if (attr.empty()) {
           error_ = "unsupported builtin";
           return false;
         }
-        out_ << attr;
+        out << attr;
       } else {
         error_ = "unsupported variable decoration for entry point output";
         return false;
       }
-      out_ << ";" << std::endl;
+      out << ";" << std::endl;
     }
     decrement_indent();
-    make_indent();
+    make_indent(out);
 
-    out_ << "};" << std::endl << std::endl;
+    out << "};" << std::endl << std::endl;
   }
 
-  if (!out_variables.empty()) {
-    auto out_struct_name = generate_name(ep_name + "_" + kOutStructNameSuffix);
-    auto out_var_name = generate_name(kTintStructOutVarPrefix);
-    ep_name_to_out_data_[ep_name] = {out_struct_name, out_var_name};
+  if (!outvariables.empty()) {
+    auto outstruct_name = generate_name(ep_name + "_" + kOutStructNameSuffix);
+    auto outvar_name = generate_name(kTintStructOutVarPrefix);
+    ep_name_to_out_data_[ep_name] = {outstruct_name, outvar_name};
 
-    make_indent();
-    out_ << "struct " << out_struct_name << " {" << std::endl;
+    make_indent(out);
+    out << "struct " << outstruct_name << " {" << std::endl;
 
     increment_indent();
-    for (auto& data : out_variables) {
+    for (auto& data : outvariables) {
       auto* var = data.first;
       auto* deco = data.second;
 
-      make_indent();
-      if (!EmitType(var->type(), var->name())) {
+      make_indent(out);
+      if (!EmitType(out, var->type(), var->name())) {
         return false;
       }
 
-      out_ << " " << var->name() << " : ";
+      out << " " << var->name() << " : ";
 
       if (deco->IsLocation()) {
         auto loc = deco->AsLocation()->value();
         if (ep->stage() == ast::PipelineStage::kVertex) {
-          out_ << "TEXCOORD" << loc;
+          out << "TEXCOORD" << loc;
         } else if (ep->stage() == ast::PipelineStage::kFragment) {
-          out_ << "SV_Target" << loc << "";
+          out << "SV_Target" << loc << "";
         } else {
           error_ = "invalid location variable for pipeline stage";
           return false;
@@ -1274,16 +1291,16 @@
           error_ = "unsupported builtin";
           return false;
         }
-        out_ << attr;
+        out << attr;
       } else {
         error_ = "unsupported variable decoration for entry point output";
         return false;
       }
-      out_ << ";" << std::endl;
+      out << ";" << std::endl;
     }
     decrement_indent();
-    make_indent();
-    out_ << "};" << std::endl << std::endl;
+    make_indent(out);
+    out << "};" << std::endl << std::endl;
   }
 
   return true;
@@ -1320,8 +1337,9 @@
   return "";
 }
 
-bool GeneratorImpl::EmitEntryPointFunction(ast::EntryPoint* ep) {
-  make_indent();
+bool GeneratorImpl::EmitEntryPointFunction(std::ostream& out,
+                                           ast::EntryPoint* ep) {
+  make_indent(out);
 
   current_ep_name_ = ep->name();
   if (current_ep_name_.empty()) {
@@ -1334,64 +1352,64 @@
     return false;
   }
 
-  auto out_data = ep_name_to_out_data_.find(current_ep_name_);
-  bool has_out_data = out_data != ep_name_to_out_data_.end();
-  if (has_out_data) {
-    out_ << out_data->second.struct_name;
+  auto outdata = ep_name_to_out_data_.find(current_ep_name_);
+  bool has_outdata = outdata != ep_name_to_out_data_.end();
+  if (has_outdata) {
+    out << outdata->second.struct_name;
   } else {
-    out_ << "void";
+    out << "void";
   }
-  out_ << " " << namer_.NameFor(current_ep_name_) << "(";
+  out << " " << namer_.NameFor(current_ep_name_) << "(";
 
   auto in_data = ep_name_to_in_data_.find(current_ep_name_);
   if (in_data != ep_name_to_in_data_.end()) {
-    out_ << in_data->second.struct_name << " " << in_data->second.var_name;
+    out << in_data->second.struct_name << " " << in_data->second.var_name;
   }
-  out_ << ") {" << std::endl;
+  out << ") {" << std::endl;
 
   increment_indent();
 
-  if (has_out_data) {
-    make_indent();
-    out_ << out_data->second.struct_name << " " << out_data->second.var_name
-         << ";" << std::endl;
+  if (has_outdata) {
+    make_indent(out);
+    out << outdata->second.struct_name << " " << outdata->second.var_name << ";"
+        << std::endl;
   }
 
   generating_entry_point_ = true;
   for (const auto& s : *(func->body())) {
-    if (!EmitStatement(s.get())) {
+    if (!EmitStatement(out, s.get())) {
       return false;
     }
   }
   generating_entry_point_ = false;
 
   decrement_indent();
-  make_indent();
-  out_ << "}" << std::endl;
+  make_indent(out);
+  out << "}" << std::endl;
 
   current_ep_name_ = "";
 
   return true;
 }
 
-bool GeneratorImpl::EmitLiteral(ast::Literal* lit) {
+bool GeneratorImpl::EmitLiteral(std::ostream& out, ast::Literal* lit) {
   if (lit->IsBool()) {
-    out_ << (lit->AsBool()->IsTrue() ? "true" : "false");
+    out << (lit->AsBool()->IsTrue() ? "true" : "false");
   } else if (lit->IsFloat()) {
-    auto flags = out_.flags();
-    auto precision = out_.precision();
+    auto flags = out.flags();
+    auto precision = out.precision();
 
-    out_.flags(flags | std::ios_base::showpoint);
-    out_.precision(std::numeric_limits<float>::max_digits10);
+    out.flags(flags | std::ios_base::showpoint);
+    out.precision(std::numeric_limits<float>::max_digits10);
 
-    out_ << lit->AsFloat()->value() << "f";
+    out << lit->AsFloat()->value() << "f";
 
-    out_.precision(precision);
-    out_.flags(flags);
+    out.precision(precision);
+    out.flags(flags);
   } else if (lit->IsSint()) {
-    out_ << lit->AsSint()->value();
+    out << lit->AsSint()->value();
   } else if (lit->IsUint()) {
-    out_ << lit->AsUint()->value() << "u";
+    out << lit->AsUint()->value() << "u";
   } else {
     error_ = "unknown literal type";
     return false;
@@ -1399,17 +1417,17 @@
   return true;
 }
 
-bool GeneratorImpl::EmitZeroValue(ast::type::Type* type) {
+bool GeneratorImpl::EmitZeroValue(std::ostream& out, ast::type::Type* type) {
   if (type->IsBool()) {
-    out_ << "false";
+    out << "false";
   } else if (type->IsF32()) {
-    out_ << "0.0f";
+    out << "0.0f";
   } else if (type->IsI32()) {
-    out_ << "0";
+    out << "0";
   } else if (type->IsU32()) {
-    out_ << "0u";
+    out << "0u";
   } else if (type->IsVector()) {
-    return EmitZeroValue(type->AsVector()->type());
+    return EmitZeroValue(out, type->AsVector()->type());
   } else {
     error_ = "Invalid type for zero emission: " + type->type_name();
     return false;
@@ -1417,55 +1435,55 @@
   return true;
 }
 
-bool GeneratorImpl::EmitLoop(ast::LoopStatement* stmt) {
+bool GeneratorImpl::EmitLoop(std::ostream& out, ast::LoopStatement* stmt) {
   loop_emission_counter_++;
 
   std::string guard = namer_.NameFor("tint_hlsl_is_first_" +
                                      std::to_string(loop_emission_counter_));
 
   if (stmt->has_continuing()) {
-    make_indent();
+    make_indent(out);
 
     // Continuing variables get their own scope.
-    out_ << "{" << std::endl;
+    out << "{" << std::endl;
     increment_indent();
 
-    make_indent();
-    out_ << "bool " << guard << " = true;" << std::endl;
+    make_indent(out);
+    out << "bool " << guard << " = true;" << std::endl;
   }
 
-  make_indent();
-  out_ << "for(;;) {" << std::endl;
+  make_indent(out);
+  out << "for(;;) {" << std::endl;
   increment_indent();
 
   if (stmt->has_continuing()) {
-    make_indent();
-    out_ << "if (!" << guard << ") ";
+    make_indent(out);
+    out << "if (!" << guard << ") ";
 
-    if (!EmitBlockAndNewline(stmt->continuing())) {
+    if (!EmitBlockAndNewline(out, stmt->continuing())) {
       return false;
     }
 
-    make_indent();
-    out_ << guard << " = false;" << std::endl;
-    out_ << std::endl;
+    make_indent(out);
+    out << guard << " = false;" << std::endl;
+    out << std::endl;
   }
 
   for (const auto& s : *(stmt->body())) {
-    if (!EmitStatement(s.get())) {
+    if (!EmitStatement(out, s.get())) {
       return false;
     }
   }
 
   decrement_indent();
-  make_indent();
-  out_ << "}" << std::endl;
+  make_indent(out);
+  out << "}" << std::endl;
 
   // Close the scope for any continuing variables.
   if (stmt->has_continuing()) {
     decrement_indent();
-    make_indent();
-    out_ << "}" << std::endl;
+    make_indent(out);
+    out << "}" << std::endl;
   }
 
   return true;
@@ -1478,7 +1496,8 @@
 // TODO(dsinclair): Need to support loading through a pointer. The pointer is
 // just a memory address in the storage buffer, so need to do the correct
 // calculation.
-bool GeneratorImpl::EmitStorageBufferAccessor(ast::Expression* expr,
+bool GeneratorImpl::EmitStorageBufferAccessor(std::ostream& out,
+                                              ast::Expression* expr,
                                               ast::Expression* rhs) {
   auto* result_type = expr->result_type()->UnwrapAliasPtrAlias();
   std::string access_method = rhs != nullptr ? "Store" : "Load";
@@ -1489,11 +1508,11 @@
   // If we aren't storing then we need to put in the outer cast.
   if (rhs == nullptr) {
     if (result_type->is_float_scalar_or_vector()) {
-      out_ << "asfloat(";
+      out << "asfloat(";
     } else if (result_type->is_signed_scalar_or_vector()) {
-      out_ << "asint(";
+      out << "asint(";
     } else if (result_type->is_unsigned_scalar_or_vector()) {
-      out_ << "asuint(";
+      out << "asuint(";
     }
   }
 
@@ -1502,7 +1521,7 @@
     error_ = "error emitting storage buffer access";
     return false;
   }
-  out_ << buffer_name << "." << access_method << "(";
+  out << buffer_name << "." << access_method << "(";
 
   auto* ptr = expr;
   bool first = true;
@@ -1512,7 +1531,7 @@
     }
 
     if (!first) {
-      out_ << " + ";
+      out << " + ";
     }
     first = false;
     if (ptr->IsMemberAccessor()) {
@@ -1527,7 +1546,7 @@
           error_ = "missing offset decoration for struct member";
           return false;
         }
-        out_ << str_member->offset();
+        out << str_member->offset();
       } else if (res_type->IsVector()) {
         // This must be a single element swizzle if we've got a vector at this
         // point.
@@ -1541,8 +1560,8 @@
         // TODO(dsinclair): All our types are currently 4 bytes (f32, i32, u32)
         // so this is assuming 4. This will need to be fixed when we get f16 or
         // f64 types.
-        out_ << "(4 * " << convert_swizzle_to_index(mem->member()->name())
-             << ")";
+        out << "(4 * " << convert_swizzle_to_index(mem->member()->name())
+            << ")";
       } else {
         error_ =
             "Invalid result type for member accessor: " + res_type->type_name();
@@ -1554,24 +1573,24 @@
       auto* ary = ptr->AsArrayAccessor();
       auto* ary_type = ary->array()->result_type()->UnwrapAliasPtrAlias();
 
-      out_ << "(";
+      out << "(";
       // TODO(dsinclair): Handle matrix case and struct case.
       if (ary_type->IsArray()) {
-        out_ << ary_type->AsArray()->array_stride();
+        out << ary_type->AsArray()->array_stride();
       } else if (ary_type->IsVector()) {
         // TODO(dsinclair): This is a hack. Our vectors can only be f32, i32
         // or u32 which are all 4 bytes. When we get f16 or other types we'll
         // have to ask the type for the byte size.
-        out_ << "4";
+        out << "4";
       } else {
         error_ = "Invalid array type in storage buffer access";
         return false;
       }
-      out_ << " * ";
-      if (!EmitExpression(ary->idx_expr())) {
+      out << " * ";
+      if (!EmitExpression(out, ary->idx_expr())) {
         return false;
       }
-      out_ << ")";
+      out << ")";
 
       ptr = ary->array();
     } else {
@@ -1581,18 +1600,18 @@
   }
 
   if (rhs != nullptr) {
-    out_ << ", asuint(";
-    if (!EmitExpression(rhs)) {
+    out << ", asuint(";
+    if (!EmitExpression(out, rhs)) {
       return false;
     }
-    out_ << ")";
+    out << ")";
   }
 
-  out_ << ")";
+  out << ")";
 
   // Close the outer cast.
   if (rhs == nullptr) {
-    out_ << ")";
+    out << ")";
   }
 
   return true;
@@ -1645,118 +1664,121 @@
   return false;
 }
 
-bool GeneratorImpl::EmitMemberAccessor(ast::MemberAccessorExpression* expr) {
+bool GeneratorImpl::EmitMemberAccessor(std::ostream& out,
+                                       ast::MemberAccessorExpression* expr) {
   // Look for storage buffer accesses as we have to convert them into Load
   // expressions. Stores will be identified in the assignment emission and a
   // member accessor store of a storage buffer will not get here.
   if (is_storage_buffer_access(expr)) {
-    return EmitStorageBufferAccessor(expr, nullptr);
+    return EmitStorageBufferAccessor(out, expr, nullptr);
   }
 
-  if (!EmitExpression(expr->structure())) {
+  if (!EmitExpression(out, expr->structure())) {
     return false;
   }
-  out_ << ".";
-  return EmitExpression(expr->member());
+  out << ".";
+  return EmitExpression(out, expr->member());
 }
 
-bool GeneratorImpl::EmitReturn(ast::ReturnStatement* stmt) {
-  make_indent();
+bool GeneratorImpl::EmitReturn(std::ostream& out, ast::ReturnStatement* stmt) {
+  make_indent(out);
 
-  out_ << "return";
+  out << "return";
 
   if (generating_entry_point_) {
-    auto out_data = ep_name_to_out_data_.find(current_ep_name_);
-    if (out_data != ep_name_to_out_data_.end()) {
-      out_ << " " << out_data->second.var_name;
+    auto outdata = ep_name_to_out_data_.find(current_ep_name_);
+    if (outdata != ep_name_to_out_data_.end()) {
+      out << " " << outdata->second.var_name;
     }
   } else if (stmt->has_value()) {
-    out_ << " ";
-    if (!EmitExpression(stmt->value())) {
+    out << " ";
+    if (!EmitExpression(out, stmt->value())) {
       return false;
     }
   }
-  out_ << ";" << std::endl;
+  out << ";" << std::endl;
   return true;
 }
 
-bool GeneratorImpl::EmitStatement(ast::Statement* stmt) {
+bool GeneratorImpl::EmitStatement(std::ostream& out, ast::Statement* stmt) {
   if (stmt->IsAssign()) {
-    return EmitAssign(stmt->AsAssign());
+    return EmitAssign(out, stmt->AsAssign());
   }
   if (stmt->IsBlock()) {
-    return EmitIndentedBlockAndNewline(stmt->AsBlock());
+    return EmitIndentedBlockAndNewline(out, stmt->AsBlock());
   }
   if (stmt->IsBreak()) {
-    return EmitBreak(stmt->AsBreak());
+    return EmitBreak(out, stmt->AsBreak());
   }
   if (stmt->IsCall()) {
-    make_indent();
-    if (!EmitCall(stmt->AsCall()->expr())) {
+    make_indent(out);
+    if (!EmitCall(out, stmt->AsCall()->expr())) {
       return false;
     }
-    out_ << ";" << std::endl;
+    out << ";" << std::endl;
     return true;
   }
   if (stmt->IsContinue()) {
-    return EmitContinue(stmt->AsContinue());
+    return EmitContinue(out, stmt->AsContinue());
   }
   if (stmt->IsDiscard()) {
-    return EmitDiscard(stmt->AsDiscard());
+    return EmitDiscard(out, stmt->AsDiscard());
   }
   if (stmt->IsFallthrough()) {
-    make_indent();
-    out_ << "/* fallthrough */" << std::endl;
+    make_indent(out);
+    out << "/* fallthrough */" << std::endl;
     return true;
   }
   if (stmt->IsIf()) {
-    return EmitIf(stmt->AsIf());
+    return EmitIf(out, stmt->AsIf());
   }
   if (stmt->IsLoop()) {
-    return EmitLoop(stmt->AsLoop());
+    return EmitLoop(out, stmt->AsLoop());
   }
   if (stmt->IsReturn()) {
-    return EmitReturn(stmt->AsReturn());
+    return EmitReturn(out, stmt->AsReturn());
   }
   if (stmt->IsSwitch()) {
-    return EmitSwitch(stmt->AsSwitch());
+    return EmitSwitch(out, stmt->AsSwitch());
   }
   if (stmt->IsVariableDecl()) {
-    return EmitVariable(stmt->AsVariableDecl()->variable());
+    return EmitVariable(out, stmt->AsVariableDecl()->variable());
   }
 
   error_ = "unknown statement type: " + stmt->str();
   return false;
 }
 
-bool GeneratorImpl::EmitSwitch(ast::SwitchStatement* stmt) {
-  make_indent();
+bool GeneratorImpl::EmitSwitch(std::ostream& out, ast::SwitchStatement* stmt) {
+  make_indent(out);
 
-  out_ << "switch(";
-  if (!EmitExpression(stmt->condition())) {
+  out << "switch(";
+  if (!EmitExpression(out, stmt->condition())) {
     return false;
   }
-  out_ << ") {" << std::endl;
+  out << ") {" << std::endl;
 
   increment_indent();
 
   for (const auto& s : stmt->body()) {
-    if (!EmitCase(s.get())) {
+    if (!EmitCase(out, s.get())) {
       return false;
     }
   }
 
   decrement_indent();
-  make_indent();
-  out_ << "}" << std::endl;
+  make_indent(out);
+  out << "}" << std::endl;
 
   return true;
 }
 
-bool GeneratorImpl::EmitType(ast::type::Type* type, const std::string& name) {
+bool GeneratorImpl::EmitType(std::ostream& out,
+                             ast::type::Type* type,
+                             const std::string& name) {
   if (type->IsAlias()) {
     auto* alias = type->AsAlias();
-    out_ << namer_.NameFor(alias->name());
+    out << namer_.NameFor(alias->name());
   } else if (type->IsArray()) {
     auto* ary = type->AsArray();
 
@@ -1773,28 +1795,28 @@
       }
       base_type = base_type->AsArray()->type();
     }
-    if (!EmitType(base_type, "")) {
+    if (!EmitType(out, base_type, "")) {
       return false;
     }
     if (!name.empty()) {
-      out_ << " " << namer_.NameFor(name);
+      out << " " << namer_.NameFor(name);
     }
     for (uint32_t size : sizes) {
-      out_ << "[" << size << "]";
+      out << "[" << size << "]";
     }
   } else if (type->IsBool()) {
-    out_ << "bool";
+    out << "bool";
   } else if (type->IsF32()) {
-    out_ << "float";
+    out << "float";
   } else if (type->IsI32()) {
-    out_ << "int";
+    out << "int";
   } else if (type->IsMatrix()) {
     auto* mat = type->AsMatrix();
-    out_ << "matrix<";
-    if (!EmitType(mat->type(), "")) {
+    out << "matrix<";
+    if (!EmitType(out, mat->type(), "")) {
       return false;
     }
-    out_ << ", " << mat->rows() << ", " << mat->columns() << ">";
+    out << ", " << mat->rows() << ", " << mat->columns() << ">";
   } else if (type->IsPointer()) {
     // TODO(dsinclair): What do we do with pointers in HLSL?
     // https://bugs.chromium.org/p/tint/issues/detail?id=183
@@ -1805,43 +1827,43 @@
     // TODO(dsinclair): Block decoration?
     // if (str->decoration() != ast::StructDecoration::kNone) {
     // }
-    out_ << "struct";
+    out << "struct";
     // If a name was provided for the struct emit it.
     if (!name.empty()) {
-      out_ << " " << name;
+      out << " " << name;
     }
-    out_ << " {" << std::endl;
+    out << " {" << std::endl;
 
     increment_indent();
     for (const auto& mem : str->members()) {
-      make_indent();
+      make_indent(out);
       // TODO(dsinclair): Handle [[offset]] annotation on structs
       // https://bugs.chromium.org/p/tint/issues/detail?id=184
 
-      if (!EmitType(mem->type(), mem->name())) {
+      if (!EmitType(out, mem->type(), mem->name())) {
         return false;
       }
       // Array member name will be output with the type
       if (!mem->type()->IsArray()) {
-        out_ << " " << namer_.NameFor(mem->name());
+        out << " " << namer_.NameFor(mem->name());
       }
-      out_ << ";" << std::endl;
+      out << ";" << std::endl;
     }
     decrement_indent();
-    make_indent();
+    make_indent(out);
 
-    out_ << "}";
+    out << "}";
   } else if (type->IsU32()) {
-    out_ << "uint";
+    out << "uint";
   } else if (type->IsVector()) {
     auto* vec = type->AsVector();
-    out_ << "vector<";
-    if (!EmitType(vec->type(), "")) {
+    out << "vector<";
+    if (!EmitType(out, vec->type(), "")) {
       return false;
     }
-    out_ << ", " << vec->size() << ">";
+    out << ", " << vec->size() << ">";
   } else if (type->IsVoid()) {
-    out_ << "void";
+    out << "void";
   } else {
     error_ = "unknown type in EmitType";
     return false;
@@ -1850,28 +1872,29 @@
   return true;
 }
 
-bool GeneratorImpl::EmitUnaryOp(ast::UnaryOpExpression* expr) {
+bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
+                                ast::UnaryOpExpression* expr) {
   switch (expr->op()) {
     case ast::UnaryOp::kNot:
-      out_ << "!";
+      out << "!";
       break;
     case ast::UnaryOp::kNegation:
-      out_ << "-";
+      out << "-";
       break;
   }
-  out_ << "(";
+  out << "(";
 
-  if (!EmitExpression(expr->expr())) {
+  if (!EmitExpression(out, expr->expr())) {
     return false;
   }
 
-  out_ << ")";
+  out << ")";
 
   return true;
 }
 
-bool GeneratorImpl::EmitVariable(ast::Variable* var) {
-  make_indent();
+bool GeneratorImpl::EmitVariable(std::ostream& out, ast::Variable* var) {
+  make_indent(out);
 
   // TODO(dsinclair): Handle variable decorations
   if (var->IsDecorated()) {
@@ -1880,28 +1903,29 @@
   }
 
   if (var->is_const()) {
-    out_ << "const ";
+    out << "const ";
   }
-  if (!EmitType(var->type(), var->name())) {
+  if (!EmitType(out, var->type(), var->name())) {
     return false;
   }
   if (!var->type()->IsArray()) {
-    out_ << " " << var->name();
+    out << " " << var->name();
   }
 
   if (var->constructor() != nullptr) {
-    out_ << " = ";
-    if (!EmitExpression(var->constructor())) {
+    out << " = ";
+    if (!EmitExpression(out, var->constructor())) {
       return false;
     }
   }
-  out_ << ";" << std::endl;
+  out << ";" << std::endl;
 
   return true;
 }
 
-bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
-  make_indent();
+bool GeneratorImpl::EmitProgramConstVariable(std::ostream& out,
+                                             const ast::Variable* var) {
+  make_indent(out);
 
   if (var->IsDecorated()) {
     error_ = "Decorated const values not valid";
@@ -1912,21 +1936,21 @@
     return false;
   }
 
-  out_ << "static const ";
-  if (!EmitType(var->type(), var->name())) {
+  out << "static const ";
+  if (!EmitType(out, var->type(), var->name())) {
     return false;
   }
   if (!var->type()->IsArray()) {
-    out_ << " " << var->name();
+    out << " " << var->name();
   }
 
   if (var->constructor() != nullptr) {
-    out_ << " = ";
-    if (!EmitExpression(var->constructor())) {
+    out << " = ";
+    if (!EmitExpression(out, var->constructor())) {
       return false;
     }
   }
-  out_ << ";" << std::endl;
+  out << ";" << std::endl;
 
   return true;
 }
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index c9eae28..7908e42 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -21,182 +21,247 @@
 #include "src/ast/type_constructor_expression.h"
 #include "src/scope_stack.h"
 #include "src/writer/hlsl/namer.h"
-#include "src/writer/text_generator.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 
 /// Implementation class for HLSL generator
-class GeneratorImpl : public TextGenerator {
+class GeneratorImpl {
  public:
   /// Constructor
   /// @param module the module to generate
   explicit GeneratorImpl(ast::Module* module);
   ~GeneratorImpl();
 
+  /// Increment the emitter indent level
+  void increment_indent() { indent_ += 2; }
+  /// Decrement the emiter indent level
+  void decrement_indent() {
+    if (indent_ < 2) {
+      indent_ = 0;
+      return;
+    }
+    indent_ -= 2;
+  }
+
+  /// Writes the current indent to the output stream
+  /// @param out the output stream
+  void make_indent(std::ostream& out);
+
+  /// @returns the error
+  std::string error() const { return error_; }
+
+  /// @param out the output stream
   /// @returns true on successful generation; false otherwise
-  bool Generate();
+  bool Generate(std::ostream& out);
 
   /// Handles generating an alias
+  /// @param out the output stream
   /// @param alias the alias to generate
   /// @returns true if the alias was emitted
-  bool EmitAliasType(const ast::type::AliasType* alias);
+  bool EmitAliasType(std::ostream& out, const ast::type::AliasType* alias);
   /// Handles an array accessor expression
+  /// @param out the output stream
   /// @param expr the expression to emit
   /// @returns true if the array accessor was emitted
-  bool EmitArrayAccessor(ast::ArrayAccessorExpression* expr);
+  bool EmitArrayAccessor(std::ostream& out, ast::ArrayAccessorExpression* expr);
   /// Handles generating an as expression
+  /// @param out the output stream
   /// @param expr the as expression
   /// @returns true if the as was emitted
-  bool EmitAs(ast::AsExpression* expr);
+  bool EmitAs(std::ostream& out, ast::AsExpression* expr);
   /// Handles an assignment statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted successfully
-  bool EmitAssign(ast::AssignmentStatement* stmt);
+  bool EmitAssign(std::ostream& out, ast::AssignmentStatement* stmt);
   /// Handles generating a binary expression
+  /// @param out the output stream
   /// @param expr the binary expression
   /// @returns true if the expression was emitted, false otherwise
-  bool EmitBinary(ast::BinaryExpression* expr);
+  bool EmitBinary(std::ostream& out, ast::BinaryExpression* expr);
   /// Handles a block statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted successfully
-  bool EmitBlock(const ast::BlockStatement* stmt);
+  bool EmitBlock(std::ostream& out, const ast::BlockStatement* stmt);
   /// Handles a block statement with a newline at the end
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted successfully
-  bool EmitIndentedBlockAndNewline(ast::BlockStatement* stmt);
+  bool EmitIndentedBlockAndNewline(std::ostream& out,
+                                   ast::BlockStatement* stmt);
   /// Handles a block statement with a newline at the end
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted successfully
-  bool EmitBlockAndNewline(const ast::BlockStatement* stmt);
+  bool EmitBlockAndNewline(std::ostream& out, const ast::BlockStatement* stmt);
   /// Handles a break statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted successfully
-  bool EmitBreak(ast::BreakStatement* stmt);
+  bool EmitBreak(std::ostream& out, ast::BreakStatement* stmt);
   /// Handles generating a call expression
+  /// @param out the output stream
   /// @param expr the call expression
   /// @returns true if the call expression is emitted
-  bool EmitCall(ast::CallExpression* expr);
+  bool EmitCall(std::ostream& out, ast::CallExpression* expr);
   /// Handles a case statement
+  /// @param out the output stream
   /// @param stmt the statement
   /// @returns true if the statment was emitted successfully
-  bool EmitCase(ast::CaseStatement* stmt);
+  bool EmitCase(std::ostream& out, ast::CaseStatement* stmt);
   /// Handles generating a cast expression
+  /// @param out the output stream
   /// @param expr the cast expression
   /// @returns true if the cast was emitted
-  bool EmitCast(ast::CastExpression* expr);
+  bool EmitCast(std::ostream& out, ast::CastExpression* expr);
   /// Handles generating constructor expressions
+  /// @param out the output stream
   /// @param expr the constructor expression
   /// @returns true if the expression was emitted
-  bool EmitConstructor(ast::ConstructorExpression* expr);
+  bool EmitConstructor(std::ostream& out, ast::ConstructorExpression* expr);
   /// Handles generating a discard statement
+  /// @param out the output stream
   /// @param stmt the discard statement
   /// @returns true if the statement was successfully emitted
-  bool EmitDiscard(ast::DiscardStatement* stmt);
+  bool EmitDiscard(std::ostream& out, ast::DiscardStatement* stmt);
   /// Handles generating a scalar constructor
+  /// @param out the output stream
   /// @param expr the scalar constructor expression
   /// @returns true if the scalar constructor is emitted
-  bool EmitScalarConstructor(ast::ScalarConstructorExpression* expr);
+  bool EmitScalarConstructor(std::ostream& out,
+                             ast::ScalarConstructorExpression* expr);
   /// Handles emitting a type constructor
+  /// @param out the output stream
   /// @param expr the type constructor expression
   /// @returns true if the constructor is emitted
-  bool EmitTypeConstructor(ast::TypeConstructorExpression* expr);
+  bool EmitTypeConstructor(std::ostream& out,
+                           ast::TypeConstructorExpression* expr);
   /// Handles a continue statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted successfully
-  bool EmitContinue(ast::ContinueStatement* stmt);
+  bool EmitContinue(std::ostream& out, ast::ContinueStatement* stmt);
   /// Handles generating an else statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted
-  bool EmitElse(ast::ElseStatement* stmt);
+  bool EmitElse(std::ostream& out, ast::ElseStatement* stmt);
   /// Handles generate an Expression
+  /// @param out the output stream
   /// @param expr the expression
   /// @returns true if the expression was emitted
-  bool EmitExpression(ast::Expression* expr);
+  bool EmitExpression(std::ostream& out, ast::Expression* expr);
   /// Handles generating a function
+  /// @param out the output stream
   /// @param func the function to generate
   /// @returns true if the function was emitted
-  bool EmitFunction(ast::Function* func);
+  bool EmitFunction(std::ostream& out, ast::Function* func);
   /// Internal helper for emitting functions
+  /// @param out the output stream
   /// @param func the function to emit
   /// @param emit_duplicate_functions set true if we need to duplicate per entry
   /// point
   /// @param ep_name the current entry point or blank if none set
   /// @returns true if the function was emitted.
-  bool EmitFunctionInternal(ast::Function* func,
+  bool EmitFunctionInternal(std::ostream& out,
+                            ast::Function* func,
                             bool emit_duplicate_functions,
                             const std::string& ep_name);
   /// Handles emitting information for an entry point
+  /// @param out the output stream
   /// @param ep the entry point
   /// @returns true if the entry point data was emitted
-  bool EmitEntryPointData(ast::EntryPoint* ep);
+  bool EmitEntryPointData(std::ostream& out, ast::EntryPoint* ep);
   /// Handles emitting the entry point function
+  /// @param out the output stream
   /// @param ep the entry point
   /// @returns true if the entry point function was emitted
-  bool EmitEntryPointFunction(ast::EntryPoint* ep);
+  bool EmitEntryPointFunction(std::ostream& out, ast::EntryPoint* ep);
   /// Handles an if statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was successfully emitted
-  bool EmitIf(ast::IfStatement* stmt);
+  bool EmitIf(std::ostream& out, ast::IfStatement* stmt);
   /// Handles genreating an import expression
+  /// @param out the output stream
   /// @param expr the expression
   /// @returns true if the expression was successfully emitted.
-  bool EmitImportFunction(ast::CallExpression* expr);
+  bool EmitImportFunction(std::ostream& out, ast::CallExpression* expr);
   /// Handles a literal
+  /// @param out the output stream
   /// @param lit the literal to emit
   /// @returns true if the literal was successfully emitted
-  bool EmitLiteral(ast::Literal* lit);
+  bool EmitLiteral(std::ostream& out, ast::Literal* lit);
   /// Handles a loop statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted
-  bool EmitLoop(ast::LoopStatement* stmt);
+  bool EmitLoop(std::ostream& out, ast::LoopStatement* stmt);
   /// Handles generating an identifier expression
+  /// @param out the output stream
   /// @param expr the identifier expression
   /// @returns true if the identifeir was emitted
-  bool EmitIdentifier(ast::IdentifierExpression* expr);
+  bool EmitIdentifier(std::ostream& out, ast::IdentifierExpression* expr);
   /// Handles a member accessor expression
+  /// @param out the output stream
   /// @param expr the member accessor expression
   /// @returns true if the member accessor was emitted
-  bool EmitMemberAccessor(ast::MemberAccessorExpression* expr);
+  bool EmitMemberAccessor(std::ostream& out,
+                          ast::MemberAccessorExpression* expr);
   /// Handles a storage buffer accessor expression
+  /// @param out the output stream
   /// @param expr the storage buffer accessor expression
   /// @param rhs the right side of a store expression. Set to nullptr for a load
   /// @returns true if the storage buffer accessor was emitted
-  bool EmitStorageBufferAccessor(ast::Expression* expr, ast::Expression* rhs);
+  bool EmitStorageBufferAccessor(std::ostream& out,
+                                 ast::Expression* expr,
+                                 ast::Expression* rhs);
   /// Handles return statements
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was successfully emitted
-  bool EmitReturn(ast::ReturnStatement* stmt);
+  bool EmitReturn(std::ostream& out, ast::ReturnStatement* stmt);
   /// Handles statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted
-  bool EmitStatement(ast::Statement* stmt);
+  bool EmitStatement(std::ostream& out, ast::Statement* stmt);
   /// Handles generating a switch statement
+  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was emitted
-  bool EmitSwitch(ast::SwitchStatement* stmt);
+  bool EmitSwitch(std::ostream& out, ast::SwitchStatement* stmt);
   /// Handles generating type
+  /// @param out the output stream
   /// @param type the type to generate
   /// @param name the name of the variable, only used for array emission
   /// @returns true if the type is emitted
-  bool EmitType(ast::type::Type* type, const std::string& name);
+  bool EmitType(std::ostream& out,
+                ast::type::Type* type,
+                const std::string& name);
   /// Handles a unary op expression
+  /// @param out the output stream
   /// @param expr the expression to emit
   /// @returns true if the expression was emitted
-  bool EmitUnaryOp(ast::UnaryOpExpression* expr);
+  bool EmitUnaryOp(std::ostream& out, ast::UnaryOpExpression* expr);
   /// Emits the zero value for the given type
+  /// @param out the output stream
   /// @param type the type to emit the value for
   /// @returns true if the zero value was successfully emitted.
-  bool EmitZeroValue(ast::type::Type* type);
+  bool EmitZeroValue(std::ostream& out, ast::type::Type* type);
   /// Handles generating a variable
+  /// @param out the output stream
   /// @param var the variable to generate
   /// @returns true if the variable was emitted
-  bool EmitVariable(ast::Variable* var);
+  bool EmitVariable(std::ostream& out, ast::Variable* var);
   /// Handles generating a program scope constant variable
+  /// @param out the output stream
   /// @param var the variable to emit
   /// @returns true if the variable was emitted
-  bool EmitProgramConstVariable(const ast::Variable* var);
+  bool EmitProgramConstVariable(std::ostream& out, const ast::Variable* var);
 
   /// Returns true if the accessor is accessing a storage buffer.
   /// @param expr the expression to check
@@ -252,6 +317,9 @@
 
   std::string current_ep_var_name(VarType type);
 
+  std::string error_;
+  size_t indent_ = 0;
+
   Namer namer_;
   ast::Module* module_ = nullptr;
   std::string current_ep_name_;
diff --git a/src/writer/hlsl/generator_impl_alias_type_test.cc b/src/writer/hlsl/generator_impl_alias_type_test.cc
index fd24703..c68de9f 100644
--- a/src/writer/hlsl/generator_impl_alias_type_test.cc
+++ b/src/writer/hlsl/generator_impl_alias_type_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/module.h"
 #include "src/ast/struct.h"
 #include "src/ast/struct_member.h"
@@ -21,38 +20,35 @@
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type/i32_type.h"
 #include "src/ast/type/struct_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_AliasType : public TestHelper,
+                                        public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitAliasType_F32) {
+TEST_F(HlslGeneratorImplTest_AliasType, EmitAliasType_F32) {
   ast::type::F32Type f32;
   ast::type::AliasType alias("a", &f32);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitAliasType(&alias)) << g.error();
-  EXPECT_EQ(g.result(), R"(typedef float a;
+  ASSERT_TRUE(gen().EmitAliasType(out(), &alias)) << gen().error();
+  EXPECT_EQ(result(), R"(typedef float a;
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitAliasType_NameCollision) {
+TEST_F(HlslGeneratorImplTest_AliasType, EmitAliasType_NameCollision) {
   ast::type::F32Type f32;
   ast::type::AliasType alias("float", &f32);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitAliasType(&alias)) << g.error();
-  EXPECT_EQ(g.result(), R"(typedef float float_tint_0;
+  ASSERT_TRUE(gen().EmitAliasType(out(), &alias)) << gen().error();
+  EXPECT_EQ(result(), R"(typedef float float_tint_0;
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitAliasType_Struct) {
+TEST_F(HlslGeneratorImplTest_AliasType, EmitAliasType_Struct) {
   ast::type::I32Type i32;
   ast::type::F32Type f32;
 
@@ -73,8 +69,8 @@
 
   ast::Module m;
   GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitAliasType(&alias)) << g.error();
-  EXPECT_EQ(g.result(), R"(struct a {
+  ASSERT_TRUE(gen().EmitAliasType(out(), &alias)) << gen().error();
+  EXPECT_EQ(result(), R"(struct a {
   float a;
   int b;
 };
diff --git a/src/writer/hlsl/generator_impl_array_accessor_test.cc b/src/writer/hlsl/generator_impl_array_accessor_test.cc
index ecd5310..ce7371d 100644
--- a/src/writer/hlsl/generator_impl_array_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_array_accessor_test.cc
@@ -14,23 +14,23 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/array_accessor_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/type/i32_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Expression : public TestHelper,
+                                         public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_ArrayAccessor) {
+TEST_F(HlslGeneratorImplTest_Expression, EmitExpression_ArrayAccessor) {
   ast::type::I32Type i32;
   auto lit = std::make_unique<ast::SintLiteral>(&i32, 5);
   auto idx = std::make_unique<ast::ScalarConstructorExpression>(std::move(lit));
@@ -38,22 +38,18 @@
 
   ast::ArrayAccessorExpression expr(std::move(ary), std::move(idx));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "ary[5]");
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "ary[5]");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitArrayAccessor) {
+TEST_F(HlslGeneratorImplTest_Expression, EmitArrayAccessor) {
   auto ary = std::make_unique<ast::IdentifierExpression>("ary");
   auto idx = std::make_unique<ast::IdentifierExpression>("idx");
 
   ast::ArrayAccessorExpression expr(std::move(ary), std::move(idx));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "ary[idx]");
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "ary[idx]");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_as_test.cc b/src/writer/hlsl/generator_impl_as_test.cc
index c7975ce..710b57e 100644
--- a/src/writer/hlsl/generator_impl_as_test.cc
+++ b/src/writer/hlsl/generator_impl_as_test.cc
@@ -14,53 +14,46 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/as_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type/i32_type.h"
 #include "src/ast/type/u32_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_As : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_As_Float) {
+TEST_F(HlslGeneratorImplTest_As, EmitExpression_As_Float) {
   ast::type::F32Type f32;
   auto id = std::make_unique<ast::IdentifierExpression>("id");
   ast::AsExpression as(&f32, std::move(id));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&as)) << g.error();
-  EXPECT_EQ(g.result(), "asfloat(id)");
+  ASSERT_TRUE(gen().EmitExpression(out(), &as)) << gen().error();
+  EXPECT_EQ(result(), "asfloat(id)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_As_Int) {
+TEST_F(HlslGeneratorImplTest_As, EmitExpression_As_Int) {
   ast::type::I32Type i32;
   auto id = std::make_unique<ast::IdentifierExpression>("id");
   ast::AsExpression as(&i32, std::move(id));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&as)) << g.error();
-  EXPECT_EQ(g.result(), "asint(id)");
+  ASSERT_TRUE(gen().EmitExpression(out(), &as)) << gen().error();
+  EXPECT_EQ(result(), "asint(id)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_As_Uint) {
+TEST_F(HlslGeneratorImplTest_As, EmitExpression_As_Uint) {
   ast::type::U32Type u32;
   auto id = std::make_unique<ast::IdentifierExpression>("id");
   ast::AsExpression as(&u32, std::move(id));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&as)) << g.error();
-  EXPECT_EQ(g.result(), "asuint(id)");
+  ASSERT_TRUE(gen().EmitExpression(out(), &as)) << gen().error();
+  EXPECT_EQ(result(), "asuint(id)");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_assign_test.cc b/src/writer/hlsl/generator_impl_assign_test.cc
index b8e551a..5781d97 100644
--- a/src/writer/hlsl/generator_impl_assign_test.cc
+++ b/src/writer/hlsl/generator_impl_assign_test.cc
@@ -15,30 +15,27 @@
 #include <memory>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Assign : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Assign) {
+TEST_F(HlslGeneratorImplTest_Assign, Emit_Assign) {
   auto lhs = std::make_unique<ast::IdentifierExpression>("lhs");
   auto rhs = std::make_unique<ast::IdentifierExpression>("rhs");
   ast::AssignmentStatement assign(std::move(lhs), std::move(rhs));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&assign)) << g.error();
-  EXPECT_EQ(g.result(), "  lhs = rhs;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &assign)) << gen().error();
+  EXPECT_EQ(result(), "  lhs = rhs;\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_binary_test.cc b/src/writer/hlsl/generator_impl_binary_test.cc
index 44a393c..717148a 100644
--- a/src/writer/hlsl/generator_impl_binary_test.cc
+++ b/src/writer/hlsl/generator_impl_binary_test.cc
@@ -14,11 +14,10 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/binary_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
@@ -33,7 +32,9 @@
   out << data.op;
   return out;
 }
-using HlslBinaryTest = testing::TestWithParam<BinaryData>;
+
+class HlslBinaryTest : public TestHelper,
+                       public testing::TestWithParam<BinaryData> {};
 TEST_P(HlslBinaryTest, Emit) {
   auto params = GetParam();
 
@@ -42,10 +43,8 @@
 
   ast::BinaryExpression expr(params.op, std::move(left), std::move(right));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), params.result);
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), params.result);
 }
 INSTANTIATE_TEST_SUITE_P(
     HlslGeneratorImplTest,
diff --git a/src/writer/hlsl/generator_impl_block_test.cc b/src/writer/hlsl/generator_impl_block_test.cc
index 772274f..1d44592 100644
--- a/src/writer/hlsl/generator_impl_block_test.cc
+++ b/src/writer/hlsl/generator_impl_block_test.cc
@@ -14,43 +14,38 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/block_statement.h"
 #include "src/ast/discard_statement.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Block : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Block) {
+TEST_F(HlslGeneratorImplTest_Block, Emit_Block) {
   ast::BlockStatement b;
   b.append(std::make_unique<ast::DiscardStatement>());
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&b)) << g.error();
-  EXPECT_EQ(g.result(), R"(  {
+  ASSERT_TRUE(gen().EmitStatement(out(), &b)) << gen().error();
+  EXPECT_EQ(result(), R"(  {
     discard;
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Block_WithoutNewline) {
+TEST_F(HlslGeneratorImplTest_Block, Emit_Block_WithoutNewline) {
   ast::BlockStatement b;
   b.append(std::make_unique<ast::DiscardStatement>());
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitBlock(&b)) << g.error();
-  EXPECT_EQ(g.result(), R"({
+  ASSERT_TRUE(gen().EmitBlock(out(), &b)) << gen().error();
+  EXPECT_EQ(result(), R"({
     discard;
   })");
 }
diff --git a/src/writer/hlsl/generator_impl_break_test.cc b/src/writer/hlsl/generator_impl_break_test.cc
index ad53672..eafd188 100644
--- a/src/writer/hlsl/generator_impl_break_test.cc
+++ b/src/writer/hlsl/generator_impl_break_test.cc
@@ -15,27 +15,24 @@
 #include <memory>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/break_statement.h"
 #include "src/ast/module.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Break : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Break) {
+TEST_F(HlslGeneratorImplTest_Break, Emit_Break) {
   ast::BreakStatement b;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&b)) << g.error();
-  EXPECT_EQ(g.result(), "  break;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &b)) << gen().error();
+  EXPECT_EQ(result(), "  break;\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_call_test.cc b/src/writer/hlsl/generator_impl_call_test.cc
index e1082a0..3405d1b 100644
--- a/src/writer/hlsl/generator_impl_call_test.cc
+++ b/src/writer/hlsl/generator_impl_call_test.cc
@@ -14,23 +14,22 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/function.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/type/void_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Call : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_Call_WithoutParams) {
+TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithoutParams) {
   ast::type::VoidType void_type;
 
   auto id = std::make_unique<ast::IdentifierExpression>("my_func");
@@ -38,16 +37,13 @@
 
   auto func = std::make_unique<ast::Function>("my_func", ast::VariableList{},
                                               &void_type);
+  mod()->AddFunction(std::move(func));
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
-
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&call)) << g.error();
-  EXPECT_EQ(g.result(), "my_func()");
+  ASSERT_TRUE(gen().EmitExpression(out(), &call)) << gen().error();
+  EXPECT_EQ(result(), "my_func()");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_Call_WithParams) {
+TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
   ast::type::VoidType void_type;
 
   auto id = std::make_unique<ast::IdentifierExpression>("my_func");
@@ -58,16 +54,13 @@
 
   auto func = std::make_unique<ast::Function>("my_func", ast::VariableList{},
                                               &void_type);
+  mod()->AddFunction(std::move(func));
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
-
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&call)) << g.error();
-  EXPECT_EQ(g.result(), "my_func(param1, param2)");
+  ASSERT_TRUE(gen().EmitExpression(out(), &call)) << gen().error();
+  EXPECT_EQ(result(), "my_func(param1, param2)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitStatement_Call) {
+TEST_F(HlslGeneratorImplTest_Call, EmitStatement_Call) {
   ast::type::VoidType void_type;
 
   auto id = std::make_unique<ast::IdentifierExpression>("my_func");
@@ -79,14 +72,10 @@
 
   auto func = std::make_unique<ast::Function>("my_func", ast::VariableList{},
                                               &void_type);
-
-  ast::Module m;
-  m.AddFunction(std::move(func));
-
-  GeneratorImpl g(&m);
-  g.increment_indent();
-  ASSERT_TRUE(g.EmitStatement(&call)) << g.error();
-  EXPECT_EQ(g.result(), "  my_func(param1, param2);\n");
+  mod()->AddFunction(std::move(func));
+  gen().increment_indent();
+  ASSERT_TRUE(gen().EmitStatement(out(), &call)) << gen().error();
+  EXPECT_EQ(result(), "  my_func(param1, param2);\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_case_test.cc b/src/writer/hlsl/generator_impl_case_test.cc
index acc0c8b..05d06c3 100644
--- a/src/writer/hlsl/generator_impl_case_test.cc
+++ b/src/writer/hlsl/generator_impl_case_test.cc
@@ -14,7 +14,6 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/break_statement.h"
 #include "src/ast/case_statement.h"
 #include "src/ast/fallthrough_statement.h"
@@ -22,16 +21,16 @@
 #include "src/ast/module.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/type/i32_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Case : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Case) {
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case) {
   ast::type::I32Type i32;
 
   auto body = std::make_unique<ast::BlockStatement>();
@@ -41,36 +40,32 @@
   lit.push_back(std::make_unique<ast::SintLiteral>(&i32, 5));
   ast::CaseStatement c(std::move(lit), std::move(body));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitCase(&c)) << g.error();
-  EXPECT_EQ(g.result(), R"(  case 5: {
+  ASSERT_TRUE(gen().EmitCase(out(), &c)) << gen().error();
+  EXPECT_EQ(result(), R"(  case 5: {
     break;
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Case_BreaksByDefault) {
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_BreaksByDefault) {
   ast::type::I32Type i32;
 
   ast::CaseSelectorList lit;
   lit.push_back(std::make_unique<ast::SintLiteral>(&i32, 5));
   ast::CaseStatement c(std::move(lit), std::make_unique<ast::BlockStatement>());
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitCase(&c)) << g.error();
-  EXPECT_EQ(g.result(), R"(  case 5: {
+  ASSERT_TRUE(gen().EmitCase(out(), &c)) << gen().error();
+  EXPECT_EQ(result(), R"(  case 5: {
     break;
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Case_WithFallthrough) {
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) {
   ast::type::I32Type i32;
 
   auto body = std::make_unique<ast::BlockStatement>();
@@ -80,18 +75,16 @@
   lit.push_back(std::make_unique<ast::SintLiteral>(&i32, 5));
   ast::CaseStatement c(std::move(lit), std::move(body));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitCase(&c)) << g.error();
-  EXPECT_EQ(g.result(), R"(  case 5: {
+  ASSERT_TRUE(gen().EmitCase(out(), &c)) << gen().error();
+  EXPECT_EQ(result(), R"(  case 5: {
     /* fallthrough */
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Case_MultipleSelectors) {
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
   ast::type::I32Type i32;
 
   auto body = std::make_unique<ast::BlockStatement>();
@@ -102,31 +95,27 @@
   lit.push_back(std::make_unique<ast::SintLiteral>(&i32, 6));
   ast::CaseStatement c(std::move(lit), std::move(body));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitCase(&c)) << g.error();
-  EXPECT_EQ(g.result(), R"(  case 5:
+  ASSERT_TRUE(gen().EmitCase(out(), &c)) << gen().error();
+  EXPECT_EQ(result(), R"(  case 5:
   case 6: {
     break;
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Case_Default) {
+TEST_F(HlslGeneratorImplTest_Case, Emit_Case_Default) {
   ast::CaseStatement c;
 
   auto body = std::make_unique<ast::BlockStatement>();
   body->append(std::make_unique<ast::BreakStatement>());
   c.set_body(std::move(body));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitCase(&c)) << g.error();
-  EXPECT_EQ(g.result(), R"(  default: {
+  ASSERT_TRUE(gen().EmitCase(out(), &c)) << gen().error();
+  EXPECT_EQ(result(), R"(  default: {
     break;
   }
 )");
diff --git a/src/writer/hlsl/generator_impl_cast_test.cc b/src/writer/hlsl/generator_impl_cast_test.cc
index c9d6f87..1e782b3 100644
--- a/src/writer/hlsl/generator_impl_cast_test.cc
+++ b/src/writer/hlsl/generator_impl_cast_test.cc
@@ -14,43 +14,38 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/cast_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type/vector_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Cast : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_Cast_Scalar) {
+TEST_F(HlslGeneratorImplTest_Cast, EmitExpression_Cast_Scalar) {
   ast::type::F32Type f32;
   auto id = std::make_unique<ast::IdentifierExpression>("id");
   ast::CastExpression cast(&f32, std::move(id));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&cast)) << g.error();
-  EXPECT_EQ(g.result(), "float(id)");
+  ASSERT_TRUE(gen().EmitExpression(out(), &cast)) << gen().error();
+  EXPECT_EQ(result(), "float(id)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_Cast_Vector) {
+TEST_F(HlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) {
   ast::type::F32Type f32;
   ast::type::VectorType vec3(&f32, 3);
 
   auto id = std::make_unique<ast::IdentifierExpression>("id");
   ast::CastExpression cast(&vec3, std::move(id));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&cast)) << g.error();
-  EXPECT_EQ(g.result(), "vector<float, 3>(id)");
+  ASSERT_TRUE(gen().EmitExpression(out(), &cast)) << gen().error();
+  EXPECT_EQ(result(), "vector<float, 3>(id)");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_constructor_test.cc b/src/writer/hlsl/generator_impl_constructor_test.cc
index 823fa21..f9b2b2b 100644
--- a/src/writer/hlsl/generator_impl_constructor_test.cc
+++ b/src/writer/hlsl/generator_impl_constructor_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/bool_literal.h"
 #include "src/ast/float_literal.h"
 #include "src/ast/module.h"
@@ -27,61 +26,54 @@
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type_constructor_expression.h"
 #include "src/ast/uint_literal.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Constructor : public TestHelper,
+                                          public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Bool) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Bool) {
   ast::type::BoolType bool_type;
   auto lit = std::make_unique<ast::BoolLiteral>(&bool_type, false);
   ast::ScalarConstructorExpression expr(std::move(lit));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "false");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "false");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Int) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Int) {
   ast::type::I32Type i32;
   auto lit = std::make_unique<ast::SintLiteral>(&i32, -12345);
   ast::ScalarConstructorExpression expr(std::move(lit));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "-12345");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "-12345");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_UInt) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_UInt) {
   ast::type::U32Type u32;
   auto lit = std::make_unique<ast::UintLiteral>(&u32, 56779);
   ast::ScalarConstructorExpression expr(std::move(lit));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "56779u");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "56779u");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Float) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Float) {
   ast::type::F32Type f32;
   // Use a number close to 1<<30 but whose decimal representation ends in 0.
   auto lit = std::make_unique<ast::FloatLiteral>(&f32, float((1 << 30) - 4));
   ast::ScalarConstructorExpression expr(std::move(lit));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "1.07374182e+09f");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "1.07374182e+09f");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Float) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Float) {
   ast::type::F32Type f32;
 
   auto lit = std::make_unique<ast::FloatLiteral>(&f32, -1.2e-5);
@@ -91,13 +83,11 @@
 
   ast::TypeConstructorExpression expr(&f32, std::move(values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "float(-1.20000004e-05f)");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "float(-1.20000004e-05f)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Bool) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) {
   ast::type::BoolType b;
 
   auto lit = std::make_unique<ast::BoolLiteral>(&b, true);
@@ -107,13 +97,11 @@
 
   ast::TypeConstructorExpression expr(&b, std::move(values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "bool(true)");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "bool(true)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Int) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) {
   ast::type::I32Type i32;
 
   auto lit = std::make_unique<ast::SintLiteral>(&i32, -12345);
@@ -123,13 +111,11 @@
 
   ast::TypeConstructorExpression expr(&i32, std::move(values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "int(-12345)");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "int(-12345)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Uint) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) {
   ast::type::U32Type u32;
 
   auto lit = std::make_unique<ast::UintLiteral>(&u32, 12345);
@@ -139,13 +125,11 @@
 
   ast::TypeConstructorExpression expr(&u32, std::move(values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "uint(12345u)");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "uint(12345u)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Vec) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) {
   ast::type::F32Type f32;
   ast::type::VectorType vec(&f32, 3);
 
@@ -162,27 +146,23 @@
 
   ast::TypeConstructorExpression expr(&vec, std::move(values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(),
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(),
             "vector<float, 3>(1.00000000f, 2.00000000f, 3.00000000f)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Vec_Empty) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_Empty) {
   ast::type::F32Type f32;
   ast::type::VectorType vec(&f32, 3);
 
   ast::ExpressionList values;
   ast::TypeConstructorExpression expr(&vec, std::move(values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "vector<float, 3>(0.0f)");
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "vector<float, 3>(0.0f)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Mat) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) {
   ast::type::F32Type f32;
   ast::type::MatrixType mat(&f32, 3, 2);  // 3 ROWS, 2 COLUMNS
   ast::type::VectorType vec(&f32, 3);
@@ -214,19 +194,17 @@
 
   ast::TypeConstructorExpression expr(&mat, std::move(mat_values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
 
   // A matrix of type T with n columns and m rows can also be constructed from
   // n vectors of type T with m components.
-  EXPECT_EQ(g.result(),
+  EXPECT_EQ(result(),
             std::string("matrix<float, 3, 2>(vector<float, 3>(1.00000000f, "
                         "2.00000000f, 3.00000000f), ") +
                 "vector<float, 3>(3.00000000f, 4.00000000f, 5.00000000f))");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitConstructor_Type_Array) {
+TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Array) {
   ast::type::F32Type f32;
   ast::type::VectorType vec(&f32, 3);
   ast::type::ArrayType ary(&vec, 3);
@@ -255,10 +233,8 @@
 
   ast::TypeConstructorExpression expr(&ary, std::move(ary_values));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitConstructor(&expr)) << g.error();
-  EXPECT_EQ(g.result(),
+  ASSERT_TRUE(gen().EmitConstructor(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(),
             std::string("{") +
                 "vector<float, 3>(1.00000000f, 2.00000000f, 3.00000000f), " +
                 "vector<float, 3>(4.00000000f, 5.00000000f, 6.00000000f), " +
@@ -266,7 +242,8 @@
 }
 
 // TODO(dsinclair): Add struct constructor test.
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitConstructor_Type_Struct) {}
+TEST_F(HlslGeneratorImplTest_Constructor,
+       DISABLED_EmitConstructor_Type_Struct) {}
 
 }  // namespace
 }  // namespace hlsl
diff --git a/src/writer/hlsl/generator_impl_continue_test.cc b/src/writer/hlsl/generator_impl_continue_test.cc
index 1aa0238..97b4ae0 100644
--- a/src/writer/hlsl/generator_impl_continue_test.cc
+++ b/src/writer/hlsl/generator_impl_continue_test.cc
@@ -15,27 +15,25 @@
 #include <memory>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/continue_statement.h"
 #include "src/ast/module.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Continue : public TestHelper,
+                                       public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Continue) {
+TEST_F(HlslGeneratorImplTest_Continue, Emit_Continue) {
   ast::ContinueStatement c;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&c)) << g.error();
-  EXPECT_EQ(g.result(), "  continue;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &c)) << gen().error();
+  EXPECT_EQ(result(), "  continue;\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_discard_test.cc b/src/writer/hlsl/generator_impl_discard_test.cc
index 4dfa099..bbd4383 100644
--- a/src/writer/hlsl/generator_impl_discard_test.cc
+++ b/src/writer/hlsl/generator_impl_discard_test.cc
@@ -12,27 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/discard_statement.h"
 #include "src/ast/module.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Discard : public TestHelper,
+                                      public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Discard) {
+TEST_F(HlslGeneratorImplTest_Discard, Emit_Discard) {
   ast::DiscardStatement stmt;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), "  discard;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), "  discard;\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_entry_point_test.cc b/src/writer/hlsl/generator_impl_entry_point_test.cc
index d7a55a9..2177806 100644
--- a/src/writer/hlsl/generator_impl_entry_point_test.cc
+++ b/src/writer/hlsl/generator_impl_entry_point_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/decorated_variable.h"
 #include "src/ast/entry_point.h"
@@ -27,16 +26,17 @@
 #include "src/ast/variable.h"
 #include "src/context.h"
 #include "src/type_determiner.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_EntryPoint : public TestHelper,
+                                         public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitEntryPointData_Vertex_Input) {
+TEST_F(HlslGeneratorImplTest_EntryPoint, EmitEntryPointData_Vertex_Input) {
   // [[location 0]] var<in> foo : f32;
   // [[location 1]] var<in> bar : i32;
   //
@@ -60,14 +60,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func =
@@ -82,19 +79,17 @@
       std::make_unique<ast::IdentifierExpression>("bar")));
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kVertex, "",
                                               "vtx_main");
   auto* ep_ptr = ep.get();
 
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitEntryPointData(ep_ptr)) << g.error();
-  EXPECT_EQ(g.result(), R"(struct vtx_main_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().EmitEntryPointData(out(), ep_ptr)) << gen().error();
+  EXPECT_EQ(result(), R"(struct vtx_main_in {
   float foo : TEXCOORD0;
   int bar : TEXCOORD1;
 };
@@ -102,7 +97,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitEntryPointData_Vertex_Output) {
+TEST_F(HlslGeneratorImplTest_EntryPoint, EmitEntryPointData_Vertex_Output) {
   // [[location 0]] var<out> foo : f32;
   // [[location 1]] var<out> bar : i32;
   //
@@ -126,14 +121,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func =
@@ -148,19 +140,17 @@
       std::make_unique<ast::IdentifierExpression>("bar")));
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kVertex, "",
                                               "vtx_main");
   auto* ep_ptr = ep.get();
 
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitEntryPointData(ep_ptr)) << g.error();
-  EXPECT_EQ(g.result(), R"(struct vtx_main_out {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().EmitEntryPointData(out(), ep_ptr)) << gen().error();
+  EXPECT_EQ(result(), R"(struct vtx_main_out {
   float foo : TEXCOORD0;
   int bar : TEXCOORD1;
 };
@@ -168,7 +158,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitEntryPointData_Fragment_Input) {
+TEST_F(HlslGeneratorImplTest_EntryPoint, EmitEntryPointData_Fragment_Input) {
   // [[location 0]] var<in> foo : f32;
   // [[location 1]] var<in> bar : i32;
   //
@@ -192,14 +182,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func =
@@ -214,19 +201,17 @@
       std::make_unique<ast::IdentifierExpression>("bar")));
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                               "main", "frag_main");
   auto* ep_ptr = ep.get();
 
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitEntryPointData(ep_ptr)) << g.error();
-  EXPECT_EQ(g.result(), R"(struct main_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().EmitEntryPointData(out(), ep_ptr)) << gen().error();
+  EXPECT_EQ(result(), R"(struct main_in {
   float foo : TEXCOORD0;
   int bar : TEXCOORD1;
 };
@@ -234,7 +219,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitEntryPointData_Fragment_Output) {
+TEST_F(HlslGeneratorImplTest_EntryPoint, EmitEntryPointData_Fragment_Output) {
   // [[location 0]] var<out> foo : f32;
   // [[location 1]] var<out> bar : i32;
   //
@@ -258,14 +243,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func =
@@ -280,19 +262,17 @@
       std::make_unique<ast::IdentifierExpression>("bar")));
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                               "main", "frag_main");
   auto* ep_ptr = ep.get();
 
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitEntryPointData(ep_ptr)) << g.error();
-  EXPECT_EQ(g.result(), R"(struct main_out {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().EmitEntryPointData(out(), ep_ptr)) << gen().error();
+  EXPECT_EQ(result(), R"(struct main_out {
   float foo : SV_Target0;
   int bar : SV_Target1;
 };
@@ -300,7 +280,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitEntryPointData_Compute_Input) {
+TEST_F(HlslGeneratorImplTest_EntryPoint, EmitEntryPointData_Compute_Input) {
   // [[location 0]] var<in> foo : f32;
   // [[location 1]] var<in> bar : i32;
   //
@@ -321,14 +301,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func =
@@ -343,22 +320,20 @@
       std::make_unique<ast::IdentifierExpression>("bar")));
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kCompute,
                                               "main", "comp_main");
   auto* ep_ptr = ep.get();
 
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_FALSE(g.EmitEntryPointData(ep_ptr)) << g.error();
-  EXPECT_EQ(g.error(), R"(invalid location variable for pipeline stage)");
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_FALSE(gen().EmitEntryPointData(out(), ep_ptr)) << gen().error();
+  EXPECT_EQ(gen().error(), R"(invalid location variable for pipeline stage)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitEntryPointData_Compute_Output) {
+TEST_F(HlslGeneratorImplTest_EntryPoint, EmitEntryPointData_Compute_Output) {
   // [[location 0]] var<out> foo : f32;
   // [[location 1]] var<out> bar : i32;
   //
@@ -379,14 +354,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func =
@@ -401,22 +373,20 @@
       std::make_unique<ast::IdentifierExpression>("bar")));
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kCompute,
                                               "main", "comp_main");
   auto* ep_ptr = ep.get();
 
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_FALSE(g.EmitEntryPointData(ep_ptr)) << g.error();
-  EXPECT_EQ(g.error(), R"(invalid location variable for pipeline stage)");
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_FALSE(gen().EmitEntryPointData(out(), ep_ptr)) << gen().error();
+  EXPECT_EQ(gen().error(), R"(invalid location variable for pipeline stage)");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitEntryPointData_Builtins) {
+TEST_F(HlslGeneratorImplTest_EntryPoint, EmitEntryPointData_Builtins) {
   // [[builtin frag_coord]] var<in> coord : vec4<f32>;
   // [[builtin frag_depth]] var<out> depth : f32;
   //
@@ -448,14 +418,11 @@
       std::make_unique<ast::BuiltinDecoration>(ast::Builtin::kFragDepth));
   depth_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  td.RegisterVariableForTesting(depth_var.get());
+  td().RegisterVariableForTesting(coord_var.get());
+  td().RegisterVariableForTesting(depth_var.get());
 
-  mod.AddGlobalVariable(std::move(coord_var));
-  mod.AddGlobalVariable(std::move(depth_var));
+  mod()->AddGlobalVariable(std::move(coord_var));
+  mod()->AddGlobalVariable(std::move(depth_var));
 
   ast::VariableList params;
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
@@ -469,19 +436,17 @@
           std::make_unique<ast::IdentifierExpression>("x"))));
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                               "main", "frag_main");
   auto* ep_ptr = ep.get();
 
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitEntryPointData(ep_ptr)) << g.error();
-  EXPECT_EQ(g.result(), R"(struct main_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().EmitEntryPointData(out(), ep_ptr)) << gen().error();
+  EXPECT_EQ(result(), R"(struct main_in {
   vector<float, 4> coord : SV_Position;
 };
 
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index 9170fc7..51b6830 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/binary_expression.h"
 #include "src/ast/binding_decoration.h"
@@ -42,16 +41,17 @@
 #include "src/ast/variable_decl_statement.h"
 #include "src/context.h"
 #include "src/type_determiner.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Function : public TestHelper,
+                                       public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Function) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function) {
   ast::type::VoidType void_type;
 
   auto func = std::make_unique<ast::Function>("my_func", ast::VariableList{},
@@ -61,21 +61,18 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
+  gen().increment_indent();
 
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(  void my_func() {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(  void my_func() {
     return;
   }
 
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_Name_Collision) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_Name_Collision) {
   ast::type::VoidType void_type;
 
   auto func = std::make_unique<ast::Function>("GeometryShader",
@@ -85,21 +82,18 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
+  gen().increment_indent();
 
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(  void GeometryShader_tint_0() {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(  void GeometryShader_tint_0() {
     return;
   }
 
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_WithParams) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithParams) {
   ast::type::F32Type f32;
   ast::type::I32Type i32;
 
@@ -117,21 +111,18 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
+  gen().increment_indent();
 
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(  void my_func(float a, int b) {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(  void my_func(float a, int b) {
     return;
   }
 
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_NoName) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_EntryPoint_NoName) {
   ast::type::VoidType void_type;
 
   auto func = std::make_unique<ast::Function>("frag_main", ast::VariableList{},
@@ -139,19 +130,17 @@
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
-  m.AddEntryPoint(std::move(ep));
+  mod()->AddFunction(std::move(func));
+  mod()->AddEntryPoint(std::move(ep));
 
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(void frag_main() {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(void frag_main() {
 }
 
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_WithInOutVars) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_EntryPoint_WithInOutVars) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
 
@@ -167,14 +156,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
@@ -187,17 +173,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct frag_main_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct frag_main_in {
   float foo : TEXCOORD0;
 };
 
@@ -214,7 +198,8 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_WithInOut_Builtins) {
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Function_EntryPoint_WithInOut_Builtins) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
   ast::type::VectorType vec4(&f32, 4);
@@ -235,14 +220,11 @@
       std::make_unique<ast::BuiltinDecoration>(ast::Builtin::kFragDepth));
   depth_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  td.RegisterVariableForTesting(depth_var.get());
+  td().RegisterVariableForTesting(coord_var.get());
+  td().RegisterVariableForTesting(depth_var.get());
 
-  mod.AddGlobalVariable(std::move(coord_var));
-  mod.AddGlobalVariable(std::move(depth_var));
+  mod()->AddGlobalVariable(std::move(coord_var));
+  mod()->AddGlobalVariable(std::move(depth_var));
 
   ast::VariableList params;
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
@@ -257,17 +239,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct frag_main_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct frag_main_in {
   vector<float, 4> coord : SV_Position;
 };
 
@@ -284,7 +264,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_With_Uniform) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_EntryPoint_With_Uniform) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
   ast::type::VectorType vec4(&f32, 4);
@@ -298,12 +278,8 @@
   decos.push_back(std::make_unique<ast::SetDecoration>(1));
   coord_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
-
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
   ast::VariableList params;
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
@@ -320,17 +296,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(cbuffer : register(b0) {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(cbuffer : register(b0) {
   vector<float, 4> coord;
 };
 
@@ -342,7 +316,8 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_With_UniformStruct) {
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Function_EntryPoint_With_UniformStruct) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
   ast::type::VectorType vec4(&f32, 4);
@@ -358,23 +333,19 @@
   s.set_name("Uniforms");
   auto alias = std::make_unique<ast::type::AliasType>("Uniforms", &s);
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-
   auto coord_var =
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "uniforms", ast::StorageClass::kUniform, alias.get()));
 
-  mod.AddAliasType(alias.get());
+  mod()->AddAliasType(alias.get());
 
   ast::VariableDecorationList decos;
   decos.push_back(std::make_unique<ast::BindingDecoration>(0));
   decos.push_back(std::make_unique<ast::SetDecoration>(1));
   coord_var->set_decorations(std::move(decos));
 
-  td.RegisterVariableForTesting(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
   ast::VariableList params;
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
@@ -393,17 +364,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct Uniforms {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct Uniforms {
   vector<float, 4> coord;
 };
 
@@ -417,7 +386,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        Emit_Function_EntryPoint_With_StorageBuffer_Read) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -449,11 +418,8 @@
   decos.push_back(std::make_unique<ast::SetDecoration>(1));
   coord_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
   ast::VariableList params;
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
@@ -470,17 +436,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(RWByteAddressBuffer coord : register(u0);
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(RWByteAddressBuffer coord : register(u0);
 
 void frag_main() {
   float v = asfloat(coord.Load(4));
@@ -490,7 +454,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        Emit_Function_EntryPoint_With_StorageBuffer_Store) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -522,12 +486,9 @@
   decos.push_back(std::make_unique<ast::SetDecoration>(1));
   coord_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
+  td().RegisterVariableForTesting(coord_var.get());
 
-  mod.AddGlobalVariable(std::move(coord_var));
+  mod()->AddGlobalVariable(std::move(coord_var));
 
   ast::VariableList params;
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
@@ -545,17 +506,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(RWByteAddressBuffer coord : register(u0);
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(RWByteAddressBuffer coord : register(u0);
 
 void frag_main() {
   coord.Store(4, asuint(2.00000000f));
@@ -565,7 +524,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        Emit_Function_Called_By_EntryPoints_WithLocationGlobals_And_Params) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -587,16 +546,13 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(0));
   val_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
-  td.RegisterVariableForTesting(val_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(val_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
-  mod.AddGlobalVariable(std::move(val_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(val_var));
 
   ast::VariableList params;
   params.push_back(std::make_unique<ast::Variable>(
@@ -615,7 +571,7 @@
       std::make_unique<ast::IdentifierExpression>("foo")));
   sub_func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(sub_func));
+  mod()->AddFunction(std::move(sub_func));
 
   auto func_1 = std::make_unique<ast::Function>("frag_1_main",
                                                 std::move(params), &void_type);
@@ -633,17 +589,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func_1->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func_1));
+  mod()->AddFunction(std::move(func_1));
 
   auto ep1 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_1", "frag_1_main");
-  mod.AddEntryPoint(std::move(ep1));
+  mod()->AddEntryPoint(std::move(ep1));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct ep_1_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct ep_1_in {
   float foo : TEXCOORD0;
 };
 
@@ -667,7 +621,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        Emit_Function_Called_By_EntryPoints_NoUsedGlobals) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -682,12 +636,9 @@
       std::make_unique<ast::BuiltinDecoration>(ast::Builtin::kFragDepth));
   depth_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(depth_var.get());
+  td().RegisterVariableForTesting(depth_var.get());
 
-  mod.AddGlobalVariable(std::move(depth_var));
+  mod()->AddGlobalVariable(std::move(depth_var));
 
   ast::VariableList params;
   params.push_back(std::make_unique<ast::Variable>(
@@ -700,7 +651,7 @@
       std::make_unique<ast::IdentifierExpression>("param")));
   sub_func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(sub_func));
+  mod()->AddFunction(std::move(sub_func));
 
   auto func_1 = std::make_unique<ast::Function>("frag_1_main",
                                                 std::move(params), &void_type);
@@ -718,17 +669,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func_1->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func_1));
+  mod()->AddFunction(std::move(func_1));
 
   auto ep1 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_1", "frag_1_main");
-  mod.AddEntryPoint(std::move(ep1));
+  mod()->AddEntryPoint(std::move(ep1));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct ep_1_out {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct ep_1_out {
   float depth : SV_Depth;
 };
 
@@ -745,7 +694,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        Emit_Function_Called_By_EntryPoints_WithBuiltinGlobals_And_Params) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -767,14 +716,11 @@
       std::make_unique<ast::BuiltinDecoration>(ast::Builtin::kFragDepth));
   depth_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  td.RegisterVariableForTesting(depth_var.get());
+  td().RegisterVariableForTesting(coord_var.get());
+  td().RegisterVariableForTesting(depth_var.get());
 
-  mod.AddGlobalVariable(std::move(coord_var));
-  mod.AddGlobalVariable(std::move(depth_var));
+  mod()->AddGlobalVariable(std::move(coord_var));
+  mod()->AddGlobalVariable(std::move(depth_var));
 
   ast::VariableList params;
   params.push_back(std::make_unique<ast::Variable>(
@@ -792,7 +738,7 @@
       std::make_unique<ast::IdentifierExpression>("param")));
   sub_func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(sub_func));
+  mod()->AddFunction(std::move(sub_func));
 
   auto func_1 = std::make_unique<ast::Function>("frag_1_main",
                                                 std::move(params), &void_type);
@@ -810,17 +756,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func_1->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func_1));
+  mod()->AddFunction(std::move(func_1));
 
   auto ep1 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_1", "frag_1_main");
-  mod.AddEntryPoint(std::move(ep1));
+  mod()->AddEntryPoint(std::move(ep1));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct ep_1_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct ep_1_in {
   vector<float, 4> coord : SV_Position;
 };
 
@@ -842,7 +786,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        DISABLED_Emit_Function_Called_By_EntryPoint_With_Uniform) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -857,12 +801,9 @@
   decos.push_back(std::make_unique<ast::SetDecoration>(1));
   coord_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
+  td().RegisterVariableForTesting(coord_var.get());
 
-  mod.AddGlobalVariable(std::move(coord_var));
+  mod()->AddGlobalVariable(std::move(coord_var));
 
   ast::VariableList params;
   params.push_back(std::make_unique<ast::Variable>(
@@ -877,7 +818,7 @@
           std::make_unique<ast::IdentifierExpression>("x"))));
   sub_func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(sub_func));
+  mod()->AddFunction(std::move(sub_func));
 
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
                                               &void_type);
@@ -897,20 +838,18 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"( ... )");
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"( ... )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        DISABLED_Emit_Function_Called_By_EntryPoint_With_StorageBuffer) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -925,12 +864,9 @@
   decos.push_back(std::make_unique<ast::SetDecoration>(1));
   coord_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(coord_var.get());
+  td().RegisterVariableForTesting(coord_var.get());
 
-  mod.AddGlobalVariable(std::move(coord_var));
+  mod()->AddGlobalVariable(std::move(coord_var));
 
   ast::VariableList params;
   params.push_back(std::make_unique<ast::Variable>(
@@ -945,7 +881,7 @@
           std::make_unique<ast::IdentifierExpression>("x"))));
   sub_func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(sub_func));
+  mod()->AddFunction(std::move(sub_func));
 
   auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
                                               &void_type);
@@ -965,20 +901,18 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
 
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
                                               "frag_main");
-  mod.AddEntryPoint(std::move(ep));
+  mod()->AddEntryPoint(std::move(ep));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"( ... )");
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"( ... )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        DISABLED_Emit_Function_Called_Two_EntryPoints_WithGlobals) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -995,14 +929,11 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(foo_var.get());
-  td.RegisterVariableForTesting(bar_var.get());
+  td().RegisterVariableForTesting(foo_var.get());
+  td().RegisterVariableForTesting(bar_var.get());
 
-  mod.AddGlobalVariable(std::move(foo_var));
-  mod.AddGlobalVariable(std::move(bar_var));
+  mod()->AddGlobalVariable(std::move(foo_var));
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto sub_func =
@@ -1016,7 +947,7 @@
       std::make_unique<ast::IdentifierExpression>("foo")));
   sub_func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(sub_func));
+  mod()->AddFunction(std::move(sub_func));
 
   auto func_1 = std::make_unique<ast::Function>("frag_1_main",
                                                 std::move(params), &void_type);
@@ -1030,20 +961,18 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func_1->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func_1));
+  mod()->AddFunction(std::move(func_1));
 
   auto ep1 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_1", "frag_1_main");
   auto ep2 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_2", "frag_1_main");
-  mod.AddEntryPoint(std::move(ep1));
-  mod.AddEntryPoint(std::move(ep2));
+  mod()->AddEntryPoint(std::move(ep1));
+  mod()->AddEntryPoint(std::move(ep2));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct ep_1_in {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct ep_1_in {
   float foo : TEXCOORD0;
 };
 
@@ -1084,7 +1013,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        DISABLED_Emit_Function_EntryPoints_WithGlobal_Nested_Return) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
@@ -1096,11 +1025,8 @@
   decos.push_back(std::make_unique<ast::LocationDecoration>(1));
   bar_var->set_decorations(std::move(decos));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  td.RegisterVariableForTesting(bar_var.get());
-  mod.AddGlobalVariable(std::move(bar_var));
+  td().RegisterVariableForTesting(bar_var.get());
+  mod()->AddGlobalVariable(std::move(bar_var));
 
   ast::VariableList params;
   auto func_1 = std::make_unique<ast::Function>("frag_1_main",
@@ -1127,17 +1053,15 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func_1->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func_1));
+  mod()->AddFunction(std::move(func_1));
 
   auto ep1 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_1", "frag_1_main");
-  mod.AddEntryPoint(std::move(ep1));
+  mod()->AddEntryPoint(std::move(ep1));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(struct ep_1_out {
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(struct ep_1_out {
   float bar : SV_Target0;
 };
 
@@ -1153,15 +1077,11 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_Function,
        Emit_Function_Called_Two_EntryPoints_WithoutGlobals) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-
   ast::VariableList params;
   auto sub_func =
       std::make_unique<ast::Function>("sub_func", std::move(params), &f32);
@@ -1172,7 +1092,7 @@
           std::make_unique<ast::FloatLiteral>(&f32, 1.0))));
   sub_func->set_body(std::move(body));
 
-  mod.AddFunction(std::move(sub_func));
+  mod()->AddFunction(std::move(sub_func));
 
   auto func_1 = std::make_unique<ast::Function>("frag_1_main",
                                                 std::move(params), &void_type);
@@ -1188,20 +1108,19 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func_1->set_body(std::move(body));
 
-  mod.AddFunction(std::move(func_1));
+  mod()->AddFunction(std::move(func_1));
 
   auto ep1 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_1", "frag_1_main");
   auto ep2 = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment,
                                                "ep_2", "frag_1_main");
-  mod.AddEntryPoint(std::move(ep1));
-  mod.AddEntryPoint(std::move(ep2));
+  mod()->AddEntryPoint(std::move(ep1));
+  mod()->AddEntryPoint(std::move(ep2));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(float sub_func() {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(float sub_func() {
   return 1.00000000f;
 }
 
@@ -1217,7 +1136,7 @@
 
 )");
 }
-TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_WithName) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_EntryPoint_WithName) {
   ast::type::VoidType void_type;
 
   auto func = std::make_unique<ast::Function>("comp_main", ast::VariableList{},
@@ -1225,19 +1144,18 @@
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kCompute,
                                               "my_main", "comp_main");
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
-  m.AddEntryPoint(std::move(ep));
+  mod()->AddFunction(std::move(func));
+  mod()->AddEntryPoint(std::move(ep));
 
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(void my_main() {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(void my_main() {
 }
 
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_WithNameCollision) {
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_Function_EntryPoint_WithNameCollision) {
   ast::type::VoidType void_type;
 
   auto func = std::make_unique<ast::Function>("comp_main", ast::VariableList{},
@@ -1245,19 +1163,17 @@
   auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kCompute,
                                               "GeometryShader", "comp_main");
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
-  m.AddEntryPoint(std::move(ep));
+  mod()->AddFunction(std::move(func));
+  mod()->AddEntryPoint(std::move(ep));
 
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(void GeometryShader_tint_0() {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(void GeometryShader_tint_0() {
 }
 
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_Function_WithArrayParams) {
+TEST_F(HlslGeneratorImplTest_Function, Emit_Function_WithArrayParams) {
   ast::type::F32Type f32;
   ast::type::ArrayType ary(&f32, 5);
 
@@ -1273,14 +1189,11 @@
   body->append(std::make_unique<ast::ReturnStatement>());
   func->set_body(std::move(body));
 
-  ast::Module m;
-  m.AddFunction(std::move(func));
+  mod()->AddFunction(std::move(func));
+  gen().increment_indent();
 
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(  void my_func(float a[5]) {
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(  void my_func(float a[5]) {
     return;
   }
 
diff --git a/src/writer/hlsl/generator_impl_identifier_test.cc b/src/writer/hlsl/generator_impl_identifier_test.cc
index 7f45efd..cdd99e7 100644
--- a/src/writer/hlsl/generator_impl_identifier_test.cc
+++ b/src/writer/hlsl/generator_impl_identifier_test.cc
@@ -12,53 +12,43 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Identifier : public TestHelper,
+                                         public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitExpression_Identifier) {
+TEST_F(HlslGeneratorImplTest_Identifier, DISABLED_EmitExpression_Identifier) {
   ast::IdentifierExpression i(std::vector<std::string>{"std", "glsl"});
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&i)) << g.error();
-  EXPECT_EQ(g.result(), "std::glsl");
+  ASSERT_TRUE(gen().EmitExpression(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), "std::glsl");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitIdentifierExpression_Single) {
+TEST_F(HlslGeneratorImplTest_Identifier, EmitIdentifierExpression_Single) {
   ast::IdentifierExpression i("foo");
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&i)) << g.error();
-  EXPECT_EQ(g.result(), "foo");
+  ASSERT_TRUE(gen().EmitExpression(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), "foo");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitIdentifierExpression_Single_WithCollision) {
+TEST_F(HlslGeneratorImplTest_Identifier,
+       EmitIdentifierExpression_Single_WithCollision) {
   ast::IdentifierExpression i("virtual");
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&i)) << g.error();
-  EXPECT_EQ(g.result(), "virtual_tint_0");
+  ASSERT_TRUE(gen().EmitExpression(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), "virtual_tint_0");
 }
 
 // TODO(dsinclair): Handle import names
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitIdentifierExpression_MultipleNames) {
+TEST_F(HlslGeneratorImplTest_Identifier,
+       DISABLED_EmitIdentifierExpression_MultipleNames) {
   ast::IdentifierExpression i({"std", "glsl", "init"});
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&i)) << g.error();
-  EXPECT_EQ(g.result(), "std::glsl::init");
+  ASSERT_TRUE(gen().EmitExpression(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), "std::glsl::init");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_if_test.cc b/src/writer/hlsl/generator_impl_if_test.cc
index f133304..cfdc393 100644
--- a/src/writer/hlsl/generator_impl_if_test.cc
+++ b/src/writer/hlsl/generator_impl_if_test.cc
@@ -12,40 +12,36 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/else_statement.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/module.h"
 #include "src/ast/return_statement.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_If : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_If) {
+TEST_F(HlslGeneratorImplTest_If, Emit_If) {
   auto cond = std::make_unique<ast::IdentifierExpression>("cond");
   auto body = std::make_unique<ast::BlockStatement>();
   body->append(std::make_unique<ast::ReturnStatement>());
 
   ast::IfStatement i(std::move(cond), std::move(body));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&i)) << g.error();
-  EXPECT_EQ(g.result(), R"(  if (cond) {
+  ASSERT_TRUE(gen().EmitStatement(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), R"(  if (cond) {
     return;
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_IfWithElseIf) {
+TEST_F(HlslGeneratorImplTest_If, Emit_IfWithElseIf) {
   auto else_cond = std::make_unique<ast::IdentifierExpression>("else_cond");
   auto else_body = std::make_unique<ast::BlockStatement>();
   else_body->append(std::make_unique<ast::ReturnStatement>());
@@ -61,12 +57,10 @@
   ast::IfStatement i(std::move(cond), std::move(body));
   i.set_else_statements(std::move(elses));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&i)) << g.error();
-  EXPECT_EQ(g.result(), R"(  if (cond) {
+  ASSERT_TRUE(gen().EmitStatement(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), R"(  if (cond) {
     return;
   } else if (else_cond) {
     return;
@@ -74,7 +68,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_IfWithElse) {
+TEST_F(HlslGeneratorImplTest_If, Emit_IfWithElse) {
   auto else_body = std::make_unique<ast::BlockStatement>();
   else_body->append(std::make_unique<ast::ReturnStatement>());
 
@@ -88,12 +82,10 @@
   ast::IfStatement i(std::move(cond), std::move(body));
   i.set_else_statements(std::move(elses));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&i)) << g.error();
-  EXPECT_EQ(g.result(), R"(  if (cond) {
+  ASSERT_TRUE(gen().EmitStatement(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), R"(  if (cond) {
     return;
   } else {
     return;
@@ -101,7 +93,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_IfWithMultiple) {
+TEST_F(HlslGeneratorImplTest_If, Emit_IfWithMultiple) {
   auto else_cond = std::make_unique<ast::IdentifierExpression>("else_cond");
 
   auto else_body = std::make_unique<ast::BlockStatement>();
@@ -122,12 +114,10 @@
   ast::IfStatement i(std::move(cond), std::move(body));
   i.set_else_statements(std::move(elses));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
+  gen().increment_indent();
 
-  ASSERT_TRUE(g.EmitStatement(&i)) << g.error();
-  EXPECT_EQ(g.result(), R"(  if (cond) {
+  ASSERT_TRUE(gen().EmitStatement(out(), &i)) << gen().error();
+  EXPECT_EQ(result(), R"(  if (cond) {
     return;
   } else if (else_cond) {
     return;
diff --git a/src/writer/hlsl/generator_impl_import_test.cc b/src/writer/hlsl/generator_impl_import_test.cc
index 5ebfd24..a6c5631 100644
--- a/src/writer/hlsl/generator_impl_import_test.cc
+++ b/src/writer/hlsl/generator_impl_import_test.cc
@@ -16,7 +16,6 @@
 #include <string>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/float_literal.h"
 #include "src/ast/identifier_expression.h"
@@ -30,14 +29,14 @@
 #include "src/ast/type_constructor_expression.h"
 #include "src/context.h"
 #include "src/type_determiner.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Import : public TestHelper, public testing::Test {};
 
 struct HlslImportData {
   const char* name;
@@ -47,7 +46,10 @@
   out << data.name;
   return out;
 }
-using HlslImportData_SingleParamTest = testing::TestWithParam<HlslImportData>;
+
+class HlslImportData_SingleParamTest
+    : public TestHelper,
+      public testing::TestWithParam<HlslImportData> {};
 TEST_P(HlslImportData_SingleParamTest, FloatScalar) {
   auto param = GetParam();
 
@@ -61,19 +63,14 @@
                                std::vector<std::string>{"std", param.name}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(), std::string(param.hlsl_name) + "(1.00000000f)");
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1.00000000f)");
 }
 INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest,
+    HlslGeneratorImplTest_Import,
     HlslImportData_SingleParamTest,
     testing::Values(HlslImportData{"acos", "acos"},
                     HlslImportData{"asin", "asin"},
@@ -104,20 +101,21 @@
                     HlslImportData{"tanh", "tanh"},
                     HlslImportData{"trunc", "trunc"}));
 
-TEST_F(HlslGeneratorImplTest, DISABLED_HlslImportData_Acosh) {
+TEST_F(HlslGeneratorImplTest_Import, DISABLED_HlslImportData_Acosh) {
   FAIL();
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_HlslImportData_ASinh) {
+TEST_F(HlslGeneratorImplTest_Import, DISABLED_HlslImportData_ASinh) {
   FAIL();
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_HlslImportData_ATanh) {
+TEST_F(HlslGeneratorImplTest_Import, DISABLED_HlslImportData_ATanh) {
   FAIL();
 }
 
-using HlslImportData_SingleIntParamTest =
-    testing::TestWithParam<HlslImportData>;
+class HlslImportData_SingleIntParamTest
+    : public TestHelper,
+      public testing::TestWithParam<HlslImportData> {};
 TEST_P(HlslImportData_SingleIntParamTest, IntScalar) {
   auto param = GetParam();
 
@@ -131,23 +129,20 @@
                                std::vector<std::string>{"std", param.name}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(), std::string(param.hlsl_name) + "(1)");
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1)");
 }
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_SingleIntParamTest,
                          testing::Values(HlslImportData{"sabs", "abs"},
                                          HlslImportData{"ssign", "sign"}));
 
-using HlslImportData_DualParamTest = testing::TestWithParam<HlslImportData>;
+class HlslImportData_DualParamTest
+    : public TestHelper,
+      public testing::TestWithParam<HlslImportData> {};
 TEST_P(HlslImportData_DualParamTest, FloatScalar) {
   auto param = GetParam();
 
@@ -163,19 +158,14 @@
                                std::vector<std::string>{"std", param.name}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(),
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(),
             std::string(param.hlsl_name) + "(1.00000000f, 2.00000000f)");
 }
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_DualParamTest,
                          testing::Values(HlslImportData{"atan2", "atan2"},
                                          HlslImportData{"distance", "distance"},
@@ -187,8 +177,9 @@
                                          HlslImportData{"reflect", "reflect"},
                                          HlslImportData{"step", "step"}));
 
-using HlslImportData_DualParam_VectorTest =
-    testing::TestWithParam<HlslImportData>;
+class HlslImportData_DualParam_VectorTest
+    : public TestHelper,
+      public testing::TestWithParam<HlslImportData> {};
 TEST_P(HlslImportData_DualParam_VectorTest, FloatVector) {
   auto param = GetParam();
 
@@ -220,26 +211,22 @@
                                std::vector<std::string>{"std", param.name}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(),
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(),
             std::string(param.hlsl_name) +
                 "(vector<float, 3>(1.00000000f, 2.00000000f, 3.00000000f), "
                 "vector<float, 3>(4.00000000f, 5.00000000f, 6.00000000f))");
 }
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_DualParam_VectorTest,
                          testing::Values(HlslImportData{"cross", "cross"}));
 
-using HlslImportData_DualParam_Int_Test =
-    testing::TestWithParam<HlslImportData>;
+class HlslImportData_DualParam_Int_Test
+    : public TestHelper,
+      public testing::TestWithParam<HlslImportData> {};
 TEST_P(HlslImportData_DualParam_Int_Test, IntScalar) {
   auto param = GetParam();
 
@@ -255,25 +242,22 @@
                                std::vector<std::string>{"std", param.name}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(), std::string(param.hlsl_name) + "(1, 2)");
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1, 2)");
 }
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_DualParam_Int_Test,
                          testing::Values(HlslImportData{"smax", "max"},
                                          HlslImportData{"smin", "min"},
                                          HlslImportData{"umax", "max"},
                                          HlslImportData{"umin", "min"}));
 
-using HlslImportData_TripleParamTest = testing::TestWithParam<HlslImportData>;
+class HlslImportData_TripleParamTest
+    : public TestHelper,
+      public testing::TestWithParam<HlslImportData> {};
 TEST_P(HlslImportData_TripleParamTest, FloatScalar) {
   auto param = GetParam();
 
@@ -291,20 +275,15 @@
                                std::vector<std::string>{"std", param.name}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(), std::string(param.hlsl_name) +
-                            "(1.00000000f, 2.00000000f, 3.00000000f)");
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), std::string(param.hlsl_name) +
+                          "(1.00000000f, 2.00000000f, 3.00000000f)");
 }
 INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest,
+    HlslGeneratorImplTest_Import,
     HlslImportData_TripleParamTest,
     testing::Values(HlslImportData{"faceforward", "faceforward"},
                     HlslImportData{"fma", "fma"},
@@ -312,12 +291,13 @@
                     HlslImportData{"nclamp", "clamp"},
                     HlslImportData{"smoothstep", "smoothstep"}));
 
-TEST_F(HlslGeneratorImplTest, DISABLED_HlslImportData_FMix) {
+TEST_F(HlslGeneratorImplTest_Import, DISABLED_HlslImportData_FMix) {
   FAIL();
 }
 
-using HlslImportData_TripleParam_Int_Test =
-    testing::TestWithParam<HlslImportData>;
+class HlslImportData_TripleParam_Int_Test
+    : public TestHelper,
+      public testing::TestWithParam<HlslImportData> {};
 TEST_P(HlslImportData_TripleParam_Int_Test, IntScalar) {
   auto param = GetParam();
 
@@ -335,23 +315,18 @@
                                std::vector<std::string>{"std", param.name}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(), std::string(param.hlsl_name) + "(1, 2, 3)");
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1, 2, 3)");
 }
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_TripleParam_Int_Test,
                          testing::Values(HlslImportData{"sclamp", "clamp"},
                                          HlslImportData{"uclamp", "clamp"}));
 
-TEST_F(HlslGeneratorImplTest, HlslImportData_Determinant) {
+TEST_F(HlslGeneratorImplTest_Import, HlslImportData_Determinant) {
   ast::type::F32Type f32;
   ast::type::MatrixType mat(&f32, 3, 3);
 
@@ -365,19 +340,14 @@
                                std::vector<std::string>{"std", "determinant"}),
                            std::move(params));
 
-  Context ctx;
-  ast::Module mod;
-  mod.AddGlobalVariable(std::move(var));
-  mod.AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
+  mod()->AddGlobalVariable(std::move(var));
+  mod()->AddImport(std::make_unique<ast::Import>("GLSL.std.450", "std"));
 
-  TypeDeterminer td(&ctx, &mod);
   // Register the global
-  ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitImportFunction(&expr)) << g.error();
-  EXPECT_EQ(g.result(), std::string("determinant(var)"));
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitImportFunction(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), std::string("determinant(var)"));
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_intrinsic_test.cc b/src/writer/hlsl/generator_impl_intrinsic_test.cc
index d485b37..845628e 100644
--- a/src/writer/hlsl/generator_impl_intrinsic_test.cc
+++ b/src/writer/hlsl/generator_impl_intrinsic_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
@@ -20,14 +19,15 @@
 #include "src/ast/type/vector_type.h"
 #include "src/context.h"
 #include "src/type_determiner.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Intrinsic : public TestHelper,
+                                        public testing::Test {};
 
 struct IntrinsicData {
   const char* name;
@@ -37,16 +37,14 @@
   out << data.name;
   return out;
 }
-using HlslIntrinsicTest = testing::TestWithParam<IntrinsicData>;
+class HlslIntrinsicTest : public TestHelper,
+                          public testing::TestWithParam<IntrinsicData> {};
 TEST_P(HlslIntrinsicTest, Emit) {
   auto param = GetParam();
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  EXPECT_EQ(g.generate_intrinsic_name(param.name), param.hlsl_name);
+  EXPECT_EQ(gen().generate_intrinsic_name(param.name), param.hlsl_name);
 }
 INSTANTIATE_TEST_SUITE_P(
-    HlslGeneratorImplTest,
+    HlslGeneratorImplTest_Intrinsic,
     HlslIntrinsicTest,
     testing::Values(IntrinsicData{"any", "any"},
                     IntrinsicData{"all", "all"},
@@ -64,15 +62,15 @@
                     IntrinsicData{"is_inf", "isinf"},
                     IntrinsicData{"is_nan", "isnan"}));
 
-TEST_F(HlslGeneratorImplTest, DISABLED_Intrinsic_IsNormal) {
+TEST_F(HlslGeneratorImplTest_Intrinsic, DISABLED_Intrinsic_IsNormal) {
   FAIL();
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_Intrinsic_Select) {
+TEST_F(HlslGeneratorImplTest_Intrinsic, DISABLED_Intrinsic_Select) {
   FAIL();
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_Intrinsic_OuterProduct) {
+TEST_F(HlslGeneratorImplTest_Intrinsic, DISABLED_Intrinsic_OuterProduct) {
   ast::type::F32Type f32;
   ast::type::VectorType vec2(&f32, 2);
   ast::type::VectorType vec3(&f32, 3);
@@ -90,32 +88,25 @@
       std::make_unique<ast::IdentifierExpression>("outer_product"),
       std::move(params));
 
-  Context ctx;
-  ast::Module m;
-  TypeDeterminer td(&ctx, &m);
-  td.RegisterVariableForTesting(a.get());
-  td.RegisterVariableForTesting(b.get());
+  td().RegisterVariableForTesting(a.get());
+  td().RegisterVariableForTesting(b.get());
 
-  m.AddGlobalVariable(std::move(a));
-  m.AddGlobalVariable(std::move(b));
+  mod()->AddGlobalVariable(std::move(a));
+  mod()->AddGlobalVariable(std::move(b));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&call)) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(td().DetermineResultType(&call)) << td().error();
 
-  GeneratorImpl g(&m);
-
-  g.increment_indent();
-  ASSERT_TRUE(g.EmitExpression(&call)) << g.error();
-  EXPECT_EQ(g.result(), "  float3x2(a * b[0], a * b[1], a * b[2])");
+  gen().increment_indent();
+  ASSERT_TRUE(gen().EmitExpression(out(), &call)) << gen().error();
+  EXPECT_EQ(result(), "  float3x2(a * b[0], a * b[1], a * b[2])");
 }
 
-TEST_F(HlslGeneratorImplTest, Intrinsic_Bad_Name) {
-  ast::Module m;
-  GeneratorImpl g(&m);
-  EXPECT_EQ(g.generate_intrinsic_name("unknown name"), "");
+TEST_F(HlslGeneratorImplTest_Intrinsic, Intrinsic_Bad_Name) {
+  EXPECT_EQ(gen().generate_intrinsic_name("unknown name"), "");
 }
 
-TEST_F(HlslGeneratorImplTest, Intrinsic_Call) {
+TEST_F(HlslGeneratorImplTest_Intrinsic, Intrinsic_Call) {
   ast::ExpressionList params;
   params.push_back(std::make_unique<ast::IdentifierExpression>("param1"));
   params.push_back(std::make_unique<ast::IdentifierExpression>("param2"));
@@ -123,11 +114,9 @@
   ast::CallExpression call(std::make_unique<ast::IdentifierExpression>("dot"),
                            std::move(params));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-  ASSERT_TRUE(g.EmitExpression(&call)) << g.error();
-  EXPECT_EQ(g.result(), "  dot(param1, param2)");
+  gen().increment_indent();
+  ASSERT_TRUE(gen().EmitExpression(out(), &call)) << gen().error();
+  EXPECT_EQ(result(), "  dot(param1, param2)");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_loop_test.cc b/src/writer/hlsl/generator_impl_loop_test.cc
index 1f9dabf..8a92b1c 100644
--- a/src/writer/hlsl/generator_impl_loop_test.cc
+++ b/src/writer/hlsl/generator_impl_loop_test.cc
@@ -14,7 +14,6 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/discard_statement.h"
 #include "src/ast/float_literal.h"
@@ -25,33 +24,30 @@
 #include "src/ast/type/f32_type.h"
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Loop : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Loop) {
+TEST_F(HlslGeneratorImplTest_Loop, Emit_Loop) {
   auto body = std::make_unique<ast::BlockStatement>();
   body->append(std::make_unique<ast::DiscardStatement>());
 
   ast::LoopStatement l(std::move(body), {});
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&l)) << g.error();
-  EXPECT_EQ(g.result(), R"(  for(;;) {
+  ASSERT_TRUE(gen().EmitStatement(out(), &l)) << gen().error();
+  EXPECT_EQ(result(), R"(  for(;;) {
     discard;
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_LoopWithContinuing) {
+TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) {
   auto body = std::make_unique<ast::BlockStatement>();
   body->append(std::make_unique<ast::DiscardStatement>());
 
@@ -59,13 +55,10 @@
   continuing->append(std::make_unique<ast::ReturnStatement>());
 
   ast::LoopStatement l(std::move(body), std::move(continuing));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&l)) << g.error();
-  EXPECT_EQ(g.result(), R"(  {
+  ASSERT_TRUE(gen().EmitStatement(out(), &l)) << gen().error();
+  EXPECT_EQ(result(), R"(  {
     bool tint_hlsl_is_first_1 = true;
     for(;;) {
       if (!tint_hlsl_is_first_1) {
@@ -79,7 +72,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_LoopNestedWithContinuing) {
+TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) {
   ast::type::F32Type f32;
 
   auto body = std::make_unique<ast::BlockStatement>();
@@ -102,13 +95,10 @@
       std::move(lhs), std::move(rhs)));
 
   ast::LoopStatement outer(std::move(body), std::move(continuing));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&outer)) << g.error();
-  EXPECT_EQ(g.result(), R"(  {
+  ASSERT_TRUE(gen().EmitStatement(out(), &outer)) << gen().error();
+  EXPECT_EQ(result(), R"(  {
     bool tint_hlsl_is_first_1 = true;
     for(;;) {
       if (!tint_hlsl_is_first_1) {
@@ -134,7 +124,7 @@
 
 // TODO(dsinclair): Handle pulling declared variables up and out of the for() if
 // there is a continuing block.
-TEST_F(HlslGeneratorImplTest, DISABLED_Emit_LoopWithVarUsedInContinuing) {
+TEST_F(HlslGeneratorImplTest_Loop, DISABLED_Emit_LoopWithVarUsedInContinuing) {
   ast::type::F32Type f32;
 
   auto var = std::make_unique<ast::Variable>(
@@ -156,13 +146,10 @@
       std::move(lhs), std::move(rhs)));
 
   ast::LoopStatement outer(std::move(body), std::move(continuing));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&outer)) << g.error();
-  EXPECT_EQ(g.result(), R"(  {
+  ASSERT_TRUE(gen().EmitStatement(out(), &outer)) << gen().error();
+  EXPECT_EQ(result(), R"(  {
     float lhs;
     bool tint_hlsl_is_first_1 = true;
     for(;;) {
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
index 88e3660..34be490 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -14,7 +14,6 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/array_accessor_expression.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/binary_expression.h"
@@ -36,16 +35,17 @@
 #include "src/ast/type_constructor_expression.h"
 #include "src/context.h"
 #include "src/type_determiner.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_MemberAccessor : public TestHelper,
+                                             public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitExpression_MemberAccessor) {
+TEST_F(HlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
   ast::type::F32Type f32;
 
   ast::StructMemberList members;
@@ -68,20 +68,16 @@
 
   ast::MemberAccessorExpression expr(std::move(str), std::move(mem));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(str_var.get());
-  g.register_global(str_var.get());
-  mod.AddGlobalVariable(std::move(str_var));
+  td().RegisterVariableForTesting(str_var.get());
+  gen().register_global(str_var.get());
+  mod()->AddGlobalVariable(std::move(str_var));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "str.mem");
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "str.mem");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load) {
   // struct Data {
   //   [[offset 0]] a : i32;
@@ -119,23 +115,18 @@
       std::make_unique<ast::IdentifierExpression>("data"),
       std::make_unique<ast::IdentifierExpression>("b"));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(td().DetermineResultType(&expr));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asfloat(data.Load(4))");
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asfloat(data.Load(4))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_Int) {
   // struct Data {
   //   [[offset 0]] a : i32;
@@ -173,22 +164,18 @@
       std::make_unique<ast::IdentifierExpression>("data"),
       std::make_unique<ast::IdentifierExpression>("a"));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&expr));
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(td().DetermineResultType(&expr));
 
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asint(data.Load(0))");
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asint(data.Load(0))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_ArrayAccessor_StorageBuffer_Load_Int_FromArray) {
   // struct Data {
   //   [[offset 0]] a : [[stride 4]] array<i32, 5>;
@@ -225,22 +212,18 @@
       std::make_unique<ast::ScalarConstructorExpression>(
           std::make_unique<ast::SintLiteral>(&i32, 2)));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
 
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asint(data.Load((4 * 2) + 0))");
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asint(data.Load((4 * 2) + 0))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_ArrayAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) {
   // struct Data {
   //   [[offset 0]] a : [[stride 4]] array<i32, 5>;
@@ -285,22 +268,18 @@
           std::make_unique<ast::ScalarConstructorExpression>(
               std::make_unique<ast::SintLiteral>(&i32, 3))));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
-  ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
+  ASSERT_TRUE(td().DetermineResultType(&expr)) << td().error();
 
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asint(data.Load((4 * ((2 + 4) - 3)) + 0))");
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asint(data.Load((4 * ((2 + 4) - 3)) + 0))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store) {
   // struct Data {
   //   [[offset 0]] a : i32;
@@ -335,15 +314,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &s));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   auto lhs = std::make_unique<ast::MemberAccessorExpression>(
       std::make_unique<ast::IdentifierExpression>("data"),
@@ -352,13 +327,13 @@
       std::make_unique<ast::FloatLiteral>(&f32, 2.0f));
   ast::AssignmentStatement assign(std::move(lhs), std::move(rhs));
 
-  ASSERT_TRUE(td.DetermineResultType(&assign));
-  ASSERT_TRUE(g.EmitStatement(&assign)) << g.error();
-  EXPECT_EQ(g.result(), R"(data.Store(4, asuint(2.00000000f));
+  ASSERT_TRUE(td().DetermineResultType(&assign));
+  ASSERT_TRUE(gen().EmitStatement(out(), &assign)) << gen().error();
+  EXPECT_EQ(result(), R"(data.Store(4, asuint(2.00000000f));
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_ToArray) {
   // struct Data {
   //   [[offset 0]] a : [[stride 4]] array<i32, 5>;
@@ -389,15 +364,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &s));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   auto lhs = std::make_unique<ast::ArrayAccessorExpression>(
       std::make_unique<ast::MemberAccessorExpression>(
@@ -409,13 +380,13 @@
       std::make_unique<ast::SintLiteral>(&i32, 2));
   ast::AssignmentStatement assign(std::move(lhs), std::move(rhs));
 
-  ASSERT_TRUE(td.DetermineResultType(&assign)) << td.error();
-  ASSERT_TRUE(g.EmitStatement(&assign)) << g.error();
-  EXPECT_EQ(g.result(), R"(data.Store((4 * 2) + 0, asuint(2));
+  ASSERT_TRUE(td().DetermineResultType(&assign)) << td().error();
+  ASSERT_TRUE(gen().EmitStatement(out(), &assign)) << gen().error();
+  EXPECT_EQ(result(), R"(data.Store((4 * 2) + 0, asuint(2));
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Int) {
   // struct Data {
   //   [[offset 0]] a : i32;
@@ -450,15 +421,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &s));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   auto lhs = std::make_unique<ast::MemberAccessorExpression>(
       std::make_unique<ast::IdentifierExpression>("data"),
@@ -467,13 +434,13 @@
       std::make_unique<ast::SintLiteral>(&i32, 2));
   ast::AssignmentStatement assign(std::move(lhs), std::move(rhs));
 
-  ASSERT_TRUE(td.DetermineResultType(&assign));
-  ASSERT_TRUE(g.EmitStatement(&assign)) << g.error();
-  EXPECT_EQ(g.result(), R"(data.Store(0, asuint(2));
+  ASSERT_TRUE(td().DetermineResultType(&assign));
+  ASSERT_TRUE(gen().EmitStatement(out(), &assign)) << gen().error();
+  EXPECT_EQ(result(), R"(data.Store(0, asuint(2));
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_Vec3) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -510,26 +477,22 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &s));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   ast::MemberAccessorExpression expr(
       std::make_unique<ast::IdentifierExpression>("data"),
       std::make_unique<ast::IdentifierExpression>("b"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asfloat(data.Load3(16))");
+  ASSERT_TRUE(td().DetermineResultType(&expr));
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asfloat(data.Load3(16))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Vec3) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -566,15 +529,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &s));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   auto lit1 = std::make_unique<ast::FloatLiteral>(&f32, 1.f);
   auto lit2 = std::make_unique<ast::FloatLiteral>(&f32, 2.f);
@@ -595,15 +554,15 @@
 
   ast::AssignmentStatement assign(std::move(lhs), std::move(rhs));
 
-  ASSERT_TRUE(td.DetermineResultType(&assign));
-  ASSERT_TRUE(g.EmitStatement(&assign)) << g.error();
+  ASSERT_TRUE(td().DetermineResultType(&assign));
+  ASSERT_TRUE(gen().EmitStatement(out(), &assign)) << gen().error();
   EXPECT_EQ(
-      g.result(),
+      result(),
       R"(data.Store3(16, asuint(vector<float, 3>(1.00000000f, 2.00000000f, 3.00000000f)));
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -656,15 +615,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &pre));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   ast::MemberAccessorExpression expr(
       std::make_unique<ast::ArrayAccessorExpression>(
@@ -675,12 +630,12 @@
               std::make_unique<ast::SintLiteral>(&i32, 2))),
       std::make_unique<ast::IdentifierExpression>("b"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asfloat(data.Load3(16 + (32 * 2) + 0))");
+  ASSERT_TRUE(td().DetermineResultType(&expr));
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asfloat(data.Load3(16 + (32 * 2) + 0))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel_Swizzle) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -733,15 +688,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &pre));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   ast::MemberAccessorExpression expr(
       std::make_unique<ast::MemberAccessorExpression>(
@@ -754,13 +705,13 @@
           std::make_unique<ast::IdentifierExpression>("b")),
       std::make_unique<ast::IdentifierExpression>("xy"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asfloat(data.Load3(16 + (32 * 2) + 0)).xy");
+  ASSERT_TRUE(td().DetermineResultType(&expr));
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asfloat(data.Load3(16 + (32 * 2) + 0)).xy");
 }
 
 TEST_F(
-    HlslGeneratorImplTest,
+    HlslGeneratorImplTest_MemberAccessor,
     EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -813,15 +764,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &pre));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   ast::MemberAccessorExpression expr(
       std::make_unique<ast::MemberAccessorExpression>(
@@ -834,12 +781,12 @@
           std::make_unique<ast::IdentifierExpression>("b")),
       std::make_unique<ast::IdentifierExpression>("g"));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asfloat(data.Load((4 * 1) + 16 + (32 * 2) + 0))");
+  ASSERT_TRUE(td().DetermineResultType(&expr));
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asfloat(data.Load((4 * 1) + 16 + (32 * 2) + 0))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel_Index) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -892,15 +839,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &pre));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   ast::ArrayAccessorExpression expr(
       std::make_unique<ast::MemberAccessorExpression>(
@@ -914,12 +857,12 @@
       std::make_unique<ast::ScalarConstructorExpression>(
           std::make_unique<ast::SintLiteral>(&i32, 1)));
 
-  ASSERT_TRUE(td.DetermineResultType(&expr));
-  ASSERT_TRUE(g.EmitExpression(&expr)) << g.error();
-  EXPECT_EQ(g.result(), "asfloat(data.Load((4 * 1) + 16 + (32 * 2) + 0))");
+  ASSERT_TRUE(td().DetermineResultType(&expr));
+  ASSERT_TRUE(gen().EmitExpression(out(), &expr)) << gen().error();
+  EXPECT_EQ(result(), "asfloat(data.Load((4 * 1) + 16 + (32 * 2) + 0))");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_MultiLevel) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -972,15 +915,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &pre));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   auto lhs = std::make_unique<ast::MemberAccessorExpression>(
       std::make_unique<ast::ArrayAccessorExpression>(
@@ -1007,15 +946,15 @@
 
   ast::AssignmentStatement assign(std::move(lhs), std::move(rhs));
 
-  ASSERT_TRUE(td.DetermineResultType(&assign));
-  ASSERT_TRUE(g.EmitStatement(&assign)) << g.error();
+  ASSERT_TRUE(td().DetermineResultType(&assign));
+  ASSERT_TRUE(gen().EmitStatement(out(), &assign)) << gen().error();
   EXPECT_EQ(
-      g.result(),
+      result(),
       R"(data.Store3(16 + (32 * 2) + 0, asuint(vector<float, 3>(1.00000000f, 2.00000000f, 3.00000000f)));
 )");
 }
 
-TEST_F(HlslGeneratorImplTest,
+TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Swizzle_SingleLetter) {
   // struct Data {
   //   [[offset 0]] a : vec3<i32>;
@@ -1068,15 +1007,11 @@
       std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
           "data", ast::StorageClass::kStorageBuffer, &pre));
 
-  Context ctx;
-  ast::Module mod;
-  TypeDeterminer td(&ctx, &mod);
-  GeneratorImpl g(&mod);
-  td.RegisterVariableForTesting(coord_var.get());
-  g.register_global(coord_var.get());
-  mod.AddGlobalVariable(std::move(coord_var));
+  td().RegisterVariableForTesting(coord_var.get());
+  gen().register_global(coord_var.get());
+  mod()->AddGlobalVariable(std::move(coord_var));
 
-  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(td().Determine()) << td().error();
 
   auto lhs = std::make_unique<ast::MemberAccessorExpression>(
       std::make_unique<ast::MemberAccessorExpression>(
@@ -1094,9 +1029,9 @@
 
   ast::AssignmentStatement assign(std::move(lhs), std::move(rhs));
 
-  ASSERT_TRUE(td.DetermineResultType(&assign));
-  ASSERT_TRUE(g.EmitStatement(&assign)) << g.error();
-  EXPECT_EQ(g.result(),
+  ASSERT_TRUE(td().DetermineResultType(&assign));
+  ASSERT_TRUE(gen().EmitStatement(out(), &assign)) << gen().error();
+  EXPECT_EQ(result(),
             R"(data.Store((4 * 1) + 16 + (32 * 2) + 0, asuint(1.00000000f));
 )");
 }
diff --git a/src/writer/hlsl/generator_impl_module_constant_test.cc b/src/writer/hlsl/generator_impl_module_constant_test.cc
index 74226e1..d787c23 100644
--- a/src/writer/hlsl/generator_impl_module_constant_test.cc
+++ b/src/writer/hlsl/generator_impl_module_constant_test.cc
@@ -15,7 +15,6 @@
 #include <memory>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/float_literal.h"
 #include "src/ast/module.h"
 #include "src/ast/scalar_constructor_expression.h"
@@ -23,16 +22,17 @@
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type_constructor_expression.h"
 #include "src/ast/variable.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_ModuleConstant : public TestHelper,
+                                             public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_ModuleConstant) {
+TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_ModuleConstant) {
   ast::type::F32Type f32;
   ast::type::ArrayType ary(&f32, 3);
 
@@ -50,11 +50,10 @@
   var->set_constructor(
       std::make_unique<ast::TypeConstructorExpression>(&ary, std::move(exprs)));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitProgramConstVariable(var.get())) << g.error();
+  ASSERT_TRUE(gen().EmitProgramConstVariable(out(), var.get()))
+      << gen().error();
   EXPECT_EQ(
-      g.result(),
+      result(),
       "static const float pos[3] = {1.00000000f, 2.00000000f, 3.00000000f};\n");
 }
 
diff --git a/src/writer/hlsl/generator_impl_return_test.cc b/src/writer/hlsl/generator_impl_return_test.cc
index f160d3b..2662bd2 100644
--- a/src/writer/hlsl/generator_impl_return_test.cc
+++ b/src/writer/hlsl/generator_impl_return_test.cc
@@ -15,40 +15,33 @@
 #include <memory>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/return_statement.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Return : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Return) {
+TEST_F(HlslGeneratorImplTest_Return, Emit_Return) {
   ast::ReturnStatement r;
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&r)) << g.error();
-  EXPECT_EQ(g.result(), "  return;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &r)) << gen().error();
+  EXPECT_EQ(result(), "  return;\n");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_ReturnWithValue) {
+TEST_F(HlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
   auto expr = std::make_unique<ast::IdentifierExpression>("expr");
   ast::ReturnStatement r(std::move(expr));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&r)) << g.error();
-  EXPECT_EQ(g.result(), "  return expr;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &r)) << gen().error();
+  EXPECT_EQ(result(), "  return expr;\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_switch_test.cc b/src/writer/hlsl/generator_impl_switch_test.cc
index 8648893..3b0b5bf 100644
--- a/src/writer/hlsl/generator_impl_switch_test.cc
+++ b/src/writer/hlsl/generator_impl_switch_test.cc
@@ -14,7 +14,6 @@
 
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/break_statement.h"
 #include "src/ast/case_statement.h"
 #include "src/ast/identifier_expression.h"
@@ -22,16 +21,16 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/type/i32_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Switch : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_Switch) {
+TEST_F(HlslGeneratorImplTest_Switch, Emit_Switch) {
   auto def = std::make_unique<ast::CaseStatement>();
   auto def_body = std::make_unique<ast::BlockStatement>();
   def_body->append(std::make_unique<ast::BreakStatement>());
@@ -53,13 +52,10 @@
 
   auto cond = std::make_unique<ast::IdentifierExpression>("cond");
   ast::SwitchStatement s(std::move(cond), std::move(body));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&s)) << g.error();
-  EXPECT_EQ(g.result(), R"(  switch(cond) {
+  ASSERT_TRUE(gen().EmitStatement(out(), &s)) << gen().error();
+  EXPECT_EQ(result(), R"(  switch(cond) {
     case 5: {
       break;
     }
diff --git a/src/writer/hlsl/generator_impl_test.cc b/src/writer/hlsl/generator_impl_test.cc
index b1b16db..5ddbeb0 100644
--- a/src/writer/hlsl/generator_impl_test.cc
+++ b/src/writer/hlsl/generator_impl_test.cc
@@ -12,35 +12,31 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/writer/hlsl/generator_impl.h"
-
 #include <memory>
 
-#include "gtest/gtest.h"
 #include "src/ast/entry_point.h"
 #include "src/ast/function.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/type/void_type.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest : public TestHelper, public testing::Test {};
 
 TEST_F(HlslGeneratorImplTest, DISABLED_Generate) {
   ast::type::VoidType void_type;
-  ast::Module m;
-  m.AddFunction(std::make_unique<ast::Function>("my_func", ast::VariableList{},
-                                                &void_type));
-  m.AddEntryPoint(std::make_unique<ast::EntryPoint>(
+  mod()->AddFunction(std::make_unique<ast::Function>(
+      "my_func", ast::VariableList{}, &void_type));
+  mod()->AddEntryPoint(std::make_unique<ast::EntryPoint>(
       ast::PipelineStage::kFragment, "my_func", ""));
 
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"(#import <metal_lib>
+  ASSERT_TRUE(gen().Generate(out())) << gen().error();
+  EXPECT_EQ(result(), R"(#import <metal_lib>
 
 void my_func() {
 }
@@ -48,30 +44,23 @@
 }
 
 TEST_F(HlslGeneratorImplTest, InputStructName) {
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_EQ(g.generate_name("func_main_in"), "func_main_in");
+  ASSERT_EQ(gen().generate_name("func_main_in"), "func_main_in");
 }
 
 TEST_F(HlslGeneratorImplTest, InputStructName_ConflictWithExisting) {
-  ast::Module m;
-  GeneratorImpl g(&m);
-
   // Register the struct name as existing.
-  auto* namer = g.namer_for_testing();
+  auto* namer = gen().namer_for_testing();
   namer->NameFor("func_main_out");
 
-  ASSERT_EQ(g.generate_name("func_main_out"), "func_main_out_0");
+  ASSERT_EQ(gen().generate_name("func_main_out"), "func_main_out_0");
 }
 
 TEST_F(HlslGeneratorImplTest, NameConflictWith_InputStructName) {
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_EQ(g.generate_name("func_main_in"), "func_main_in");
+  ASSERT_EQ(gen().generate_name("func_main_in"), "func_main_in");
 
   ast::IdentifierExpression ident("func_main_in");
-  ASSERT_TRUE(g.EmitIdentifier(&ident));
-  EXPECT_EQ(g.result(), "func_main_in_0");
+  ASSERT_TRUE(gen().EmitIdentifier(out(), &ident));
+  EXPECT_EQ(result(), "func_main_in_0");
 }
 
 struct HlslBuiltinData {
@@ -82,13 +71,12 @@
   out << data.builtin;
   return out;
 }
-using HlslBuiltinConversionTest = testing::TestWithParam<HlslBuiltinData>;
+class HlslBuiltinConversionTest
+    : public TestHelper,
+      public testing::TestWithParam<HlslBuiltinData> {};
 TEST_P(HlslBuiltinConversionTest, Emit) {
   auto params = GetParam();
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  EXPECT_EQ(g.builtin_to_attribute(params.builtin),
+  EXPECT_EQ(gen().builtin_to_attribute(params.builtin),
             std::string(params.attribute_name));
 }
 INSTANTIATE_TEST_SUITE_P(
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 6b27c00..96c464b 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
 #include "src/ast/module.h"
 #include "src/ast/struct.h"
 #include "src/ast/struct_decoration.h"
@@ -29,170 +28,142 @@
 #include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type/void_type.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_Type : public TestHelper, public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, EmitType_Alias) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Alias) {
   ast::type::F32Type f32;
   ast::type::AliasType alias("alias", &f32);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&alias, "")) << g.error();
-  EXPECT_EQ(g.result(), "alias");
+  ASSERT_TRUE(gen().EmitType(out(), &alias, "")) << gen().error();
+  EXPECT_EQ(result(), "alias");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Alias_NameCollision) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Alias_NameCollision) {
   ast::type::F32Type f32;
   ast::type::AliasType alias("bool", &f32);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&alias, "")) << g.error();
-  EXPECT_EQ(g.result(), "bool_tint_0");
+  ASSERT_TRUE(gen().EmitType(out(), &alias, "")) << gen().error();
+  EXPECT_EQ(result(), "bool_tint_0");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Array) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Array) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b, 4);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&a, "ary")) << g.error();
-  EXPECT_EQ(g.result(), "bool ary[4]");
+  ASSERT_TRUE(gen().EmitType(out(), &a, "ary")) << gen().error();
+  EXPECT_EQ(result(), "bool ary[4]");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_ArrayOfArray) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b, 4);
   ast::type::ArrayType c(&a, 5);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&c, "ary")) << g.error();
-  EXPECT_EQ(g.result(), "bool ary[5][4]");
+  ASSERT_TRUE(gen().EmitType(out(), &c, "ary")) << gen().error();
+  EXPECT_EQ(result(), "bool ary[5][4]");
 }
 
 // TODO(dsinclair): Is this possible? What order should it output in?
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
+TEST_F(HlslGeneratorImplTest_Type,
+       DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b, 4);
   ast::type::ArrayType c(&a, 5);
   ast::type::ArrayType d(&c);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&c, "ary")) << g.error();
-  EXPECT_EQ(g.result(), "bool ary[5][4][1]");
+  ASSERT_TRUE(gen().EmitType(out(), &c, "ary")) << gen().error();
+  EXPECT_EQ(result(), "bool ary[5][4][1]");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_ArrayOfArrayOfArray) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b, 4);
   ast::type::ArrayType c(&a, 5);
   ast::type::ArrayType d(&c, 6);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&d, "ary")) << g.error();
-  EXPECT_EQ(g.result(), "bool ary[6][5][4]");
+  ASSERT_TRUE(gen().EmitType(out(), &d, "ary")) << gen().error();
+  EXPECT_EQ(result(), "bool ary[6][5][4]");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Array_NameCollision) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_NameCollision) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b, 4);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&a, "bool")) << g.error();
-  EXPECT_EQ(g.result(), "bool bool_tint_0[4]");
+  ASSERT_TRUE(gen().EmitType(out(), &a, "bool")) << gen().error();
+  EXPECT_EQ(result(), "bool bool_tint_0[4]");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Array_WithoutName) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b, 4);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&a, "")) << g.error();
-  EXPECT_EQ(g.result(), "bool[4]");
+  ASSERT_TRUE(gen().EmitType(out(), &a, "")) << gen().error();
+  EXPECT_EQ(result(), "bool[4]");
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_RuntimeArray) {
+TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_RuntimeArray) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&a, "ary")) << g.error();
-  EXPECT_EQ(g.result(), "bool ary[]");
+  ASSERT_TRUE(gen().EmitType(out(), &a, "ary")) << gen().error();
+  EXPECT_EQ(result(), "bool ary[]");
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_RuntimeArray_NameCollision) {
+TEST_F(HlslGeneratorImplTest_Type,
+       DISABLED_EmitType_RuntimeArray_NameCollision) {
   ast::type::BoolType b;
   ast::type::ArrayType a(&b);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&a, "double")) << g.error();
-  EXPECT_EQ(g.result(), "bool double_tint_0[]");
+  ASSERT_TRUE(gen().EmitType(out(), &a, "double")) << gen().error();
+  EXPECT_EQ(result(), "bool double_tint_0[]");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Bool) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Bool) {
   ast::type::BoolType b;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&b, "")) << g.error();
-  EXPECT_EQ(g.result(), "bool");
+  ASSERT_TRUE(gen().EmitType(out(), &b, "")) << gen().error();
+  EXPECT_EQ(result(), "bool");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_F32) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_F32) {
   ast::type::F32Type f32;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&f32, "")) << g.error();
-  EXPECT_EQ(g.result(), "float");
+  ASSERT_TRUE(gen().EmitType(out(), &f32, "")) << gen().error();
+  EXPECT_EQ(result(), "float");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_I32) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_I32) {
   ast::type::I32Type i32;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&i32, "")) << g.error();
-  EXPECT_EQ(g.result(), "int");
+  ASSERT_TRUE(gen().EmitType(out(), &i32, "")) << gen().error();
+  EXPECT_EQ(result(), "int");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Matrix) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Matrix) {
   ast::type::F32Type f32;
   ast::type::MatrixType m(&f32, 3, 2);
 
-  ast::Module mod;
-  GeneratorImpl g(&mod);
-  ASSERT_TRUE(g.EmitType(&m, "")) << g.error();
-  EXPECT_EQ(g.result(), "matrix<float, 3, 2>");
+  ASSERT_TRUE(gen().EmitType(out(), &m, "")) << gen().error();
+  EXPECT_EQ(result(), "matrix<float, 3, 2>");
 }
 
 // TODO(dsinclair): How to annotate as workgroup?
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_Pointer) {
+TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Pointer) {
   ast::type::F32Type f32;
   ast::type::PointerType p(&f32, ast::StorageClass::kWorkgroup);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&p, "")) << g.error();
-  EXPECT_EQ(g.result(), "float*");
+  ASSERT_TRUE(gen().EmitType(out(), &p, "")) << gen().error();
+  EXPECT_EQ(result(), "float*");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Struct) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct) {
   ast::type::I32Type i32;
   ast::type::F32Type f32;
 
@@ -210,16 +181,14 @@
 
   ast::type::StructType s(std::move(str));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&s, "")) << g.error();
-  EXPECT_EQ(g.result(), R"(struct {
+  ASSERT_TRUE(gen().EmitType(out(), &s, "")) << gen().error();
+  EXPECT_EQ(result(), R"(struct {
   int a;
   float b;
 })");
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_Struct_InjectPadding) {
+TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Struct_InjectPadding) {
   ast::type::I32Type i32;
   ast::type::F32Type f32;
 
@@ -243,10 +212,8 @@
 
   ast::type::StructType s(std::move(str));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&s, "")) << g.error();
-  EXPECT_EQ(g.result(), R"(struct {
+  ASSERT_TRUE(gen().EmitType(out(), &s, "")) << gen().error();
+  EXPECT_EQ(result(), R"(struct {
   int8_t pad_0[4];
   int a;
   int8_t pad_1[24];
@@ -256,7 +223,7 @@
 })");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Struct_NameCollision) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
   ast::type::I32Type i32;
   ast::type::F32Type f32;
 
@@ -273,17 +240,15 @@
 
   ast::type::StructType s(std::move(str));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&s, "")) << g.error();
-  EXPECT_EQ(g.result(), R"(struct {
+  ASSERT_TRUE(gen().EmitType(out(), &s, "")) << gen().error();
+  EXPECT_EQ(result(), R"(struct {
   int double_tint_0;
   float float_tint_0;
 })");
 }
 
 // TODO(dsinclair): How to translate [[block]]
-TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_Struct_WithDecoration) {
+TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Struct_WithDecoration) {
   ast::type::I32Type i32;
   ast::type::F32Type f32;
 
@@ -302,41 +267,33 @@
 
   ast::type::StructType s(std::move(str));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&s, "")) << g.error();
-  EXPECT_EQ(g.result(), R"(struct {
+  ASSERT_TRUE(gen().EmitType(out(), &s, "")) << gen().error();
+  EXPECT_EQ(result(), R"(struct {
   int a;
   float b;
 })");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_U32) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_U32) {
   ast::type::U32Type u32;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&u32, "")) << g.error();
-  EXPECT_EQ(g.result(), "uint");
+  ASSERT_TRUE(gen().EmitType(out(), &u32, "")) << gen().error();
+  EXPECT_EQ(result(), "uint");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Vector) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Vector) {
   ast::type::F32Type f32;
   ast::type::VectorType v(&f32, 3);
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&v, "")) << g.error();
-  EXPECT_EQ(g.result(), "vector<float, 3>");
+  ASSERT_TRUE(gen().EmitType(out(), &v, "")) << gen().error();
+  EXPECT_EQ(result(), "vector<float, 3>");
 }
 
-TEST_F(HlslGeneratorImplTest, EmitType_Void) {
+TEST_F(HlslGeneratorImplTest_Type, EmitType_Void) {
   ast::type::VoidType v;
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitType(&v, "")) << g.error();
-  EXPECT_EQ(g.result(), "void");
+  ASSERT_TRUE(gen().EmitType(out(), &v, "")) << gen().error();
+  EXPECT_EQ(result(), "void");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_unary_op_test.cc b/src/writer/hlsl/generator_impl_unary_op_test.cc
index 4f93177..ae76942 100644
--- a/src/writer/hlsl/generator_impl_unary_op_test.cc
+++ b/src/writer/hlsl/generator_impl_unary_op_test.cc
@@ -15,11 +15,10 @@
 #include <memory>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/unary_op_expression.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
@@ -34,19 +33,18 @@
   out << data.op;
   return out;
 }
-using HlslUnaryOpTest = testing::TestWithParam<UnaryOpData>;
+class HlslUnaryOpTest : public TestHelper,
+                        public testing::TestWithParam<UnaryOpData> {};
 TEST_P(HlslUnaryOpTest, Emit) {
   auto params = GetParam();
 
   auto expr = std::make_unique<ast::IdentifierExpression>("expr");
   ast::UnaryOpExpression op(params.op, std::move(expr));
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitExpression(&op)) << g.error();
-  EXPECT_EQ(g.result(), std::string(params.name) + "(expr)");
+  ASSERT_TRUE(gen().EmitExpression(out(), &op)) << gen().error();
+  EXPECT_EQ(result(), std::string(params.name) + "(expr)");
 }
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
+INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_UnaryOp,
                          HlslUnaryOpTest,
                          testing::Values(UnaryOpData{"!", ast::UnaryOp::kNot},
                                          UnaryOpData{"-",
diff --git a/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc b/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc
index 961beef..1a85ec9 100644
--- a/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc
@@ -15,7 +15,6 @@
 #include <memory>
 #include <vector>
 
-#include "gtest/gtest.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/type/array_type.h"
@@ -23,47 +22,42 @@
 #include "src/ast/type/vector_type.h"
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
-#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace tint {
 namespace writer {
 namespace hlsl {
 namespace {
 
-using HlslGeneratorImplTest = testing::Test;
+class HlslGeneratorImplTest_VariableDecl : public TestHelper,
+                                           public testing::Test {};
 
-TEST_F(HlslGeneratorImplTest, Emit_VariableDeclStatement) {
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement) {
   ast::type::F32Type f32;
   auto var =
       std::make_unique<ast::Variable>("a", ast::StorageClass::kNone, &f32);
 
   ast::VariableDeclStatement stmt(std::move(var));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), "  float a;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), "  float a;\n");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_VariableDeclStatement_Const) {
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const) {
   ast::type::F32Type f32;
   auto var =
       std::make_unique<ast::Variable>("a", ast::StorageClass::kNone, &f32);
   var->set_is_const(true);
 
   ast::VariableDeclStatement stmt(std::move(var));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), "  const float a;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), "  const float a;\n");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_VariableDeclStatement_Array) {
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Array) {
   ast::type::F32Type f32;
   ast::type::ArrayType ary(&f32, 5);
 
@@ -71,46 +65,39 @@
       std::make_unique<ast::Variable>("a", ast::StorageClass::kNone, &ary);
 
   ast::VariableDeclStatement stmt(std::move(var));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), "  float a[5];\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), "  float a[5];\n");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_VariableDeclStatement_Function) {
+TEST_F(HlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Function) {
   ast::type::F32Type f32;
   auto var =
       std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &f32);
 
   ast::VariableDeclStatement stmt(std::move(var));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), "  float a;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), "  float a;\n");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_VariableDeclStatement_Private) {
+TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Private) {
   ast::type::F32Type f32;
   auto var =
       std::make_unique<ast::Variable>("a", ast::StorageClass::kPrivate, &f32);
 
   ast::VariableDeclStatement stmt(std::move(var));
+  gen().increment_indent();
 
-  ast::Module m;
-  GeneratorImpl g(&m);
-  g.increment_indent();
-
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), "  float a;\n");
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), "  float a;\n");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_Private) {
+TEST_F(HlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_Private) {
   auto ident = std::make_unique<ast::IdentifierExpression>("initializer");
 
   ast::type::F32Type f32;
@@ -119,15 +106,13 @@
   var->set_constructor(std::move(ident));
 
   ast::VariableDeclStatement stmt(std::move(var));
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), R"(float a = initializer;
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), R"(float a = initializer;
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, Emit_VariableDeclStatement_Initializer_ZeroVec) {
+TEST_F(HlslGeneratorImplTest_VariableDecl,
+       Emit_VariableDeclStatement_Initializer_ZeroVec) {
   ast::type::F32Type f32;
   ast::type::VectorType vec(&f32, 3);
 
@@ -140,11 +125,8 @@
   var->set_constructor(std::move(zero_vec));
 
   ast::VariableDeclStatement stmt(std::move(var));
-
-  ast::Module m;
-  GeneratorImpl g(&m);
-  ASSERT_TRUE(g.EmitStatement(&stmt)) << g.error();
-  EXPECT_EQ(g.result(), R"(vector<float, 3> a = vector<float, 3>(0.0f);
+  ASSERT_TRUE(gen().EmitStatement(out(), &stmt)) << gen().error();
+  EXPECT_EQ(result(), R"(vector<float, 3> a = vector<float, 3>(0.0f);
 )");
 }
 
diff --git a/src/writer/hlsl/test_helper.cc b/src/writer/hlsl/test_helper.cc
new file mode 100644
index 0000000..7e5fc6f
--- /dev/null
+++ b/src/writer/hlsl/test_helper.cc
@@ -0,0 +1,27 @@
+// 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/hlsl/test_helper.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+
+TestHelper::TestHelper() : td_(&ctx_, &mod_), impl_(&mod_) {}
+
+TestHelper::~TestHelper() = default;
+
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/hlsl/test_helper.h b/src/writer/hlsl/test_helper.h
new file mode 100644
index 0000000..a8eceed
--- /dev/null
+++ b/src/writer/hlsl/test_helper.h
@@ -0,0 +1,63 @@
+// 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_HLSL_TEST_HELPER_H_
+#define SRC_WRITER_HLSL_TEST_HELPER_H_
+
+#include <sstream>
+
+#include "gtest/gtest.h"
+#include "src/ast/module.h"
+#include "src/context.h"
+#include "src/type_determiner.h"
+#include "src/writer/hlsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+
+/// Helper class for testing
+class TestHelper {
+ public:
+  TestHelper();
+  ~TestHelper();
+
+  /// @returns the generator implementation
+  GeneratorImpl& gen() { return impl_; }
+
+  /// @returns the module
+  ast::Module* mod() { return &mod_; }
+
+  /// @returns the type determiner
+  TypeDeterminer& td() { return td_; }
+
+  /// @returns the output stream
+  std::ostream& out() { return out_; }
+
+  /// @returns the result string
+  std::string result() const { return out_.str(); }
+
+ private:
+  Context ctx_;
+  ast::Module mod_;
+  TypeDeterminer td_;
+  GeneratorImpl impl_;
+  std::ostringstream out_;
+};
+
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint
+
+#endif  // SRC_WRITER_HLSL_TEST_HELPER_H_