writer/hlsl: Rework string printing

Remove `pre` and `out` parameters from most generator methods.
Use the `out_` string stream in TextGenerator, add helpers to TextGenerator to simplify line printing.

Remove the `pre` and `out` fields from TestHelper.

Cleans up the `pre` aspects of the HLSL writer, so the same concept can be used by the MSL writer.
Fixes indentation bugs in formatting.

Change-Id: Ia35daf632c7c7b84a6fbf3b9ae42baaeb3c97649
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/55960
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/writer/hlsl/generator.cc b/src/writer/hlsl/generator.cc
index d55e70c..297788e 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(out_);
+  auto ret = impl_->Generate();
   if (!ret) {
     error_ = impl_->error();
   }
@@ -34,7 +34,7 @@
 }
 
 std::string Generator::result() const {
-  return out_.str();
+  return impl_->result();
 }
 
 std::string Generator::error() const {
diff --git a/src/writer/hlsl/generator.h b/src/writer/hlsl/generator.h
index 3623dd5..a3bef00 100644
--- a/src/writer/hlsl/generator.h
+++ b/src/writer/hlsl/generator.h
@@ -48,7 +48,6 @@
   std::string error() const;
 
  private:
-  std::ostringstream out_;
   std::unique_ptr<GeneratorImpl> impl_;
 };
 
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index f7677e4..496be2d 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 #include <iomanip>
+#include <iosfwd>
 #include <utility>
 #include <vector>
 
@@ -100,13 +101,6 @@
   return s;
 }
 
-// Helper for writting a '(' on construction and a ')' destruction.
-struct ScopedParen {
-  std::ostream& s_;
-  explicit ScopedParen(std::ostream& s) : s_(s) { s << "("; }
-  ~ScopedParen() { s_ << ")"; }
-};
-
 }  // namespace
 
 GeneratorImpl::GeneratorImpl(const Program* program)
@@ -114,7 +108,7 @@
 
 GeneratorImpl::~GeneratorImpl() = default;
 
-bool GeneratorImpl::Generate(std::ostream& out) {
+bool GeneratorImpl::Generate() {
   if (!builder_.HasTransformApplied<transform::Hlsl>()) {
     diagnostics_.add_error(
         diag::System::Writer,
@@ -123,8 +117,8 @@
     return false;
   }
 
-  std::stringstream pending;
   const TypeInfo* last_kind = nullptr;
+  std::streampos last_padding_pos;
 
   for (auto* decl : builder_.AST().GlobalDeclarations()) {
     if (decl->Is<ast::Alias>()) {
@@ -134,29 +128,29 @@
     // Emit a new line between declarations if the type of declaration has
     // changed, or we're about to emit a function
     auto* kind = &decl->TypeInfo();
-    if (pending.str().length() &&
-        (last_kind != kind || decl->Is<ast::Function>())) {
-      out << pending.str() << std::endl;
-      pending.str(std::string());
-      make_indent(out);
+    if (out_.tellp() != last_padding_pos) {
+      if (last_kind && (last_kind != kind || decl->Is<ast::Function>())) {
+        out_ << std::endl;
+        last_padding_pos = out_.tellp();
+      }
     }
     last_kind = kind;
 
     if (auto* global = decl->As<ast::Variable>()) {
-      if (!EmitGlobalVariable(pending, global)) {
+      if (!EmitGlobalVariable(global)) {
         return false;
       }
     } else if (auto* str = decl->As<ast::Struct>()) {
-      if (!EmitStructType(pending, builder_.Sem().Get(str))) {
+      if (!EmitStructType(builder_.Sem().Get(str))) {
         return false;
       }
     } else if (auto* func = decl->As<ast::Function>()) {
       if (func->IsEntryPoint()) {
-        if (!EmitEntryPointFunction(pending, func)) {
+        if (!EmitEntryPointFunction(func)) {
           return false;
         }
       } else {
-        if (!EmitFunction(pending, func)) {
+        if (!EmitFunction(func)) {
           return false;
         }
       }
@@ -167,8 +161,6 @@
     }
   }
 
-  out << pending.str();
-
   return true;
 }
 
@@ -176,15 +168,14 @@
   return builder_.Symbols().NameFor(builder_.Symbols().New(prefix));
 }
 
-bool GeneratorImpl::EmitArrayAccessor(std::ostream& pre,
-                                      std::ostream& out,
+bool GeneratorImpl::EmitArrayAccessor(std::ostream& out,
                                       ast::ArrayAccessorExpression* expr) {
-  if (!EmitExpression(pre, out, expr->array())) {
+  if (!EmitExpression(out, expr->array())) {
     return false;
   }
   out << "[";
 
-  if (!EmitExpression(pre, out, expr->idx_expr())) {
+  if (!EmitExpression(out, expr->idx_expr())) {
     return false;
   }
   out << "]";
@@ -192,8 +183,7 @@
   return true;
 }
 
-bool GeneratorImpl::EmitBitcast(std::ostream& pre,
-                                std::ostream& out,
+bool GeneratorImpl::EmitBitcast(std::ostream& out,
                                 ast::BitcastExpression* expr) {
   auto* type = TypeOf(expr);
   if (!type->is_integer_scalar() && !type->is_float_scalar()) {
@@ -208,67 +198,57 @@
     return false;
   }
   out << "(";
-  if (!EmitExpression(pre, out, expr->expr())) {
+  if (!EmitExpression(out, expr->expr())) {
     return false;
   }
   out << ")";
   return true;
 }
 
-bool GeneratorImpl::EmitAssign(std::ostream& out,
-                               ast::AssignmentStatement* stmt) {
-  make_indent(out);
-
-  std::ostringstream pre;
-
-  std::ostringstream lhs_out;
-  if (!EmitExpression(pre, lhs_out, stmt->lhs())) {
+bool GeneratorImpl::EmitAssign(ast::AssignmentStatement* stmt) {
+  auto out = line();
+  if (!EmitExpression(out, stmt->lhs())) {
     return false;
   }
-  std::ostringstream rhs_out;
-  if (!EmitExpression(pre, rhs_out, stmt->rhs())) {
+  out << " = ";
+  if (!EmitExpression(out, stmt->rhs())) {
     return false;
   }
-
-  out << pre.str();
-  out << lhs_out.str() << " = " << rhs_out.str() << ";" << std::endl;
-
+  out << ";";
   return true;
 }
 
-bool GeneratorImpl::EmitBinary(std::ostream& pre,
-                               std::ostream& out,
-                               ast::BinaryExpression* expr) {
+bool GeneratorImpl::EmitBinary(std::ostream& out, ast::BinaryExpression* expr) {
   if (expr->op() == ast::BinaryOp::kLogicalAnd ||
       expr->op() == ast::BinaryOp::kLogicalOr) {
-    std::ostringstream lhs_out;
-    if (!EmitExpression(pre, lhs_out, expr->lhs())) {
-      return false;
-    }
-
     auto name = generate_name(kTempNamePrefix);
-    make_indent(pre);
-    pre << "bool " << name << " = " << lhs_out.str() << ";" << std::endl;
 
-    make_indent(pre);
-    pre << "if (";
+    {
+      auto pre = line();
+      pre << "bool " << name << " = ";
+      if (!EmitExpression(pre, expr->lhs())) {
+        return false;
+      }
+      pre << ";";
+    }
+
     if (expr->op() == ast::BinaryOp::kLogicalOr) {
-      pre << "!";
-    }
-    pre << name << ") {" << std::endl;
-    increment_indent();
-
-    std::ostringstream rhs_out;
-    if (!EmitExpression(pre, rhs_out, expr->rhs())) {
-      return false;
+      line() << "if (!" << name << ") {";
+    } else {
+      line() << "if (" << name << ") {";
     }
 
-    make_indent(pre);
-    pre << name << " = " << rhs_out.str() << ";" << std::endl;
+    {
+      ScopedIndent si(this);
+      auto pre = line();
+      pre << name << " = ";
+      if (!EmitExpression(pre, expr->rhs())) {
+        return false;
+      }
+      pre << ";";
+    }
 
-    decrement_indent();
-    make_indent(pre);
-    pre << "}" << std::endl;
+    line() << "}";
 
     out << "(" << name << ")";
     return true;
@@ -284,11 +264,11 @@
        (lhs_type->Is<sem::Matrix>() && rhs_type->Is<sem::Matrix>()))) {
     // Matrices are transposed, so swap LHS and RHS.
     out << "mul(";
-    if (!EmitExpression(pre, out, expr->rhs())) {
+    if (!EmitExpression(out, expr->rhs())) {
       return false;
     }
     out << ", ";
-    if (!EmitExpression(pre, out, expr->lhs())) {
+    if (!EmitExpression(out, expr->lhs())) {
       return false;
     }
     out << ")";
@@ -297,7 +277,7 @@
   }
 
   out << "(";
-  if (!EmitExpression(pre, out, expr->lhs())) {
+  if (!EmitExpression(out, expr->lhs())) {
     return false;
   }
   out << " ";
@@ -369,7 +349,7 @@
   }
   out << " ";
 
-  if (!EmitExpression(pre, out, expr->rhs())) {
+  if (!EmitExpression(out, expr->rhs())) {
     return false;
   }
 
@@ -377,46 +357,35 @@
   return true;
 }
 
-bool GeneratorImpl::EmitBlock(std::ostream& out,
-                              const ast::BlockStatement* stmt) {
-  return EmitBlockBraces(out, [&] {
-    for (auto* s : *stmt) {
-      if (!EmitStatement(out, s)) {
-        return false;
-      }
+bool GeneratorImpl::EmitStatements(const ast::StatementList& stmts) {
+  for (auto* s : stmts) {
+    if (!EmitStatement(s)) {
+      return false;
     }
-    return true;
-  });
-}
-
-bool GeneratorImpl::EmitBlockAndNewline(std::ostream& out,
-                                        const ast::BlockStatement* stmt) {
-  const bool result = EmitBlock(out, stmt);
-  if (result) {
-    out << std::endl;
   }
-  return result;
-}
-
-bool GeneratorImpl::EmitIndentedBlockAndNewline(std::ostream& out,
-                                                ast::BlockStatement* stmt) {
-  make_indent(out);
-  const bool result = EmitBlock(out, stmt);
-  if (result) {
-    out << std::endl;
-  }
-  return result;
-}
-
-bool GeneratorImpl::EmitBreak(std::ostream& out, ast::BreakStatement*) {
-  make_indent(out);
-  out << "break;" << std::endl;
   return true;
 }
 
-bool GeneratorImpl::EmitCall(std::ostream& pre,
-                             std::ostream& out,
-                             ast::CallExpression* expr) {
+bool GeneratorImpl::EmitStatementsWithIndent(const ast::StatementList& stmts) {
+  ScopedIndent si(this);
+  return EmitStatements(stmts);
+}
+
+bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
+  line() << "{";
+  if (!EmitStatementsWithIndent(stmt->statements())) {
+    return false;
+  }
+  line() << "}";
+  return true;
+}
+
+bool GeneratorImpl::EmitBreak(ast::BreakStatement*) {
+  line() << "break;";
+  return true;
+}
+
+bool GeneratorImpl::EmitCall(std::ostream& out, ast::CallExpression* expr) {
   const auto& params = expr->params();
   auto* ident = expr->func();
   auto* call = builder_.Sem().Get(expr);
@@ -428,11 +397,11 @@
             func->Declaration()->decorations())) {
       // Special function generated by the CalculateArrayLength transform for
       // calling X.GetDimensions(Y)
-      if (!EmitExpression(pre, out, params[0])) {
+      if (!EmitExpression(out, params[0])) {
         return false;
       }
       out << ".GetDimensions(";
-      if (!EmitExpression(pre, out, params[1])) {
+      if (!EmitExpression(out, params[1])) {
         return false;
       }
       out << ")";
@@ -444,9 +413,9 @@
                 func->Declaration()->decorations())) {
       switch (intrinsic->storage_class) {
         case ast::StorageClass::kUniform:
-          return EmitUniformBufferAccess(pre, out, expr, intrinsic);
+          return EmitUniformBufferAccess(out, expr, intrinsic);
         case ast::StorageClass::kStorage:
-          return EmitStorageBufferAccess(pre, out, expr, intrinsic);
+          return EmitStorageBufferAccess(out, expr, intrinsic);
         default:
           TINT_UNREACHABLE(Writer, diagnostics_)
               << "unsupported DecomposeMemoryAccess::Intrinsic storage class:"
@@ -458,23 +427,23 @@
 
   if (auto* intrinsic = call->Target()->As<sem::Intrinsic>()) {
     if (intrinsic->IsTexture()) {
-      return EmitTextureCall(pre, out, expr, intrinsic);
+      return EmitTextureCall(out, expr, intrinsic);
     } else if (intrinsic->Type() == sem::IntrinsicType::kSelect) {
-      return EmitSelectCall(pre, out, expr);
+      return EmitSelectCall(out, expr);
     } else if (intrinsic->Type() == sem::IntrinsicType::kFrexp) {
-      return EmitFrexpCall(pre, out, expr, intrinsic);
+      return EmitFrexpCall(out, expr, intrinsic);
     } else if (intrinsic->Type() == sem::IntrinsicType::kIsNormal) {
-      return EmitIsNormalCall(pre, out, expr, intrinsic);
+      return EmitIsNormalCall(out, expr, intrinsic);
     } else if (intrinsic->Type() == sem::IntrinsicType::kIgnore) {
-      return EmitExpression(pre, out, expr->params()[0]);
+      return EmitExpression(out, expr->params()[0]);
     } else if (intrinsic->IsDataPacking()) {
-      return EmitDataPackingCall(pre, out, expr, intrinsic);
+      return EmitDataPackingCall(out, expr, intrinsic);
     } else if (intrinsic->IsDataUnpacking()) {
-      return EmitDataUnpackingCall(pre, out, expr, intrinsic);
+      return EmitDataUnpackingCall(out, expr, intrinsic);
     } else if (intrinsic->IsBarrier()) {
-      return EmitBarrierCall(pre, out, intrinsic);
+      return EmitBarrierCall(out, intrinsic);
     } else if (intrinsic->IsAtomic()) {
-      return EmitWorkgroupAtomicCall(pre, out, expr, intrinsic);
+      return EmitWorkgroupAtomicCall(out, expr, intrinsic);
     }
     auto name = generate_builtin_name(intrinsic);
     if (name.empty()) {
@@ -490,7 +459,7 @@
       }
       first = false;
 
-      if (!EmitExpression(pre, out, param)) {
+      if (!EmitExpression(out, param)) {
         return false;
       }
     }
@@ -519,7 +488,7 @@
     }
     first = false;
 
-    if (!EmitExpression(pre, out, param)) {
+    if (!EmitExpression(out, param)) {
       return false;
     }
   }
@@ -530,7 +499,6 @@
 }
 
 bool GeneratorImpl::EmitUniformBufferAccess(
-    std::ostream& pre,
     std::ostream& out,
     ast::CallExpression* expr,
     const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
@@ -538,13 +506,12 @@
 
   std::string scalar_offset = generate_name("scalar_offset");
   {
-    std::stringstream ss;
-    ss << "const int " << scalar_offset << " = (";
-    if (!EmitExpression(pre, ss, params[1])) {  // offset
+    auto pre = line();
+    pre << "const int " << scalar_offset << " = (";
+    if (!EmitExpression(pre, params[1])) {  // offset
       return false;
     }
-    make_indent(ss << ") / 4;" << std::endl);
-    pre << ss.str();
+    pre << ") / 4;";
   }
 
   using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
@@ -558,7 +525,7 @@
         return result;
       };
       auto load_scalar = [&]() {
-        if (!EmitExpression(pre, out, params[0])) {  // buffer
+        if (!EmitExpression(out, params[0])) {  // buffer
           return false;
         }
         out << "[" << scalar_offset << " / 4][" << scalar_offset << " % 4]";
@@ -567,14 +534,15 @@
       // Has a minimum alignment of 8 bytes, so is either .xy or .zw
       auto load_vec2 = [&] {
         std::string ubo_load = generate_name("ubo_load");
-        std::stringstream ss;
-        ss << "uint4 " << ubo_load << " = ";
-        if (!EmitExpression(pre, ss, params[0])) {  // buffer
-          return false;
+
+        {
+          auto pre = line();
+          pre << "uint4 " << ubo_load << " = ";
+          if (!EmitExpression(pre, params[0])) {  // buffer
+            return false;
+          }
+          pre << "[" << scalar_offset << " / 4];";
         }
-        ss << "[" << scalar_offset << " / 4]";
-        make_indent(ss << ";" << std::endl);
-        pre << ss.str();
 
         out << "((" << scalar_offset << " & 2) ? " << ubo_load
             << ".zw : " << ubo_load << ".xy)";
@@ -582,7 +550,7 @@
       };
       // vec3 has a minimum alignment of 16 bytes, so is just a .xyz swizzle
       auto load_vec3 = [&] {
-        if (!EmitExpression(pre, out, params[0])) {  // buffer
+        if (!EmitExpression(out, params[0])) {  // buffer
           return false;
         }
         out << "[" << scalar_offset << " / 4].xyz";
@@ -590,7 +558,7 @@
       };
       // vec4 has a minimum alignment of 16 bytes, easiest case
       auto load_vec4 = [&] {
-        if (!EmitExpression(pre, out, params[0])) {  // buffer
+        if (!EmitExpression(out, params[0])) {  // buffer
           return false;
         }
         out << "[" << scalar_offset << " / 4]";
@@ -637,7 +605,6 @@
 }
 
 bool GeneratorImpl::EmitStorageBufferAccess(
-    std::ostream& pre,
     std::ostream& out,
     ast::CallExpression* expr,
     const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
@@ -651,7 +618,7 @@
         if (cast) {
           out << cast << "(";
         }
-        if (!EmitExpression(pre, out, params[0])) {  // buffer
+        if (!EmitExpression(out, params[0])) {  // buffer
           return false;
         }
         out << ".Load";
@@ -659,7 +626,7 @@
           out << n;
         }
         ScopedParen sp(out);
-        if (!EmitExpression(pre, out, params[1])) {  // offset
+        if (!EmitExpression(out, params[1])) {  // offset
           return false;
         }
         if (cast) {
@@ -701,7 +668,7 @@
 
     case Op::kStore: {
       auto store = [&](int n) {
-        if (!EmitExpression(pre, out, params[0])) {  // buffer
+        if (!EmitExpression(out, params[0])) {  // buffer
           return false;
         }
         out << ".Store";
@@ -709,12 +676,12 @@
           out << n;
         }
         ScopedParen sp1(out);
-        if (!EmitExpression(pre, out, params[1])) {  // offset
+        if (!EmitExpression(out, params[1])) {  // offset
           return false;
         }
         out << ", asuint";
         ScopedParen sp2(out);
-        if (!EmitExpression(pre, out, params[2])) {  // value
+        if (!EmitExpression(out, params[2])) {  // value
           return false;
         }
         return true;
@@ -761,7 +728,7 @@
     case Op::kAtomicXor:
     case Op::kAtomicExchange:
     case Op::kAtomicCompareExchangeWeak:
-      return EmitStorageAtomicCall(pre, out, expr, intrinsic->op);
+      return EmitStorageAtomicCall(out, expr, intrinsic->op);
   }
 
   TINT_UNREACHABLE(Writer, diagnostics_)
@@ -771,55 +738,83 @@
 }
 
 bool GeneratorImpl::EmitStorageAtomicCall(
-    std::ostream& pre,
     std::ostream& out,
     ast::CallExpression* expr,
     transform::DecomposeMemoryAccess::Intrinsic::Op op) {
   using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
 
-  std::stringstream ss;
   std::string result = generate_name("atomic_result");
 
   auto* result_ty = TypeOf(expr);
   if (!result_ty->Is<sem::Void>()) {
-    if (!EmitTypeAndName(ss, TypeOf(expr), ast::StorageClass::kNone,
+    auto pre = line();
+    if (!EmitTypeAndName(pre, TypeOf(expr), ast::StorageClass::kNone,
                          ast::Access::kUndefined, result)) {
       return false;
     }
-    ss << " = ";
-    if (!EmitZeroValue(ss, result_ty)) {
+    pre << " = ";
+    if (!EmitZeroValue(pre, result_ty)) {
       return false;
     }
-    make_indent(ss << ";" << std::endl);
+    pre << ";";
   }
 
   auto* buffer = expr->params()[0];
   auto* offset = expr->params()[1];
 
+  auto call_buffer_method = [&](const char* name) {
+    auto pre = line();
+    if (!EmitExpression(pre, buffer)) {
+      return false;
+    }
+    pre << "." << name;
+    {
+      ScopedParen sp(pre);
+      if (!EmitExpression(pre, offset)) {
+        return false;
+      }
+
+      for (size_t i = 1; i < expr->params().size() - 1; i++) {
+        auto* arg = expr->params()[i];
+        pre << ", ";
+        if (!EmitExpression(pre, arg)) {
+          return false;
+        }
+      }
+
+      pre << ", " << result;
+    }
+    pre << ";";
+
+    out << result;
+    return true;
+  };
+
   switch (op) {
     case Op::kAtomicLoad: {
       // HLSL does not have an InterlockedLoad, so we emulate it with
       // InterlockedOr using 0 as the OR value
-      if (!EmitExpression(pre, ss, buffer)) {
+      auto pre = line();
+      if (!EmitExpression(pre, buffer)) {
         return false;
       }
-      ss << ".InterlockedOr";
+      pre << ".InterlockedOr";
       {
-        ScopedParen sp(ss);
-        if (!EmitExpression(pre, ss, offset)) {
+        ScopedParen sp(pre);
+        if (!EmitExpression(pre, offset)) {
           return false;
         }
-        ss << ", 0, " << result;
+        pre << ", 0, " << result;
       }
 
-      make_indent(ss << ";" << std::endl);
-      pre << ss.str();
+      pre << ";";
       out << result;
       return true;
     }
     case Op::kAtomicStore: {
       // HLSL does not have an InterlockedStore, so we emulate it with
       // InterlockedExchange and discard the returned value
+      auto pre = line();
       auto* value = expr->params()[2];
       auto* value_ty = TypeOf(value);
       if (!EmitTypeAndName(pre, value_ty, ast::StorageClass::kNone,
@@ -830,19 +825,19 @@
       if (!EmitZeroValue(pre, value_ty)) {
         return false;
       }
-      make_indent(pre << ";" << std::endl);
+      pre << ";";
 
-      if (!EmitExpression(pre, out, buffer)) {
+      if (!EmitExpression(out, buffer)) {
         return false;
       }
       out << ".InterlockedExchange";
       {
         ScopedParen sp(out);
-        if (!EmitExpression(pre, out, offset)) {
+        if (!EmitExpression(out, offset)) {
           return false;
         }
         out << ", ";
-        if (!EmitExpression(pre, out, value)) {
+        if (!EmitExpression(out, value)) {
           return false;
         }
         out << ", " << result;
@@ -854,174 +849,163 @@
       auto* value = expr->params()[3];
 
       std::string compare = generate_name("atomic_compare_value");
-      if (!EmitTypeAndName(ss, TypeOf(compare_value), ast::StorageClass::kNone,
-                           ast::Access::kUndefined, compare)) {
-        return false;
-      }
-      ss << " = ";
-      if (!EmitExpression(pre, ss, compare_value)) {
-        return false;
-      }
-      make_indent(ss << ";" << std::endl);
-
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedCompareExchange";
-      {
-        ScopedParen sp(ss);
-        if (!EmitExpression(pre, ss, offset)) {
+      {  // T atomic_compare_value = compare_value;
+        auto pre = line();
+        if (!EmitTypeAndName(pre, TypeOf(compare_value),
+                             ast::StorageClass::kNone, ast::Access::kUndefined,
+                             compare)) {
           return false;
         }
-        ss << ", " << compare << ", ";
-        if (!EmitExpression(pre, ss, value)) {
+        pre << " = ";
+        if (!EmitExpression(pre, compare_value)) {
           return false;
         }
-        ss << ", " << result << ".x";
+        pre << ";";
       }
-      make_indent(ss << ";" << std::endl);
+      {  // buffer.InterlockedCompareExchange(offset, compare, value, result.x);
+        auto pre = line();
+        if (!EmitExpression(pre, buffer)) {
+          return false;
+        }
+        pre << ".InterlockedCompareExchange";
+        {
+          ScopedParen sp(pre);
+          if (!EmitExpression(pre, offset)) {
+            return false;
+          }
+          pre << ", " << compare << ", ";
+          if (!EmitExpression(pre, value)) {
+            return false;
+          }
+          pre << ", " << result << ".x";
+        }
+        pre << ";";
+      }
+      {  // result.y = result.x == compare;
+        line() << result << ".y = " << result << ".x == " << compare << ";";
+      }
 
-      ss << result << ".y = " << result << ".x == " << compare;
-      make_indent(ss << ";" << std::endl);
-
-      pre << ss.str();
       out << result;
       return true;
     }
 
     case Op::kAtomicAdd:
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedAdd";
-      break;
+      return call_buffer_method("InterlockedAdd");
+
     case Op::kAtomicMax:
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedMax";
-      break;
+      return call_buffer_method("InterlockedMax");
+
     case Op::kAtomicMin:
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedMin";
-      break;
+      return call_buffer_method("InterlockedMin");
+
     case Op::kAtomicAnd:
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedAnd";
-      break;
+      return call_buffer_method("InterlockedAnd");
+
     case Op::kAtomicOr:
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedOr";
-      break;
+      return call_buffer_method("InterlockedOr");
+
     case Op::kAtomicXor:
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedXor";
-      break;
+      return call_buffer_method("InterlockedXor");
+
     case Op::kAtomicExchange:
-      if (!EmitExpression(pre, ss, buffer)) {
-        return false;
-      }
-      ss << ".InterlockedExchange";
-      break;
+      return call_buffer_method("InterlockedExchange");
 
     default:
-      TINT_UNREACHABLE(Writer, diagnostics_)
-          << "unsupported atomic DecomposeMemoryAccess::Intrinsic::Op: "
-          << static_cast<int>(op);
-      return false;
+      break;
   }
 
-  {
-    ScopedParen sp(ss);
-    if (!EmitExpression(pre, ss, offset)) {
-      return false;
-    }
-
-    for (size_t i = 1; i < expr->params().size() - 1; i++) {
-      auto* arg = expr->params()[i];
-      ss << ", ";
-      if (!EmitExpression(pre, ss, arg)) {
-        return false;
-      }
-    }
-
-    ss << ", " << result;
-  }
-
-  make_indent(ss << ";" << std::endl);
-  pre << ss.str();
-  out << result;
-
-  return true;
+  TINT_UNREACHABLE(Writer, diagnostics_)
+      << "unsupported atomic DecomposeMemoryAccess::Intrinsic::Op: "
+      << static_cast<int>(op);
+  return false;
 }
 
-bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& pre,
-                                            std::ostream& out,
+bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& out,
                                             ast::CallExpression* expr,
                                             const sem::Intrinsic* intrinsic) {
-  std::stringstream ss;
   std::string result = generate_name("atomic_result");
 
   if (!intrinsic->ReturnType()->Is<sem::Void>()) {
-    if (!EmitTypeAndName(ss, intrinsic->ReturnType(), ast::StorageClass::kNone,
+    auto pre = line();
+    if (!EmitTypeAndName(pre, intrinsic->ReturnType(), ast::StorageClass::kNone,
                          ast::Access::kUndefined, result)) {
       return false;
     }
-    ss << " = ";
-    if (!EmitZeroValue(ss, intrinsic->ReturnType())) {
+    pre << " = ";
+    if (!EmitZeroValue(pre, intrinsic->ReturnType())) {
       return false;
     }
-    make_indent(ss << ";" << std::endl);
+    pre << ";";
   }
 
+  auto call = [&](const char* name) {
+    auto pre = line();
+    pre << name;
+
+    {
+      ScopedParen sp(pre);
+      for (size_t i = 0; i < expr->params().size(); i++) {
+        auto* arg = expr->params()[i];
+        if (i > 0) {
+          pre << ", ";
+        }
+        if (!EmitExpression(pre, arg)) {
+          return false;
+        }
+      }
+
+      pre << ", " << result;
+    }
+
+    pre << ";";
+
+    out << result;
+    return true;
+  };
+
   switch (intrinsic->Type()) {
     case sem::IntrinsicType::kAtomicLoad: {
       // HLSL does not have an InterlockedLoad, so we emulate it with
       // InterlockedOr using 0 as the OR value
-      ss << "InterlockedOr";
+      auto pre = line();
+      pre << "InterlockedOr";
       {
-        ScopedParen sp(ss);
-        if (!EmitExpression(pre, ss, expr->params()[0])) {
+        ScopedParen sp(pre);
+        if (!EmitExpression(pre, expr->params()[0])) {
           return false;
         }
-        ss << ", 0, " << result;
+        pre << ", 0, " << result;
       }
-      make_indent(ss << ";" << std::endl);
+      pre << ";";
 
-      pre << ss.str();
       out << result;
       return true;
     }
     case sem::IntrinsicType::kAtomicStore: {
       // HLSL does not have an InterlockedStore, so we emulate it with
       // InterlockedExchange and discard the returned value
-      auto* value_ty = intrinsic->Parameters()[1].type;
-      if (!EmitTypeAndName(pre, value_ty, ast::StorageClass::kNone,
-                           ast::Access::kUndefined, result)) {
-        return false;
+      {  // T result = 0;
+        auto pre = line();
+        auto* value_ty = intrinsic->Parameters()[1].type;
+        if (!EmitTypeAndName(pre, value_ty, ast::StorageClass::kNone,
+                             ast::Access::kUndefined, result)) {
+          return false;
+        }
+        pre << " = ";
+        if (!EmitZeroValue(pre, value_ty)) {
+          return false;
+        }
+        pre << ";";
       }
-      pre << " = ";
-      if (!EmitZeroValue(pre, value_ty)) {
-        return false;
-      }
-      make_indent(pre << ";" << std::endl);
 
       out << "InterlockedExchange";
       {
         ScopedParen sp(out);
-        if (!EmitExpression(pre, out, expr->params()[0])) {
+        if (!EmitExpression(out, expr->params()[0])) {
           return false;
         }
         out << ", ";
-        if (!EmitExpression(pre, out, expr->params()[1])) {
+        if (!EmitExpression(out, expr->params()[1])) {
           return false;
         }
         out << ", " << result;
@@ -1034,116 +1018,102 @@
       auto* value = expr->params()[2];
 
       std::string compare = generate_name("atomic_compare_value");
-      if (!EmitTypeAndName(ss, TypeOf(compare_value), ast::StorageClass::kNone,
-                           ast::Access::kUndefined, compare)) {
-        return false;
-      }
-      ss << " = ";
-      if (!EmitExpression(pre, ss, compare_value)) {
-        return false;
-      }
-      make_indent(ss << ";" << std::endl);
 
-      ss << "InterlockedCompareExchange";
-      {
-        ScopedParen sp(ss);
-        if (!EmitExpression(pre, ss, dest)) {
+      {  // T compare_value = <compare_value>;
+        auto pre = line();
+        if (!EmitTypeAndName(pre, TypeOf(compare_value),
+                             ast::StorageClass::kNone, ast::Access::kUndefined,
+                             compare)) {
           return false;
         }
-        ss << ", " << compare << ", ";
-        if (!EmitExpression(pre, ss, value)) {
+        pre << " = ";
+        if (!EmitExpression(pre, compare_value)) {
           return false;
         }
-        ss << ", " << result << ".x";
+        pre << ";";
       }
-      make_indent(ss << ";" << std::endl);
 
-      ss << result << ".y = " << result << ".x == " << compare;
-      make_indent(ss << ";" << std::endl);
+      {  // InterlockedCompareExchange(dst, compare, value, result.x);
+        auto pre = line();
+        pre << "InterlockedCompareExchange";
+        {
+          ScopedParen sp(pre);
+          if (!EmitExpression(pre, dest)) {
+            return false;
+          }
+          pre << ", " << compare << ", ";
+          if (!EmitExpression(pre, value)) {
+            return false;
+          }
+          pre << ", " << result << ".x";
+        }
+        pre << ";";
+      }
 
-      pre << ss.str();
+      {  // result.y = result.x == compare;
+        line() << result << ".y = " << result << ".x == " << compare << ";";
+      }
+
       out << result;
       return true;
     }
 
     case sem::IntrinsicType::kAtomicAdd:
-      ss << "InterlockedAdd";
-      break;
+      return call("InterlockedAdd");
+
     case sem::IntrinsicType::kAtomicMax:
-      ss << "InterlockedMax";
-      break;
+      return call("InterlockedMax");
+
     case sem::IntrinsicType::kAtomicMin:
-      ss << "InterlockedMin";
-      break;
+      return call("InterlockedMin");
+
     case sem::IntrinsicType::kAtomicAnd:
-      ss << "InterlockedAnd";
-      break;
+      return call("InterlockedAnd");
+
     case sem::IntrinsicType::kAtomicOr:
-      ss << "InterlockedOr";
-      break;
+      return call("InterlockedOr");
+
     case sem::IntrinsicType::kAtomicXor:
-      ss << "InterlockedXor";
-      break;
+      return call("InterlockedXor");
+
     case sem::IntrinsicType::kAtomicExchange:
-      ss << "InterlockedExchange";
-      break;
+      return call("InterlockedExchange");
 
     default:
-      TINT_UNREACHABLE(Writer, diagnostics_)
-          << "unsupported atomic intrinsic: " << intrinsic->Type();
-      return false;
+      break;
   }
 
-  {
-    ScopedParen sp(ss);
-    for (size_t i = 0; i < expr->params().size(); i++) {
-      auto* arg = expr->params()[i];
-      if (i > 0) {
-        ss << ", ";
-      }
-      if (!EmitExpression(pre, ss, arg)) {
-        return false;
-      }
-    }
-
-    ss << ", " << result;
-  }
-  make_indent(ss << ";" << std::endl);
-
-  pre << ss.str();
-  out << result;
-
-  return true;
+  TINT_UNREACHABLE(Writer, diagnostics_)
+      << "unsupported atomic intrinsic: " << intrinsic->Type();
+  return false;
 }
 
-bool GeneratorImpl::EmitSelectCall(std::ostream& pre,
-                                   std::ostream& out,
+bool GeneratorImpl::EmitSelectCall(std::ostream& out,
                                    ast::CallExpression* expr) {
   auto* expr_true = expr->params()[0];
   auto* expr_false = expr->params()[1];
   auto* expr_cond = expr->params()[2];
   ScopedParen paren(out);
-  if (!EmitExpression(pre, out, expr_cond)) {
+  if (!EmitExpression(out, expr_cond)) {
     return false;
   }
 
   out << " ? ";
 
-  if (!EmitExpression(pre, out, expr_true)) {
+  if (!EmitExpression(out, expr_true)) {
     return false;
   }
 
   out << " : ";
 
-  if (!EmitExpression(pre, out, expr_false)) {
+  if (!EmitExpression(out, expr_false)) {
     return false;
   }
 
   return true;
 }
 
-bool GeneratorImpl::EmitFrexpCall(std::ostream& pre,
-                                  std::ostream& out,
+bool GeneratorImpl::EmitFrexpCall(std::ostream& out,
                                   ast::CallExpression* expr,
                                   const sem::Intrinsic* intrinsic) {
   // Exponent is an integer in WGSL, but HLSL wants a float.
@@ -1159,37 +1129,35 @@
 
   // Exponent is an integer, which HLSL does not have an overload for.
   // We need to cast from a float.
-  std::stringstream ss;
   auto float_exp = generate_name(kTempNamePrefix);
-  ss << "float" << width << " " << float_exp << ";";
-
-  make_indent(ss << std::endl);
   auto significand = generate_name(kTempNamePrefix);
-  ss << "float" << width << " " << significand << " = frexp(";
-  if (!EmitExpression(pre, ss, expr->params()[0])) {
-    return false;
+  line() << "float" << width << " " << float_exp << ";";
+  {
+    auto pre = line();
+    pre << "float" << width << " " << significand << " = frexp(";
+    if (!EmitExpression(pre, expr->params()[0])) {
+      return false;
+    }
+    pre << ", " << float_exp << ");";
   }
-  ss << ", " << float_exp << ");";
+  {
+    auto pre = line();
+    if (!EmitExpression(pre, expr->params()[1])) {
+      return false;
+    }
+    pre << " = ";
+    if (!EmitType(pre, exponent.type->UnwrapPtr(), ast::StorageClass::kNone,
+                  ast::Access::kUndefined, "")) {
+      return false;
+    }
+    pre << "(" << float_exp << ");";
+  }
 
-  make_indent(ss << std::endl);
-  if (!EmitExpression(pre, ss, expr->params()[1])) {
-    return false;
-  }
-  ss << " = ";
-  if (!EmitType(ss, exponent.type->UnwrapPtr(), ast::StorageClass::kNone,
-                ast::Access::kUndefined, "")) {
-    return false;
-  }
-  ss << "(" << float_exp << ");";
-
-  make_indent(ss << std::endl);
-  pre << ss.str();
   out << significand;
   return true;
 }
 
-bool GeneratorImpl::EmitIsNormalCall(std::ostream& pre,
-                                     std::ostream& out,
+bool GeneratorImpl::EmitIsNormalCall(std::ostream& out,
                                      ast::CallExpression* expr,
                                      const sem::Intrinsic* intrinsic) {
   // HLSL doesn't have a isNormal intrinsic, we need to emulate
@@ -1207,33 +1175,29 @@
   auto exponent = generate_name("tint_isnormal_exponent");
   auto clamped = generate_name("tint_isnormal_clamped");
 
-  std::stringstream ss;
-  ss << "uint" << width << " " << exponent << " = asuint(";
-  if (!EmitExpression(pre, ss, expr->params()[0])) {
-    return false;
+  {
+    auto pre = line();
+    pre << "uint" << width << " " << exponent << " = asuint(";
+    if (!EmitExpression(pre, expr->params()[0])) {
+      return false;
+    }
+    pre << ") & " << kExponentMask << ";";
   }
-  ss << ") & " << kExponentMask << ";";
-
-  make_indent(ss << std::endl);
-  ss << "uint" << width << " " << clamped << " = "
-     << "clamp(" << exponent << ", " << kMinNormalExponent << ", "
-     << kMaxNormalExponent << ");";
-
-  make_indent(ss << std::endl);
-  pre << ss.str();
+  line() << "uint" << width << " " << clamped << " = "
+         << "clamp(" << exponent << ", " << kMinNormalExponent << ", "
+         << kMaxNormalExponent << ");";
 
   out << "(" << clamped << " == " << exponent << ")";
   return true;
 }
 
-bool GeneratorImpl::EmitDataPackingCall(std::ostream& pre,
-                                        std::ostream& out,
+bool GeneratorImpl::EmitDataPackingCall(std::ostream& out,
                                         ast::CallExpression* expr,
                                         const sem::Intrinsic* intrinsic) {
   auto* param = expr->params()[0];
   auto tmp_name = generate_name(kTempNamePrefix);
   std::ostringstream expr_out;
-  if (!EmitExpression(pre, expr_out, param)) {
+  if (!EmitExpression(expr_out, param)) {
     return false;
   }
   uint32_t dims = 2;
@@ -1253,15 +1217,18 @@
     case sem::IntrinsicType::kPack4x8snorm:
     case sem::IntrinsicType::kPack4x8unorm:
     case sem::IntrinsicType::kPack2x16snorm:
-    case sem::IntrinsicType::kPack2x16unorm:
-      pre << (is_signed ? "" : "u") << "int" << dims << " " << tmp_name << " = "
-          << (is_signed ? "" : "u") << "int" << dims << "(round(clamp("
-          << expr_out.str() << ", " << (is_signed ? "-1.0" : "0.0")
-          << ", 1.0) * " << scale << ".0))";
-      if (is_signed) {
-        pre << " & " << (dims == 4 ? "0xff" : "0xffff");
+    case sem::IntrinsicType::kPack2x16unorm: {
+      {
+        auto pre = line();
+        pre << (is_signed ? "" : "u") << "int" << dims << " " << tmp_name
+            << " = " << (is_signed ? "" : "u") << "int" << dims
+            << "(round(clamp(" << expr_out.str() << ", "
+            << (is_signed ? "-1.0" : "0.0") << ", 1.0) * " << scale << ".0))";
+        if (is_signed) {
+          pre << " & " << (dims == 4 ? "0xff" : "0xffff");
+        }
+        pre << ";";
       }
-      pre << ";\n";
       if (is_signed) {
         out << "asuint";
       }
@@ -1272,10 +1239,13 @@
       }
       out << ")";
       break;
-    case sem::IntrinsicType::kPack2x16float:
-      pre << "uint2 " << tmp_name << " = f32tof16(" << expr_out.str() << ");\n";
+    }
+    case sem::IntrinsicType::kPack2x16float: {
+      line() << "uint2 " << tmp_name << " = f32tof16(" << expr_out.str()
+             << ");";
       out << "(" << tmp_name << ".x | " << tmp_name << ".y << 16)";
       break;
+    }
     default:
       diagnostics_.add_error(
           diag::System::Writer,
@@ -1286,14 +1256,13 @@
   return true;
 }
 
-bool GeneratorImpl::EmitDataUnpackingCall(std::ostream& pre,
-                                          std::ostream& out,
+bool GeneratorImpl::EmitDataUnpackingCall(std::ostream& out,
                                           ast::CallExpression* expr,
                                           const sem::Intrinsic* intrinsic) {
   auto* param = expr->params()[0];
   auto tmp_name = generate_name(kTempNamePrefix);
   std::ostringstream expr_out;
-  if (!EmitExpression(pre, expr_out, param)) {
+  if (!EmitExpression(expr_out, param)) {
     return false;
   }
   uint32_t dims = 2;
@@ -1313,16 +1282,19 @@
     case sem::IntrinsicType::kUnpack4x8snorm:
     case sem::IntrinsicType::kUnpack2x16snorm: {
       auto tmp_name2 = generate_name(kTempNamePrefix);
-      pre << "int " << tmp_name2 << " = int(" << expr_out.str() << ");\n";
-      // Perform sign extension on the converted values.
-      pre << "int" << dims << " " << tmp_name << " = int" << dims << "(";
-      if (dims == 2) {
-        pre << tmp_name2 << " << 16, " << tmp_name2 << ") >> 16";
-      } else {
-        pre << tmp_name2 << " << 24, " << tmp_name2 << " << 16, " << tmp_name2
-            << " << 8, " << tmp_name2 << ") >> 24";
+      line() << "int " << tmp_name2 << " = int(" << expr_out.str() << ");";
+      {  // Perform sign extension on the converted values.
+        auto pre = line();
+        pre << "int" << dims << " " << tmp_name << " = int" << dims << "(";
+        if (dims == 2) {
+          pre << tmp_name2 << " << 16, " << tmp_name2 << ") >> 16";
+        } else {
+          pre << tmp_name2 << " << 24, " << tmp_name2 << " << 16, " << tmp_name2
+              << " << 8, " << tmp_name2 << ") >> 24";
+        }
+        pre << ";";
       }
-      pre << ";\n";
+
       out << "clamp(float" << dims << "(" << tmp_name << ") / " << scale
           << ".0, " << (is_signed ? "-1.0" : "0.0") << ", 1.0)";
       break;
@@ -1330,21 +1302,24 @@
     case sem::IntrinsicType::kUnpack4x8unorm:
     case sem::IntrinsicType::kUnpack2x16unorm: {
       auto tmp_name2 = generate_name(kTempNamePrefix);
-      pre << "uint " << tmp_name2 << " = " << expr_out.str() << ";\n";
-      pre << "uint" << dims << " " << tmp_name << " = uint" << dims << "(";
-      pre << tmp_name2 << " & " << (dims == 2 ? "0xffff" : "0xff") << ", ";
-      if (dims == 4) {
-        pre << "(" << tmp_name2 << " >> " << (32 / dims) << ") & 0xff, ("
-            << tmp_name2 << " >> 16) & 0xff, " << tmp_name2 << " >> 24";
-      } else {
-        pre << tmp_name2 << " >> " << (32 / dims);
+      line() << "uint " << tmp_name2 << " = " << expr_out.str() << ";";
+      {
+        auto pre = line();
+        pre << "uint" << dims << " " << tmp_name << " = uint" << dims << "(";
+        pre << tmp_name2 << " & " << (dims == 2 ? "0xffff" : "0xff") << ", ";
+        if (dims == 4) {
+          pre << "(" << tmp_name2 << " >> " << (32 / dims) << ") & 0xff, ("
+              << tmp_name2 << " >> 16) & 0xff, " << tmp_name2 << " >> 24";
+        } else {
+          pre << tmp_name2 << " >> " << (32 / dims);
+        }
+        pre << ");";
       }
-      pre << ");\n";
       out << "float" << dims << "(" << tmp_name << ") / " << scale << ".0";
       break;
     }
     case sem::IntrinsicType::kUnpack2x16float:
-      pre << "uint " << tmp_name << " = " << expr_out.str() << ";\n";
+      line() << "uint " << tmp_name << " = " << expr_out.str() << ";";
       out << "f16tof32(uint2(" << tmp_name << " & 0xffff, " << tmp_name
           << " >> 16))";
       break;
@@ -1358,8 +1333,7 @@
   return true;
 }
 
-bool GeneratorImpl::EmitBarrierCall(std::ostream&,
-                                    std::ostream& out,
+bool GeneratorImpl::EmitBarrierCall(std::ostream& out,
                                     const sem::Intrinsic* intrinsic) {
   // TODO(crbug.com/tint/661): Combine sequential barriers to a single
   // instruction.
@@ -1375,8 +1349,7 @@
   return true;
 }
 
-bool GeneratorImpl::EmitTextureCall(std::ostream& pre,
-                                    std::ostream& out,
+bool GeneratorImpl::EmitTextureCall(std::ostream& out,
                                     ast::CallExpression* expr,
                                     const sem::Intrinsic* intrinsic) {
   using Usage = sem::ParameterUsage;
@@ -1519,49 +1492,47 @@
 
       // Declare a variable to hold the queried texture info
       auto dims = generate_name(kTempNamePrefix);
-
       if (num_dimensions == 1) {
-        pre << "int " << dims << ";";
+        line() << "int " << dims << ";";
       } else {
-        pre << "int" << num_dimensions << " " << dims << ";";
+        line() << "int" << num_dimensions << " " << dims << ";";
       }
 
-      pre << std::endl;
-      make_indent(pre);
-
-      if (!EmitExpression(pre, pre, texture)) {
-        return false;
-      }
-      pre << ".GetDimensions(";
-
-      if (level_arg) {
-        if (!EmitExpression(pre, pre, level_arg)) {
+      {  // texture.GetDimensions(...)
+        auto pre = line();
+        if (!EmitExpression(pre, texture)) {
           return false;
         }
-        pre << ", ";
-      } else if (intrinsic->Type() == sem::IntrinsicType::kTextureNumLevels) {
-        pre << "0, ";
-      }
+        pre << ".GetDimensions(";
 
-      if (num_dimensions == 1) {
-        pre << dims;
-      } else {
-        static constexpr char xyzw[] = {'x', 'y', 'z', 'w'};
-        if (num_dimensions < 0 || num_dimensions > 4) {
-          TINT_ICE(Writer, diagnostics_)
-              << "vector dimensions are " << num_dimensions;
-          return false;
-        }
-        for (int i = 0; i < num_dimensions; i++) {
-          if (i > 0) {
-            pre << ", ";
+        if (level_arg) {
+          if (!EmitExpression(pre, level_arg)) {
+            return false;
           }
-          pre << dims << "." << xyzw[i];
+          pre << ", ";
+        } else if (intrinsic->Type() == sem::IntrinsicType::kTextureNumLevels) {
+          pre << "0, ";
         }
-      }
 
-      pre << ");" << std::endl;
-      make_indent(pre);
+        if (num_dimensions == 1) {
+          pre << dims;
+        } else {
+          static constexpr char xyzw[] = {'x', 'y', 'z', 'w'};
+          if (num_dimensions < 0 || num_dimensions > 4) {
+            TINT_ICE(Writer, diagnostics_)
+                << "vector dimensions are " << num_dimensions;
+            return false;
+          }
+          for (int i = 0; i < num_dimensions; i++) {
+            if (i > 0) {
+              pre << ", ";
+            }
+            pre << dims << "." << xyzw[i];
+          }
+        }
+
+        pre << ");";
+      }
 
       // The out parameters of the GetDimensions() call is now in temporary
       // `dims` variable. This may be packed with other data, so the final
@@ -1573,7 +1544,7 @@
       break;
   }
 
-  if (!EmitExpression(pre, out, texture))
+  if (!EmitExpression(out, texture))
     return false;
 
   bool pack_mip_in_coords = false;
@@ -1616,7 +1587,7 @@
   }
 
   if (auto* sampler = arg(Usage::kSampler)) {
-    if (!EmitExpression(pre, out, sampler))
+    if (!EmitExpression(out, sampler))
       return false;
     out << ", ";
   }
@@ -1633,7 +1604,7 @@
     auto* stmt = builder_.Sem().Get(vector)->Stmt();
     builder_.Sem().Add(zero, builder_.create<sem::Expression>(zero, i32, stmt));
     auto* packed = AppendVector(&builder_, vector, zero);
-    return EmitExpression(pre, out, packed);
+    return EmitExpression(out, packed);
   };
 
   if (auto* array_index = arg(Usage::kArrayIndex)) {
@@ -1644,7 +1615,7 @@
         return false;
       }
     } else {
-      if (!EmitExpression(pre, out, packed)) {
+      if (!EmitExpression(out, packed)) {
         return false;
       }
     }
@@ -1654,7 +1625,7 @@
       return false;
     }
   } else {
-    if (!EmitExpression(pre, out, param_coords)) {
+    if (!EmitExpression(out, param_coords)) {
       return false;
     }
   }
@@ -1663,7 +1634,7 @@
                      Usage::kDdy, Usage::kSampleIndex, Usage::kOffset}) {
     if (auto* e = arg(usage)) {
       out << ", ";
-      if (!EmitExpression(pre, out, e)) {
+      if (!EmitExpression(out, e)) {
         return false;
       }
     }
@@ -1671,7 +1642,7 @@
 
   if (intrinsic->Type() == sem::IntrinsicType::kTextureStore) {
     out << "] = ";
-    if (!EmitExpression(pre, out, arg(Usage::kValue))) {
+    if (!EmitExpression(out, arg(Usage::kValue))) {
       return false;
     }
   } else {
@@ -1792,69 +1763,53 @@
   return "";
 }
 
-bool GeneratorImpl::EmitCase(std::ostream& out, ast::CaseStatement* stmt) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitCase(ast::CaseStatement* stmt) {
   if (stmt->IsDefault()) {
-    out << "default:";
+    line() << "default: {";
   } else {
-    bool first = true;
     for (auto* selector : stmt->selectors()) {
-      if (!first) {
-        out << std::endl;
-        make_indent(out);
-      }
-      first = false;
-
+      auto out = line();
       out << "case ";
       if (!EmitLiteral(out, selector)) {
         return false;
       }
       out << ":";
+      if (selector == stmt->selectors().back()) {
+        out << " {";
+      }
     }
   }
 
-  out << " {" << std::endl;
-
-  increment_indent();
-
-  for (auto* s : *stmt->body()) {
-    if (!EmitStatement(out, s)) {
+  {
+    ScopedIndent si(this);
+    if (!EmitStatements(stmt->body()->statements())) {
       return false;
     }
+    if (!last_is_break_or_fallthrough(stmt->body())) {
+      line() << "break;";
+    }
   }
 
-  if (!last_is_break_or_fallthrough(stmt->body())) {
-    make_indent(out);
-    out << "break;" << std::endl;
-  }
-
-  decrement_indent();
-  make_indent(out);
-  out << "}" << std::endl;
+  line() << "}";
 
   return true;
 }
 
-bool GeneratorImpl::EmitConstructor(std::ostream& pre,
-                                    std::ostream& out,
+bool GeneratorImpl::EmitConstructor(std::ostream& out,
                                     ast::ConstructorExpression* expr) {
   if (auto* scalar = expr->As<ast::ScalarConstructorExpression>()) {
-    return EmitScalarConstructor(pre, out, scalar);
+    return EmitScalarConstructor(out, scalar);
   }
-  return EmitTypeConstructor(pre, out,
-                             expr->As<ast::TypeConstructorExpression>());
+  return EmitTypeConstructor(out, expr->As<ast::TypeConstructorExpression>());
 }
 
 bool GeneratorImpl::EmitScalarConstructor(
-    std::ostream&,
     std::ostream& out,
     ast::ScalarConstructorExpression* expr) {
   return EmitLiteral(out, expr->literal());
 }
 
-bool GeneratorImpl::EmitTypeConstructor(std::ostream& pre,
-                                        std::ostream& out,
+bool GeneratorImpl::EmitTypeConstructor(std::ostream& out,
                                         ast::TypeConstructorExpression* expr) {
   auto* type = TypeOf(expr)->UnwrapRef();
 
@@ -1897,7 +1852,7 @@
     }
     first = false;
 
-    if (!EmitExpression(pre, out, e)) {
+    if (!EmitExpression(out, e)) {
       return false;
     }
   }
@@ -1910,49 +1865,45 @@
   return true;
 }
 
-bool GeneratorImpl::EmitContinue(std::ostream& out, ast::ContinueStatement*) {
-  if (!emit_continuing_(out)) {
+bool GeneratorImpl::EmitContinue(ast::ContinueStatement*) {
+  if (!emit_continuing_()) {
     return false;
   }
-  make_indent(out);
-  out << "continue;" << std::endl;
+  line() << "continue;";
   return true;
 }
 
-bool GeneratorImpl::EmitDiscard(std::ostream& out, ast::DiscardStatement*) {
-  make_indent(out);
+bool GeneratorImpl::EmitDiscard(ast::DiscardStatement*) {
   // 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;
+  line() << "discard;";
   return true;
 }
 
-bool GeneratorImpl::EmitExpression(std::ostream& pre,
-                                   std::ostream& out,
-                                   ast::Expression* expr) {
+bool GeneratorImpl::EmitExpression(std::ostream& out, ast::Expression* expr) {
   if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
-    return EmitArrayAccessor(pre, out, a);
+    return EmitArrayAccessor(out, a);
   }
   if (auto* b = expr->As<ast::BinaryExpression>()) {
-    return EmitBinary(pre, out, b);
+    return EmitBinary(out, b);
   }
   if (auto* b = expr->As<ast::BitcastExpression>()) {
-    return EmitBitcast(pre, out, b);
+    return EmitBitcast(out, b);
   }
   if (auto* c = expr->As<ast::CallExpression>()) {
-    return EmitCall(pre, out, c);
+    return EmitCall(out, c);
   }
   if (auto* c = expr->As<ast::ConstructorExpression>()) {
-    return EmitConstructor(pre, out, c);
+    return EmitConstructor(out, c);
   }
   if (auto* i = expr->As<ast::IdentifierExpression>()) {
-    return EmitIdentifier(pre, out, i);
+    return EmitIdentifier(out, i);
   }
   if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
-    return EmitMemberAccessor(pre, out, m);
+    return EmitMemberAccessor(out, m);
   }
   if (auto* u = expr->As<ast::UnaryOpExpression>()) {
-    return EmitUnaryOp(pre, out, u);
+    return EmitUnaryOp(out, u);
   }
 
   diagnostics_.add_error(diag::System::Writer,
@@ -1960,71 +1911,60 @@
   return false;
 }
 
-bool GeneratorImpl::EmitIdentifier(std::ostream&,
-                                   std::ostream& out,
+bool GeneratorImpl::EmitIdentifier(std::ostream& out,
                                    ast::IdentifierExpression* expr) {
   out << builder_.Symbols().NameFor(expr->symbol());
   return true;
 }
 
-bool GeneratorImpl::EmitIf(std::ostream& out, ast::IfStatement* stmt) {
-  make_indent(out);
-
-  std::ostringstream pre;
-  std::ostringstream cond;
-  if (!EmitExpression(pre, cond, stmt->condition())) {
-    return false;
+bool GeneratorImpl::EmitIf(ast::IfStatement* stmt) {
+  {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, stmt->condition())) {
+      return false;
+    }
+    out << ") {";
   }
 
-  std::ostringstream if_out;
-  if_out << "if (" << cond.str() << ") ";
-  if (!EmitBlock(if_out, stmt->body())) {
+  if (!EmitStatementsWithIndent(stmt->body()->statements())) {
     return false;
   }
 
   for (auto* e : stmt->else_statements()) {
     if (e->HasCondition()) {
-      if_out << " else {" << std::endl;
-
+      line() << "} else {";
       increment_indent();
 
-      std::ostringstream else_pre;
-      std::ostringstream else_cond_out;
-      if (!EmitExpression(else_pre, else_cond_out, e->condition())) {
-        return false;
+      {
+        auto out = line();
+        out << "if (";
+        if (!EmitExpression(out, e->condition())) {
+          return false;
+        }
+        out << ") {";
       }
-      if_out << else_pre.str();
-
-      make_indent(if_out);
-      if_out << "if (" << else_cond_out.str() << ") ";
     } else {
-      if_out << " else ";
+      line() << "} else {";
     }
 
-    if (!EmitBlock(if_out, e->body())) {
+    if (!EmitStatementsWithIndent(e->body()->statements())) {
       return false;
     }
   }
-  if_out << std::endl;
+
+  line() << "}";
 
   for (auto* e : stmt->else_statements()) {
-    if (!e->HasCondition()) {
-      continue;
+    if (e->HasCondition()) {
+      decrement_indent();
+      line() << "}";
     }
-
-    decrement_indent();
-    make_indent(if_out);
-    if_out << "}" << std::endl;
   }
-
-  out << pre.str();
-  out << if_out.str();
   return true;
 }
 
-bool GeneratorImpl::EmitFunction(std::ostream& out, ast::Function* func) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitFunction(ast::Function* func) {
   auto* sem = builder_.Sem().Get(func);
 
   if (ast::HasDecoration<ast::InternalDecoration>(func->decorations())) {
@@ -2032,74 +1972,75 @@
     return true;
   }
 
-  if (!EmitType(out, sem->ReturnType(), ast::StorageClass::kNone,
-                ast::Access::kReadWrite, "")) {
-    return false;
-  }
-
-  out << " ";
-
-  out << builder_.Symbols().NameFor(func->symbol()) << "(";
-
-  bool first = true;
-
-  for (auto* v : sem->Parameters()) {
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    auto const* type = v->Type();
-
-    if (auto* ptr = type->As<sem::Pointer>()) {
-      // Transform pointer parameters in to `inout` parameters.
-      // The WGSL spec is highly restrictive in what can be passed in pointer
-      // parameters, which allows for this transformation. See:
-      // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
-      out << "inout ";
-      type = ptr->StoreType();
-    }
-
-    // Note: WGSL only allows for StorageClass::kNone on parameters, however the
-    // sanitizer transforms generates load / store functions for storage or
-    // uniform buffers. These functions have a buffer parameter with
-    // StorageClass::kStorage or StorageClass::kUniform. This is required to
-    // correctly translate the parameter to a [RW]ByteAddressBuffer for storage
-    // buffers and a uint4[N] for uniform buffers.
-    if (!EmitTypeAndName(
-            out, type, v->StorageClass(), v->Access(),
-            builder_.Symbols().NameFor(v->Declaration()->symbol()))) {
+  {
+    auto out = line();
+    if (!EmitType(out, sem->ReturnType(), ast::StorageClass::kNone,
+                  ast::Access::kReadWrite, "")) {
       return false;
     }
+
+    out << " " << builder_.Symbols().NameFor(func->symbol()) << "(";
+
+    bool first = true;
+
+    for (auto* v : sem->Parameters()) {
+      if (!first) {
+        out << ", ";
+      }
+      first = false;
+
+      auto const* type = v->Type();
+
+      if (auto* ptr = type->As<sem::Pointer>()) {
+        // Transform pointer parameters in to `inout` parameters.
+        // The WGSL spec is highly restrictive in what can be passed in pointer
+        // parameters, which allows for this transformation. See:
+        // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
+        out << "inout ";
+        type = ptr->StoreType();
+      }
+
+      // Note: WGSL only allows for StorageClass::kNone on parameters, however
+      // the sanitizer transforms generates load / store functions for storage
+      // or uniform buffers. These functions have a buffer parameter with
+      // StorageClass::kStorage or StorageClass::kUniform. This is required to
+      // correctly translate the parameter to a [RW]ByteAddressBuffer for
+      // storage buffers and a uint4[N] for uniform buffers.
+      if (!EmitTypeAndName(
+              out, type, v->StorageClass(), v->Access(),
+              builder_.Symbols().NameFor(v->Declaration()->symbol()))) {
+        return false;
+      }
+    }
+    out << ") {";
   }
 
-  out << ") ";
-
-  if (!EmitBlockAndNewline(out, func->body())) {
+  if (!EmitStatementsWithIndent(func->body()->statements())) {
     return false;
   }
 
+  line() << "}";
+
   return true;
 }
 
-bool GeneratorImpl::EmitGlobalVariable(std::ostream& out,
-                                       ast::Variable* global) {
+bool GeneratorImpl::EmitGlobalVariable(ast::Variable* global) {
   if (global->is_const()) {
-    return EmitProgramConstVariable(out, global);
+    return EmitProgramConstVariable(global);
   }
 
   auto* sem = builder_.Sem().Get(global);
   switch (sem->StorageClass()) {
     case ast::StorageClass::kUniform:
-      return EmitUniformVariable(out, sem);
+      return EmitUniformVariable(sem);
     case ast::StorageClass::kStorage:
-      return EmitStorageVariable(out, sem);
+      return EmitStorageVariable(sem);
     case ast::StorageClass::kUniformConstant:
-      return EmitHandleVariable(out, sem);
+      return EmitHandleVariable(sem);
     case ast::StorageClass::kPrivate:
-      return EmitPrivateVariable(out, sem);
+      return EmitPrivateVariable(sem);
     case ast::StorageClass::kWorkgroup:
-      return EmitWorkgroupVariable(out, sem);
+      return EmitWorkgroupVariable(sem);
     default:
       break;
   }
@@ -2109,10 +2050,7 @@
   return false;
 }
 
-bool GeneratorImpl::EmitUniformVariable(std::ostream& out,
-                                        const sem::Variable* var) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitUniformVariable(const sem::Variable* var) {
   auto* decl = var->Declaration();
   auto binding_point = decl->binding_point();
   auto* type = var->Type()->UnwrapRef();
@@ -2125,29 +2063,28 @@
   }
 
   auto name = builder_.Symbols().NameFor(decl->symbol());
-  out << "cbuffer cbuffer_" << name << RegisterAndSpace('b', binding_point)
-      << " {" << std::endl;
+  line() << "cbuffer cbuffer_" << name << RegisterAndSpace('b', binding_point)
+         << " {";
 
-  increment_indent();
-  make_indent(out);
-
-  if (!EmitTypeAndName(out, type, ast::StorageClass::kUniform, var->Access(),
-                       name)) {
-    return false;
+  {
+    ScopedIndent si(this);
+    auto out = line();
+    if (!EmitTypeAndName(out, type, ast::StorageClass::kUniform, var->Access(),
+                         name)) {
+      return false;
+    }
+    out << ";";
   }
-  out << ";" << std::endl;
-  decrement_indent();
-  out << "};" << std::endl;
+
+  line() << "};";
 
   return true;
 }
 
-bool GeneratorImpl::EmitStorageVariable(std::ostream& out,
-                                        const sem::Variable* var) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitStorageVariable(const sem::Variable* var) {
   auto* decl = var->Declaration();
   auto* type = var->Type()->UnwrapRef();
+  auto out = line();
   if (!EmitTypeAndName(out, type, ast::StorageClass::kStorage, var->Access(),
                        builder_.Symbols().NameFor(decl->symbol()))) {
     return false;
@@ -2155,17 +2092,15 @@
 
   out << RegisterAndSpace(var->Access() == ast::Access::kRead ? 't' : 'u',
                           decl->binding_point())
-      << ";" << std::endl;
+      << ";";
 
   return true;
 }
 
-bool GeneratorImpl::EmitHandleVariable(std::ostream& out,
-                                       const sem::Variable* var) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitHandleVariable(const sem::Variable* var) {
   auto* decl = var->Declaration();
   auto* unwrapped_type = var->Type()->UnwrapRef();
+  auto out = line();
 
   auto name = builder_.Symbols().NameFor(decl->symbol());
   auto* type = var->Type()->UnwrapRef();
@@ -2192,26 +2127,13 @@
         << bp.group->value() << ")";
   }
 
-  out << ";" << std::endl;
+  out << ";";
   return true;
 }
 
-bool GeneratorImpl::EmitPrivateVariable(std::ostream& out,
-                                        const sem::Variable* var) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitPrivateVariable(const sem::Variable* var) {
   auto* decl = var->Declaration();
-
-  std::ostringstream constructor_out;
-  if (auto* constructor = decl->constructor()) {
-    if (!EmitExpression(out, constructor_out, constructor)) {
-      return false;
-    }
-  } else {
-    if (!EmitZeroValue(constructor_out, var->Type()->UnwrapRef())) {
-      return false;
-    }
-  }
+  auto out = line();
 
   out << "static ";
 
@@ -2221,27 +2143,25 @@
     return false;
   }
 
-  if (constructor_out.str().length()) {
-    out << " = " << constructor_out.str();
-  }
-
-  out << ";" << std::endl;
-  return true;
-}
-
-bool GeneratorImpl::EmitWorkgroupVariable(std::ostream& out,
-                                          const sem::Variable* var) {
-  make_indent(out);
-
-  auto* decl = var->Declaration();
-
-  std::ostringstream constructor_out;
+  out << " = ";
   if (auto* constructor = decl->constructor()) {
-    if (!EmitExpression(out, constructor_out, constructor)) {
+    if (!EmitExpression(out, constructor)) {
+      return false;
+    }
+  } else {
+    if (!EmitZeroValue(out, var->Type()->UnwrapRef())) {
       return false;
     }
   }
 
+  out << ";";
+  return true;
+}
+
+bool GeneratorImpl::EmitWorkgroupVariable(const sem::Variable* var) {
+  auto* decl = var->Declaration();
+  auto out = line();
+
   out << "groupshared ";
 
   auto name = builder_.Symbols().NameFor(decl->symbol());
@@ -2250,11 +2170,14 @@
     return false;
   }
 
-  if (constructor_out.str().length()) {
-    out << " = " << constructor_out.str();
+  if (auto* constructor = decl->constructor()) {
+    out << " = ";
+    if (!EmitExpression(out, constructor)) {
+      return false;
+    }
   }
 
-  out << ";" << std::endl;
+  out << ";";
   return true;
 }
 
@@ -2288,85 +2211,81 @@
   return "";
 }
 
-bool GeneratorImpl::EmitEntryPointFunction(std::ostream& out,
-                                           ast::Function* func) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitEntryPointFunction(ast::Function* func) {
   auto* func_sem = builder_.Sem().Get(func);
 
-  if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
-    // Emit the workgroup_size attribute.
-    auto wgsize = func_sem->workgroup_size();
-    out << "[numthreads(";
-    for (int i = 0; i < 3; i++) {
-      if (i > 0) {
+  {
+    auto out = line();
+    if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
+      // Emit the workgroup_size attribute.
+      auto wgsize = func_sem->workgroup_size();
+      out << "[numthreads(";
+      for (int i = 0; i < 3; i++) {
+        if (i > 0) {
+          out << ", ";
+        }
+
+        if (wgsize[i].overridable_const) {
+          auto* sem_const = builder_.Sem().Get(wgsize[i].overridable_const);
+          if (!sem_const->IsPipelineConstant()) {
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "expected a pipeline-overridable constant";
+          }
+          out << kSpecConstantPrefix << sem_const->ConstantId();
+        } else {
+          out << std::to_string(wgsize[i].value);
+        }
+      }
+      out << ")]" << std::endl;
+    }
+
+    out << func->return_type()->FriendlyName(builder_.Symbols());
+
+    out << " " << builder_.Symbols().NameFor(func->symbol()) << "(";
+
+    bool first = true;
+
+    // Emit entry point parameters.
+    for (auto* var : func->params()) {
+      auto* sem = builder_.Sem().Get(var);
+      auto* type = sem->Type();
+      if (!type->Is<sem::Struct>()) {
+        // ICE likely indicates that the CanonicalizeEntryPointIO transform was
+        // not run, or a builtin parameter was added after it was run.
+        TINT_ICE(Writer, diagnostics_)
+            << "Unsupported non-struct entry point parameter";
+      }
+
+      if (!first) {
         out << ", ";
       }
+      first = false;
 
-      if (wgsize[i].overridable_const) {
-        auto* sem_const = builder_.Sem().Get(wgsize[i].overridable_const);
-        if (!sem_const->IsPipelineConstant()) {
-          TINT_ICE(Writer, builder_.Diagnostics())
-              << "expected a pipeline-overridable constant";
-        }
-        out << kSpecConstantPrefix << sem_const->ConstantId();
-      } else {
-        out << std::to_string(wgsize[i].value);
+      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                           builder_.Symbols().NameFor(var->symbol()))) {
+        return false;
       }
     }
-    out << ")]" << std::endl;
-    make_indent(out);
+
+    out << ") {";
   }
 
-  out << func->return_type()->FriendlyName(builder_.Symbols());
+  {
+    ScopedIndent si(this);
 
-  out << " " << builder_.Symbols().NameFor(func->symbol()) << "(";
-
-  bool first = true;
-
-  // Emit entry point parameters.
-  for (auto* var : func->params()) {
-    auto* sem = builder_.Sem().Get(var);
-    auto* type = sem->Type();
-    if (!type->Is<sem::Struct>()) {
-      // ICE likely indicates that the CanonicalizeEntryPointIO transform was
-      // not run, or a builtin parameter was added after it was run.
-      TINT_ICE(Writer, diagnostics_)
-          << "Unsupported non-struct entry point parameter";
-    }
-
-    if (!first) {
-      out << ", ";
-    }
-    first = false;
-
-    if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                         builder_.Symbols().NameFor(var->symbol()))) {
+    if (!EmitStatements(func->body()->statements())) {
       return false;
     }
-  }
 
-  out << ") {" << std::endl;
-
-  increment_indent();
-
-  for (auto* s : *func->body()) {
-    if (!EmitStatement(out, s)) {
-      return false;
-    }
-  }
-  auto* last_statement = func->get_last_statement();
-  if (last_statement == nullptr ||
-      !last_statement->Is<ast::ReturnStatement>()) {
-    ast::ReturnStatement ret(ProgramID(), Source{});
-    if (!EmitStatement(out, &ret)) {
-      return false;
+    if (!Is<ast::ReturnStatement>(func->get_last_statement())) {
+      ast::ReturnStatement ret(ProgramID(), Source{});
+      if (!EmitStatement(&ret)) {
+        return false;
+      }
     }
   }
 
-  decrement_indent();
-  make_indent(out);
-  out << "}" << std::endl;
+  line() << "}";
 
   return true;
 }
@@ -2465,37 +2384,35 @@
   return true;
 }
 
-bool GeneratorImpl::EmitLoop(std::ostream& out, ast::LoopStatement* stmt) {
-  make_indent(out);
-
-  auto emit_continuing = [this, stmt](std::ostream& o) {
+bool GeneratorImpl::EmitLoop(ast::LoopStatement* stmt) {
+  auto emit_continuing = [this, stmt]() {
     if (stmt->has_continuing()) {
-      make_indent(o);
-      if (!EmitBlock(o, stmt->continuing())) {
+      if (!EmitBlock(stmt->continuing())) {
         return false;
       }
-      o << std::endl;
     }
     return true;
   };
 
   TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
-  bool ok = EmitBlockBraces(out, "while (true)", [&] {
-    for (auto* s : stmt->body()->statements()) {
-      if (!EmitStatement(out, s)) {
-        return false;
-      }
+  line() << "while (true) {";
+  {
+    ScopedIndent si(this);
+    if (!EmitStatements(stmt->body()->statements())) {
+      return false;
     }
-    return emit_continuing(out);
-  });
-  out << std::endl;
-  return ok;
+    if (!emit_continuing()) {
+      return false;
+    }
+  }
+  line() << "}";
+
+  return true;
 }
 
-bool GeneratorImpl::EmitMemberAccessor(std::ostream& pre,
-                                       std::ostream& out,
+bool GeneratorImpl::EmitMemberAccessor(std::ostream& out,
                                        ast::MemberAccessorExpression* expr) {
-  if (!EmitExpression(pre, out, expr->structure())) {
+  if (!EmitExpression(out, expr->structure())) {
     return false;
   }
   out << ".";
@@ -2503,80 +2420,72 @@
   // Swizzles output the name directly
   if (builder_.Sem().Get(expr)->Is<sem::Swizzle>()) {
     out << builder_.Symbols().NameFor(expr->member()->symbol());
-  } else if (!EmitExpression(pre, out, expr->member())) {
+  } else if (!EmitExpression(out, expr->member())) {
     return false;
   }
 
   return true;
 }
 
-bool GeneratorImpl::EmitReturn(std::ostream& out, ast::ReturnStatement* stmt) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitReturn(ast::ReturnStatement* stmt) {
   if (stmt->has_value()) {
-    std::ostringstream pre;
-    std::ostringstream ret_out;
-    if (!EmitExpression(pre, ret_out, stmt->value())) {
+    auto out = line();
+    out << "return ";
+    if (!EmitExpression(out, stmt->value())) {
       return false;
     }
-    out << pre.str();
-    out << "return " << ret_out.str();
+    out << ";";
   } else {
-    out << "return";
+    line() << "return;";
   }
-  out << ";" << std::endl;
   return true;
 }
 
-bool GeneratorImpl::EmitStatement(std::ostream& out, ast::Statement* stmt) {
+bool GeneratorImpl::EmitStatement(ast::Statement* stmt) {
   if (auto* a = stmt->As<ast::AssignmentStatement>()) {
-    return EmitAssign(out, a);
+    return EmitAssign(a);
   }
   if (auto* b = stmt->As<ast::BlockStatement>()) {
-    return EmitIndentedBlockAndNewline(out, b);
+    return EmitBlock(b);
   }
   if (auto* b = stmt->As<ast::BreakStatement>()) {
-    return EmitBreak(out, b);
+    return EmitBreak(b);
   }
   if (auto* c = stmt->As<ast::CallStatement>()) {
-    make_indent(out);
-    std::ostringstream pre;
-    std::ostringstream call_out;
-    if (!EmitCall(pre, call_out, c->expr())) {
-      return false;
-    }
-    out << pre.str();
+    auto out = line();
     if (!TypeOf(c->expr())->Is<sem::Void>()) {
       out << "(void) ";
     }
-    out << call_out.str() << ";" << std::endl;
+    if (!EmitCall(out, c->expr())) {
+      return false;
+    }
+    out << ";";
     return true;
   }
   if (auto* c = stmt->As<ast::ContinueStatement>()) {
-    return EmitContinue(out, c);
+    return EmitContinue(c);
   }
   if (auto* d = stmt->As<ast::DiscardStatement>()) {
-    return EmitDiscard(out, d);
+    return EmitDiscard(d);
   }
   if (stmt->As<ast::FallthroughStatement>()) {
-    make_indent(out);
-    out << "/* fallthrough */" << std::endl;
+    line() << "/* fallthrough */";
     return true;
   }
   if (auto* i = stmt->As<ast::IfStatement>()) {
-    return EmitIf(out, i);
+    return EmitIf(i);
   }
   if (auto* l = stmt->As<ast::LoopStatement>()) {
-    return EmitLoop(out, l);
+    return EmitLoop(l);
   }
   if (auto* r = stmt->As<ast::ReturnStatement>()) {
-    return EmitReturn(out, r);
+    return EmitReturn(r);
   }
   if (auto* s = stmt->As<ast::SwitchStatement>()) {
-    return EmitSwitch(out, s);
+    return EmitSwitch(s);
   }
   if (auto* v = stmt->As<ast::VariableDeclStatement>()) {
-    return EmitVariable(out, v->variable());
+    return EmitVariable(v->variable());
   }
 
   diagnostics_.add_error(diag::System::Writer,
@@ -2584,29 +2493,26 @@
   return false;
 }
 
-bool GeneratorImpl::EmitSwitch(std::ostream& out, ast::SwitchStatement* stmt) {
-  make_indent(out);
-
-  std::ostringstream pre;
-  std::ostringstream cond;
-  if (!EmitExpression(pre, cond, stmt->condition())) {
-    return false;
+bool GeneratorImpl::EmitSwitch(ast::SwitchStatement* stmt) {
+  {  // switch(expr) {
+    auto out = line();
+    out << "switch(";
+    if (!EmitExpression(out, stmt->condition())) {
+      return false;
+    }
+    out << ") {";
   }
 
-  out << pre.str();
-  out << "switch(" << cond.str() << ") {" << std::endl;
-
-  increment_indent();
-
-  for (auto* s : stmt->body()) {
-    if (!EmitCase(out, s)) {
-      return false;
+  {
+    ScopedIndent si(this);
+    for (auto* s : stmt->body()) {
+      if (!EmitCase(s)) {
+        return false;
+      }
     }
   }
 
-  decrement_indent();
-  make_indent(out);
-  out << "}" << std::endl;
+  line() << "}";
 
   return true;
 }
@@ -2806,7 +2712,7 @@
   return true;
 }
 
-bool GeneratorImpl::EmitStructType(std::ostream& out, const sem::Struct* str) {
+bool GeneratorImpl::EmitStructType(const sem::Struct* str) {
   auto storage_class_uses = str->StorageClassUsage();
   if (storage_class_uses.size() ==
       (storage_class_uses.count(ast::StorageClass::kStorage) +
@@ -2821,72 +2727,69 @@
   }
 
   auto struct_name = builder_.Symbols().NameFor(str->Declaration()->name());
-  out << "struct " << struct_name << " {" << std::endl;
+  line() << "struct " << struct_name << " {";
+  {
+    ScopedIndent si(this);
+    for (auto* mem : str->Members()) {
+      auto name = builder_.Symbols().NameFor(mem->Declaration()->symbol());
 
-  increment_indent();
-  for (auto* mem : str->Members()) {
-    make_indent(out);
+      auto* ty = mem->Type();
 
-    auto name = builder_.Symbols().NameFor(mem->Declaration()->symbol());
-
-    auto* ty = mem->Type();
-
-    if (!EmitTypeAndName(out, ty, ast::StorageClass::kNone,
-                         ast::Access::kReadWrite, name)) {
-      return false;
-    }
-
-    for (auto* deco : mem->Declaration()->decorations()) {
-      if (auto* location = deco->As<ast::LocationDecoration>()) {
-        auto& pipeline_stage_uses = str->PipelineStageUses();
-        if (pipeline_stage_uses.size() != 1) {
-          TINT_ICE(Writer, diagnostics_)
-              << "invalid entry point IO struct uses";
-        }
-
-        if (pipeline_stage_uses.count(sem::PipelineStageUsage::kVertexInput)) {
-          out << " : TEXCOORD" + std::to_string(location->value());
-        } else if (pipeline_stage_uses.count(
-                       sem::PipelineStageUsage::kVertexOutput)) {
-          out << " : TEXCOORD" + std::to_string(location->value());
-        } else if (pipeline_stage_uses.count(
-                       sem::PipelineStageUsage::kFragmentInput)) {
-          out << " : TEXCOORD" + std::to_string(location->value());
-        } else if (pipeline_stage_uses.count(
-                       sem::PipelineStageUsage::kFragmentOutput)) {
-          out << " : SV_Target" + std::to_string(location->value());
-        } else {
-          TINT_ICE(Writer, diagnostics_)
-              << "invalid use of location decoration";
-        }
-      } else if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
-        auto attr = builtin_to_attribute(builtin->value());
-        if (attr.empty()) {
-          diagnostics_.add_error(diag::System::Writer, "unsupported builtin");
-          return false;
-        }
-        out << " : " << attr;
+      auto out = line();
+      if (!EmitTypeAndName(out, ty, ast::StorageClass::kNone,
+                           ast::Access::kReadWrite, name)) {
+        return false;
       }
-    }
 
-    out << ";" << std::endl;
+      for (auto* deco : mem->Declaration()->decorations()) {
+        if (auto* location = deco->As<ast::LocationDecoration>()) {
+          auto& pipeline_stage_uses = str->PipelineStageUses();
+          if (pipeline_stage_uses.size() != 1) {
+            TINT_ICE(Writer, diagnostics_)
+                << "invalid entry point IO struct uses";
+          }
+
+          if (pipeline_stage_uses.count(
+                  sem::PipelineStageUsage::kVertexInput)) {
+            out << " : TEXCOORD" + std::to_string(location->value());
+          } else if (pipeline_stage_uses.count(
+                         sem::PipelineStageUsage::kVertexOutput)) {
+            out << " : TEXCOORD" + std::to_string(location->value());
+          } else if (pipeline_stage_uses.count(
+                         sem::PipelineStageUsage::kFragmentInput)) {
+            out << " : TEXCOORD" + std::to_string(location->value());
+          } else if (pipeline_stage_uses.count(
+                         sem::PipelineStageUsage::kFragmentOutput)) {
+            out << " : SV_Target" + std::to_string(location->value());
+          } else {
+            TINT_ICE(Writer, diagnostics_)
+                << "invalid use of location decoration";
+          }
+        } else if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
+          auto attr = builtin_to_attribute(builtin->value());
+          if (attr.empty()) {
+            diagnostics_.add_error(diag::System::Writer, "unsupported builtin");
+            return false;
+          }
+          out << " : " << attr;
+        }
+      }
+
+      out << ";";
+    }
   }
 
-  decrement_indent();
-  make_indent(out);
-
-  out << "};" << std::endl;
+  line() << "};";
 
   return true;
 }
 
-bool GeneratorImpl::EmitUnaryOp(std::ostream& pre,
-                                std::ostream& out,
+bool GeneratorImpl::EmitUnaryOp(std::ostream& out,
                                 ast::UnaryOpExpression* expr) {
   switch (expr->op()) {
     case ast::UnaryOp::kIndirection:
     case ast::UnaryOp::kAddressOf:
-      return EmitExpression(pre, out, expr->expr());
+      return EmitExpression(out, expr->expr());
     case ast::UnaryOp::kComplement:
       out << "~";
       break;
@@ -2899,7 +2802,7 @@
   }
   out << "(";
 
-  if (!EmitExpression(pre, out, expr->expr())) {
+  if (!EmitExpression(out, expr->expr())) {
     return false;
   }
 
@@ -2908,9 +2811,7 @@
   return true;
 }
 
-bool GeneratorImpl::EmitVariable(std::ostream& out, ast::Variable* var) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitVariable(ast::Variable* var) {
   auto* sem = builder_.Sem().Get(var);
   auto* type = sem->Type()->UnwrapRef();
 
@@ -2921,21 +2822,7 @@
     return false;
   }
 
-  std::ostringstream constructor_out;
-  constructor_out << " = ";
-
-  if (var->constructor()) {
-    std::ostringstream pre;
-    if (!EmitExpression(pre, constructor_out, var->constructor())) {
-      return false;
-    }
-    out << pre.str();
-  } else {
-    if (!EmitZeroValue(constructor_out, type)) {
-      return false;
-    }
-  }
-
+  auto out = line();
   if (var->is_const()) {
     out << "const ";
   }
@@ -2943,15 +2830,24 @@
                        builder_.Symbols().NameFor(var->symbol()))) {
     return false;
   }
-  out << constructor_out.str() << ";" << std::endl;
+
+  out << " = ";
+
+  if (var->constructor()) {
+    if (!EmitExpression(out, var->constructor())) {
+      return false;
+    }
+  } else {
+    if (!EmitZeroValue(out, type)) {
+      return false;
+    }
+  }
+  out << ";";
 
   return true;
 }
 
-bool GeneratorImpl::EmitProgramConstVariable(std::ostream& out,
-                                             const ast::Variable* var) {
-  make_indent(out);
-
+bool GeneratorImpl::EmitProgramConstVariable(const ast::Variable* var) {
   for (auto* d : var->decorations()) {
     if (!d->Is<ast::OverrideDecoration>()) {
       diagnostics_.add_error(diag::System::Writer,
@@ -2964,48 +2860,45 @@
     return false;
   }
 
-  std::ostringstream constructor_out;
-  if (var->constructor() != nullptr) {
-    std::ostringstream pre;
-    if (!EmitExpression(pre, constructor_out, var->constructor())) {
-      return false;
-    }
-    out << pre.str();
-  }
-
   auto* sem = builder_.Sem().Get(var);
   auto* type = sem->Type();
 
   if (sem->IsPipelineConstant()) {
     auto const_id = sem->ConstantId();
 
-    out << "#ifndef " << kSpecConstantPrefix << const_id << std::endl;
+    line() << "#ifndef " << kSpecConstantPrefix << const_id;
 
     if (var->constructor() != nullptr) {
-      out << "#define " << kSpecConstantPrefix << const_id << " "
-          << constructor_out.str() << std::endl;
+      auto out = line();
+      out << "#define " << kSpecConstantPrefix << const_id << " ";
+      if (!EmitExpression(out, var->constructor())) {
+        return false;
+      }
     } else {
-      out << "#error spec constant required for constant id " << const_id
-          << std::endl;
+      line() << "#error spec constant required for constant id " << const_id;
     }
-    out << "#endif" << std::endl;
-    out << "static const ";
-    if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
-                         builder_.Symbols().NameFor(var->symbol()))) {
-      return false;
+    line() << "#endif";
+    {
+      auto out = line();
+      out << "static const ";
+      if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
+                           builder_.Symbols().NameFor(var->symbol()))) {
+        return false;
+      }
+      out << " = " << kSpecConstantPrefix << const_id << ";";
     }
-    out << " = " << kSpecConstantPrefix << const_id << ";" << std::endl;
   } else {
+    auto out = line();
     out << "static const ";
     if (!EmitTypeAndName(out, type, sem->StorageClass(), sem->Access(),
                          builder_.Symbols().NameFor(var->symbol()))) {
       return false;
     }
-
-    if (var->constructor() != nullptr) {
-      out << " = " << constructor_out.str();
+    out << " = ";
+    if (!EmitExpression(out, var->constructor())) {
+      return false;
     }
-    out << ";" << std::endl;
+    out << ";";
   }
 
   return true;
@@ -3026,23 +2919,6 @@
   return "";
 }
 
-template <typename F>
-bool GeneratorImpl::EmitBlockBraces(std::ostream& out,
-                                    const std::string& prefix,
-                                    F&& cb) {
-  out << prefix << (prefix.empty() ? "{" : " {") << std::endl;
-  increment_indent();
-
-  if (!cb()) {
-    return false;
-  }
-
-  decrement_indent();
-  make_indent(out);
-  out << "}";
-  return true;
-}
-
 }  // namespace hlsl
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index e46a6bd..fd3c6bd 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -54,320 +54,246 @@
   explicit GeneratorImpl(const Program* program);
   ~GeneratorImpl();
 
-  /// @param out the output stream
   /// @returns true on successful generation; false otherwise
-  bool Generate(std::ostream& out);
+  bool Generate();
 
   /// Handles an array accessor expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the expression to emit
   /// @returns true if the array accessor was emitted
-  bool EmitArrayAccessor(std::ostream& pre,
-                         std::ostream& out,
-                         ast::ArrayAccessorExpression* expr);
+  bool EmitArrayAccessor(std::ostream& out, ast::ArrayAccessorExpression* 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(std::ostream& out, ast::AssignmentStatement* stmt);
+  bool EmitAssign(ast::AssignmentStatement* stmt);
   /// Handles generating a binary expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the binary expression
   /// @returns true if the expression was emitted, false otherwise
-  bool EmitBinary(std::ostream& pre,
-                  std::ostream& out,
-                  ast::BinaryExpression* expr);
+  bool EmitBinary(std::ostream& out, ast::BinaryExpression* expr);
   /// Handles generating a bitcast expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the as expression
   /// @returns true if the bitcast was emitted
-  bool EmitBitcast(std::ostream& pre,
-                   std::ostream& out,
-                   ast::BitcastExpression* expr);
+  bool EmitBitcast(std::ostream& out, ast::BitcastExpression* expr);
+  /// Emits a list of statements
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatements(const ast::StatementList& stmts);
+  /// Emits a list of statements with an indentation
+  /// @param stmts the statement list
+  /// @returns true if the statements were emitted successfully
+  bool EmitStatementsWithIndent(const ast::StatementList& stmts);
   /// 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(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(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(std::ostream& out, const ast::BlockStatement* stmt);
+  bool EmitBlock(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(std::ostream& out, ast::BreakStatement* stmt);
+  bool EmitBreak(ast::BreakStatement* stmt);
   /// Handles generating a call expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @returns true if the call expression is emitted
-  bool EmitCall(std::ostream& pre,
-                std::ostream& out,
-                ast::CallExpression* expr);
+  bool EmitCall(std::ostream& out, ast::CallExpression* expr);
   /// Handles generating a call expression to a
   /// transform::DecomposeMemoryAccess::Intrinsic for a uniform buffer
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
   /// @returns true if the call expression is emitted
   bool EmitUniformBufferAccess(
-      std::ostream& pre,
       std::ostream& out,
       ast::CallExpression* expr,
       const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
   /// Handles generating a call expression to a
   /// transform::DecomposeMemoryAccess::Intrinsic for a storage buffer
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
   /// @returns true if the call expression is emitted
   bool EmitStorageBufferAccess(
-      std::ostream& pre,
       std::ostream& out,
       ast::CallExpression* expr,
       const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
   /// Handles generating a barrier intrinsic call
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param intrinsic the semantic information for the barrier intrinsic
   /// @returns true if the call expression is emitted
-  bool EmitBarrierCall(std::ostream& pre,
-                       std::ostream& out,
-                       const sem::Intrinsic* intrinsic);
+  bool EmitBarrierCall(std::ostream& out, const sem::Intrinsic* intrinsic);
   /// Handles generating an atomic intrinsic call for a storage buffer variable
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param op the atomic op
   /// @returns true if the call expression is emitted
   bool EmitStorageAtomicCall(
-      std::ostream& pre,
       std::ostream& out,
       ast::CallExpression* expr,
       transform::DecomposeMemoryAccess::Intrinsic::Op op);
   /// Handles generating an atomic intrinsic call for a workgroup variable
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the semantic information for the atomic intrinsic
   /// @returns true if the call expression is emitted
-  bool EmitWorkgroupAtomicCall(std::ostream& pre,
-                               std::ostream& out,
+  bool EmitWorkgroupAtomicCall(std::ostream& out,
                                ast::CallExpression* expr,
                                const sem::Intrinsic* intrinsic);
   /// Handles generating a call to a texture function (`textureSample`,
   /// `textureSampleGrad`, etc)
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the semantic information for the texture intrinsic
   /// @returns true if the call expression is emitted
-  bool EmitTextureCall(std::ostream& pre,
-                       std::ostream& out,
+  bool EmitTextureCall(std::ostream& out,
                        ast::CallExpression* expr,
                        const sem::Intrinsic* intrinsic);
   /// Handles generating a call to the `select()` intrinsic
-  /// @param pre the preamble of the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @returns true if the call expression is emitted
-  bool EmitSelectCall(std::ostream& pre,
-                      std::ostream& out,
-                      ast::CallExpression* expr);
+  bool EmitSelectCall(std::ostream& out, ast::CallExpression* expr);
   /// Handles generating a call to the `frexp()` intrinsic
-  /// @param pre the preamble of the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the semantic information for the intrinsic
   /// @returns true if the call expression is emitted
-  bool EmitFrexpCall(std::ostream& pre,
-                     std::ostream& out,
+  bool EmitFrexpCall(std::ostream& out,
                      ast::CallExpression* expr,
                      const sem::Intrinsic* intrinsic);
   /// Handles generating a call to the `isNormal()` intrinsic
-  /// @param pre the preamble of the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the semantic information for the intrinsic
   /// @returns true if the call expression is emitted
-  bool EmitIsNormalCall(std::ostream& pre,
-                        std::ostream& out,
+  bool EmitIsNormalCall(std::ostream& out,
                         ast::CallExpression* expr,
                         const sem::Intrinsic* intrinsic);
   /// Handles generating a call to data packing intrinsic
-  /// @param pre the preamble of the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the semantic information for the texture intrinsic
   /// @returns true if the call expression is emitted
-  bool EmitDataPackingCall(std::ostream& pre,
-                           std::ostream& out,
+  bool EmitDataPackingCall(std::ostream& out,
                            ast::CallExpression* expr,
                            const sem::Intrinsic* intrinsic);
   /// Handles generating a call to data unpacking intrinsic
-  /// @param pre the preamble of the expression stream
   /// @param out the output of the expression stream
   /// @param expr the call expression
   /// @param intrinsic the semantic information for the texture intrinsic
   /// @returns true if the call expression is emitted
-  bool EmitDataUnpackingCall(std::ostream& pre,
-                             std::ostream& out,
+  bool EmitDataUnpackingCall(std::ostream& out,
                              ast::CallExpression* expr,
                              const sem::Intrinsic* intrinsic);
   /// Handles a case statement
-  /// @param out the output stream
   /// @param stmt the statement
-  /// @returns true if the statment was emitted successfully
-  bool EmitCase(std::ostream& out, ast::CaseStatement* stmt);
+  /// @returns true if the statement was emitted successfully
+  bool EmitCase(ast::CaseStatement* stmt);
   /// Handles generating constructor expressions
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the constructor expression
   /// @returns true if the expression was emitted
-  bool EmitConstructor(std::ostream& pre,
-                       std::ostream& out,
-                       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(std::ostream& out, ast::DiscardStatement* stmt);
+  bool EmitDiscard(ast::DiscardStatement* stmt);
   /// Handles generating a scalar constructor
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the scalar constructor expression
   /// @returns true if the scalar constructor is emitted
-  bool EmitScalarConstructor(std::ostream& pre,
-                             std::ostream& out,
+  bool EmitScalarConstructor(std::ostream& out,
                              ast::ScalarConstructorExpression* expr);
   /// Handles emitting a type constructor
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the type constructor expression
   /// @returns true if the constructor is emitted
-  bool EmitTypeConstructor(std::ostream& pre,
-                           std::ostream& out,
+  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(std::ostream& out, ast::ContinueStatement* stmt);
+  bool EmitContinue(ast::ContinueStatement* stmt);
   /// Handles generate an Expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the expression
   /// @returns true if the expression was emitted
-  bool EmitExpression(std::ostream& pre,
-                      std::ostream& out,
-                      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(std::ostream& out, ast::Function* func);
+  bool EmitFunction(ast::Function* func);
 
   /// Handles emitting a global variable
-  /// @param out the output stream
   /// @param global the global variable
   /// @returns true on success
-  bool EmitGlobalVariable(std::ostream& out, ast::Variable* global);
+  bool EmitGlobalVariable(ast::Variable* global);
 
   /// Handles emitting a global variable with the uniform storage class
-  /// @param out the output stream
   /// @param var the global variable
   /// @returns true on success
-  bool EmitUniformVariable(std::ostream& out, const sem::Variable* var);
+  bool EmitUniformVariable(const sem::Variable* var);
 
   /// Handles emitting a global variable with the storage storage class
-  /// @param out the output stream
   /// @param var the global variable
   /// @returns true on success
-  bool EmitStorageVariable(std::ostream& out, const sem::Variable* var);
+  bool EmitStorageVariable(const sem::Variable* var);
 
   /// Handles emitting a global variable with the handle storage class
-  /// @param out the output stream
   /// @param var the global variable
   /// @returns true on success
-  bool EmitHandleVariable(std::ostream& out, const sem::Variable* var);
+  bool EmitHandleVariable(const sem::Variable* var);
 
   /// Handles emitting a global variable with the private storage class
-  /// @param out the output stream
   /// @param var the global variable
   /// @returns true on success
-  bool EmitPrivateVariable(std::ostream& out, const sem::Variable* var);
+  bool EmitPrivateVariable(const sem::Variable* var);
 
   /// Handles emitting a global variable with the workgroup storage class
-  /// @param out the output stream
   /// @param var the global variable
   /// @returns true on success
-  bool EmitWorkgroupVariable(std::ostream& out, const sem::Variable* var);
+  bool EmitWorkgroupVariable(const sem::Variable* var);
 
   /// Handles emitting the entry point function
-  /// @param out the output stream
   /// @param func the entry point
   /// @returns true if the entry point function was emitted
-  bool EmitEntryPointFunction(std::ostream& out, ast::Function* func);
+  bool EmitEntryPointFunction(ast::Function* func);
   /// 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(std::ostream& out, ast::IfStatement* stmt);
+  bool EmitIf(ast::IfStatement* stmt);
   /// Handles a literal
   /// @param out the output stream
   /// @param lit the literal to emit
   /// @returns true if the literal was successfully emitted
   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(std::ostream& out, ast::LoopStatement* stmt);
+  bool EmitLoop(ast::LoopStatement* stmt);
   /// Handles generating an identifier expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the identifier expression
   /// @returns true if the identifeir was emitted
-  bool EmitIdentifier(std::ostream& pre,
-                      std::ostream& out,
-                      ast::IdentifierExpression* expr);
+  bool EmitIdentifier(std::ostream& out, ast::IdentifierExpression* expr);
   /// Handles a member accessor expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the member accessor expression
   /// @returns true if the member accessor was emitted
-  bool EmitMemberAccessor(std::ostream& pre,
-                          std::ostream& out,
+  bool EmitMemberAccessor(std::ostream& out,
                           ast::MemberAccessorExpression* expr);
   /// Handles return statements
-  /// @param out the output stream
   /// @param stmt the statement to emit
   /// @returns true if the statement was successfully emitted
-  bool EmitReturn(std::ostream& out, ast::ReturnStatement* stmt);
+  bool EmitReturn(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(std::ostream& out, ast::Statement* stmt);
+  bool EmitStatement(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(std::ostream& out, ast::SwitchStatement* stmt);
+  bool EmitSwitch(ast::SwitchStatement* stmt);
   /// Handles generating type
   /// @param out the output stream
   /// @param type the type to generate
@@ -396,33 +322,27 @@
                        ast::Access access,
                        const std::string& name);
   /// Handles generating a structure declaration
-  /// @param out the output stream
   /// @param ty the struct to generate
   /// @returns true if the struct is emitted
-  bool EmitStructType(std::ostream& out, const sem::Struct* ty);
+  bool EmitStructType(const sem::Struct* ty);
   /// Handles a unary op expression
-  /// @param pre the preamble for the expression stream
   /// @param out the output of the expression stream
   /// @param expr the expression to emit
   /// @returns true if the expression was emitted
-  bool EmitUnaryOp(std::ostream& pre,
-                   std::ostream& out,
-                   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(std::ostream& out, const sem::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(std::ostream& out, ast::Variable* var);
+  bool EmitVariable(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(std::ostream& out, const ast::Variable* var);
+  bool EmitProgramConstVariable(const ast::Variable* var);
 
   /// Handles generating a builtin method name
   /// @param intrinsic the semantic info for the intrinsic
@@ -466,29 +386,8 @@
     return builder_.TypeOf(type_decl);
   }
 
-  /// Emits `prefix`, followed by an opening brace `{`, then calls `cb` to emit
-  /// the block body, then finally emits the closing brace `}`.
-  /// @param out the output stream
-  /// @param prefix the string to emit before the opening brace
-  /// @param cb a function or function-like object with the signature `bool()`
-  /// that emits the block body.
-  /// @returns the return value of `cb`.
-  template <typename F>
-  bool EmitBlockBraces(std::ostream& out, const std::string& prefix, F&& cb);
-
-  /// Emits an opening brace `{`, then calls `cb` to emit the block body, then
-  /// finally emits the closing brace `}`.
-  /// @param out the output stream
-  /// @param cb a function or function-like object with the signature `bool()`
-  /// that emits the block body.
-  /// @returns the return value of `cb`.
-  template <typename F>
-  bool EmitBlockBraces(std::ostream& out, F&& cb) {
-    return EmitBlockBraces(out, "", std::forward<F>(cb));
-  }
-
   ProgramBuilder builder_;
-  std::function<bool(std::ostream& out)> emit_continuing_;
+  std::function<bool()> emit_continuing_;
   std::unordered_map<const sem::Struct*, std::string> structure_builders_;
 };
 
diff --git a/src/writer/hlsl/generator_impl_array_accessor_test.cc b/src/writer/hlsl/generator_impl_array_accessor_test.cc
index 53c42ad..b2fb021 100644
--- a/src/writer/hlsl/generator_impl_array_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_array_accessor_test.cc
@@ -28,8 +28,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "ary[5]");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "ary[5]");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_assign_test.cc b/src/writer/hlsl/generator_impl_assign_test.cc
index 54e957a..f5a0711 100644
--- a/src/writer/hlsl/generator_impl_assign_test.cc
+++ b/src/writer/hlsl/generator_impl_assign_test.cc
@@ -31,8 +31,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, assign)) << gen.error();
-  EXPECT_EQ(result(), "  lhs = rhs;\n");
+  ASSERT_TRUE(gen.EmitStatement(assign)) << gen.error();
+  EXPECT_EQ(gen.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 743bb2b..435ce1d 100644
--- a/src/writer/hlsl/generator_impl_binary_test.cc
+++ b/src/writer/hlsl/generator_impl_binary_test.cc
@@ -56,8 +56,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), params.result);
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
 }
 TEST_P(HlslBinaryTest, Emit_u32) {
   auto params = GetParam();
@@ -74,8 +75,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), params.result);
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
 }
 TEST_P(HlslBinaryTest, Emit_i32) {
   auto params = GetParam();
@@ -98,8 +100,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), params.result);
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), params.result);
 }
 INSTANTIATE_TEST_SUITE_P(
     HlslGeneratorImplTest,
@@ -133,8 +136,9 @@
 
   GeneratorImpl& gen = Build();
 
-  EXPECT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(),
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
             "(float3(1.0f, 1.0f, 1.0f) * "
             "1.0f)");
 }
@@ -150,8 +154,9 @@
 
   GeneratorImpl& gen = Build();
 
-  EXPECT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(),
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
             "(1.0f * float3(1.0f, 1.0f, "
             "1.0f))");
 }
@@ -167,8 +172,9 @@
 
   GeneratorImpl& gen = Build();
 
-  EXPECT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "(mat * 1.0f)");
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(mat * 1.0f)");
 }
 
 TEST_F(HlslGeneratorImplTest_Binary, Multiply_ScalarMatrix) {
@@ -182,8 +188,9 @@
 
   GeneratorImpl& gen = Build();
 
-  EXPECT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "(1.0f * mat)");
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(1.0f * mat)");
 }
 
 TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixVector) {
@@ -197,8 +204,9 @@
 
   GeneratorImpl& gen = Build();
 
-  EXPECT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "mul(float3(1.0f, 1.0f, 1.0f), mat)");
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "mul(float3(1.0f, 1.0f, 1.0f), mat)");
 }
 
 TEST_F(HlslGeneratorImplTest_Binary, Multiply_VectorMatrix) {
@@ -212,8 +220,9 @@
 
   GeneratorImpl& gen = Build();
 
-  EXPECT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "mul(mat, float3(1.0f, 1.0f, 1.0f))");
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "mul(mat, float3(1.0f, 1.0f, 1.0f))");
 }
 
 TEST_F(HlslGeneratorImplTest_Binary, Multiply_MatrixMatrix) {
@@ -226,8 +235,9 @@
 
   GeneratorImpl& gen = Build();
 
-  EXPECT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "mul(rhs, lhs)");
+  std::stringstream out;
+  EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "mul(rhs, lhs)");
 }
 
 TEST_F(HlslGeneratorImplTest_Binary, Logical_And) {
@@ -240,9 +250,10 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "(tint_tmp)");
-  EXPECT_EQ(pre_result(), R"(bool tint_tmp = a;
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
 if (tint_tmp) {
   tint_tmp = b;
 }
@@ -266,19 +277,20 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "(tint_tmp_1)");
-  EXPECT_EQ(pre_result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
+if (tint_tmp_1) {
+  tint_tmp_1 = b;
 }
-bool tint_tmp_1 = (tint_tmp);
-if (!tint_tmp_1) {
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
   bool tint_tmp_2 = c;
   if (!tint_tmp_2) {
     tint_tmp_2 = d;
   }
-  tint_tmp_1 = (tint_tmp_2);
+  tint_tmp = (tint_tmp_2);
 }
 )");
 }
@@ -293,9 +305,10 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), "(tint_tmp)");
-  EXPECT_EQ(pre_result(), R"(bool tint_tmp = a;
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), "(tint_tmp)");
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
 if (!tint_tmp) {
   tint_tmp = b;
 }
@@ -338,8 +351,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStatement(out, expr)) << gen.error();
-  EXPECT_EQ(result(), R"(bool tint_tmp = a;
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
 if (tint_tmp) {
   tint_tmp = b;
 }
@@ -375,16 +388,16 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStatement(out, expr)) << gen.error();
-  EXPECT_EQ(result(), R"(bool tint_tmp = a;
-if (tint_tmp) {
-  tint_tmp = b;
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = a;
+if (tint_tmp_1) {
+  tint_tmp_1 = b;
 }
-bool tint_tmp_1 = (tint_tmp);
-if (!tint_tmp_1) {
-  tint_tmp_1 = c;
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  tint_tmp = c;
 }
-return (tint_tmp_1);
+return (tint_tmp);
 )");
 }
 
@@ -406,16 +419,16 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStatement(out, expr)) << gen.error();
-  EXPECT_EQ(result(), R"(bool tint_tmp = b;
-if (!tint_tmp) {
-  tint_tmp = c;
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
+if (!tint_tmp_1) {
+  tint_tmp_1 = c;
 }
-bool tint_tmp_1 = (tint_tmp);
-if (tint_tmp_1) {
-  tint_tmp_1 = d;
+bool tint_tmp = (tint_tmp_1);
+if (tint_tmp) {
+  tint_tmp = d;
 }
-a = (tint_tmp_1);
+a = (tint_tmp);
 )");
 }
 
@@ -438,16 +451,16 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStatement(out, decl)) << gen.error();
-  EXPECT_EQ(result(), R"(bool tint_tmp = b;
-if (tint_tmp) {
-  tint_tmp = c;
+  ASSERT_TRUE(gen.EmitStatement(decl)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp_1 = b;
+if (tint_tmp_1) {
+  tint_tmp_1 = c;
 }
-bool tint_tmp_1 = (tint_tmp);
-if (!tint_tmp_1) {
-  tint_tmp_1 = d;
+bool tint_tmp = (tint_tmp_1);
+if (!tint_tmp) {
+  tint_tmp = d;
 }
-bool a = (tint_tmp_1);
+bool a = (tint_tmp);
 )");
 }
 
@@ -467,8 +480,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
-  EXPECT_EQ(pre_result(), R"(bool tint_tmp = a;
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
 if (tint_tmp) {
   bool tint_tmp_1 = b;
   if (!tint_tmp_1) {
@@ -477,7 +491,7 @@
   tint_tmp = (tint_tmp_1);
 }
 )");
-  EXPECT_EQ(result(), R"(asint((tint_tmp)))");
+  EXPECT_EQ(out.str(), R"(asint((tint_tmp)))");
 }
 
 TEST_F(HlslGeneratorImplTest_Binary, Call_WithLogical) {
@@ -512,8 +526,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStatement(out, expr)) << gen.error();
-  EXPECT_EQ(result(), R"(bool tint_tmp = a;
+  ASSERT_TRUE(gen.EmitStatement(expr)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(bool tint_tmp = a;
 if (tint_tmp) {
   tint_tmp = b;
 }
@@ -521,19 +535,19 @@
 if (!tint_tmp_1) {
   tint_tmp_1 = d;
 }
-bool tint_tmp_2 = a;
-if (!tint_tmp_2) {
-  tint_tmp_2 = c;
+bool tint_tmp_3 = a;
+if (!tint_tmp_3) {
+  tint_tmp_3 = c;
 }
-bool tint_tmp_3 = (tint_tmp_2);
-if (tint_tmp_3) {
+bool tint_tmp_2 = (tint_tmp_3);
+if (tint_tmp_2) {
   bool tint_tmp_4 = b;
   if (!tint_tmp_4) {
     tint_tmp_4 = d;
   }
-  tint_tmp_3 = (tint_tmp_4);
+  tint_tmp_2 = (tint_tmp_4);
 }
-foo((tint_tmp), (tint_tmp_1), (tint_tmp_3));
+foo((tint_tmp), (tint_tmp_1), (tint_tmp_2));
 )");
 }
 
diff --git a/src/writer/hlsl/generator_impl_bitcast_test.cc b/src/writer/hlsl/generator_impl_bitcast_test.cc
index 45de152..9cb97d8 100644
--- a/src/writer/hlsl/generator_impl_bitcast_test.cc
+++ b/src/writer/hlsl/generator_impl_bitcast_test.cc
@@ -27,8 +27,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, bitcast)) << gen.error();
-  EXPECT_EQ(result(), "asfloat(1)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "asfloat(1)");
 }
 
 TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Int) {
@@ -37,8 +38,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, bitcast)) << gen.error();
-  EXPECT_EQ(result(), "asint(1u)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "asint(1u)");
 }
 
 TEST_F(HlslGeneratorImplTest_Bitcast, EmitExpression_Bitcast_Uint) {
@@ -47,8 +49,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, bitcast)) << gen.error();
-  EXPECT_EQ(result(), "asuint(1)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, bitcast)) << gen.error();
+  EXPECT_EQ(out.str(), "asuint(1)");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_block_test.cc b/src/writer/hlsl/generator_impl_block_test.cc
index 535304b..4aec6a3 100644
--- a/src/writer/hlsl/generator_impl_block_test.cc
+++ b/src/writer/hlsl/generator_impl_block_test.cc
@@ -29,27 +29,13 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, b)) << gen.error();
-  EXPECT_EQ(result(), R"(  {
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  {
     discard;
   }
 )");
 }
 
-TEST_F(HlslGeneratorImplTest_Block, Emit_Block_WithoutNewline) {
-  auto* b = Block(create<ast::DiscardStatement>());
-  WrapInFunction(b);
-
-  GeneratorImpl& gen = Build();
-
-  gen.increment_indent();
-
-  ASSERT_TRUE(gen.EmitBlock(out, b)) << gen.error();
-  EXPECT_EQ(result(), R"({
-    discard;
-  })");
-}
-
 }  // namespace
 }  // namespace hlsl
 }  // namespace writer
diff --git a/src/writer/hlsl/generator_impl_break_test.cc b/src/writer/hlsl/generator_impl_break_test.cc
index dbf3241..b2f532f 100644
--- a/src/writer/hlsl/generator_impl_break_test.cc
+++ b/src/writer/hlsl/generator_impl_break_test.cc
@@ -29,8 +29,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, b)) << gen.error();
-  EXPECT_EQ(result(), "  break;\n");
+  ASSERT_TRUE(gen.EmitStatement(b)) << gen.error();
+  EXPECT_EQ(gen.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 eadfcd4..a0391e5 100644
--- a/src/writer/hlsl/generator_impl_call_test.cc
+++ b/src/writer/hlsl/generator_impl_call_test.cc
@@ -31,8 +31,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_EQ(result(), "my_func()");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func()");
 }
 
 TEST_F(HlslGeneratorImplTest_Call, EmitExpression_Call_WithParams) {
@@ -50,8 +51,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_EQ(result(), "my_func(param1, param2)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "my_func(param1, param2)");
 }
 
 TEST_F(HlslGeneratorImplTest_Call, EmitStatement_Call) {
@@ -70,8 +72,8 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(out, call)) << gen.error();
-  EXPECT_EQ(result(), "  my_func(param1, param2);\n");
+  ASSERT_TRUE(gen.EmitStatement(call)) << gen.error();
+  EXPECT_EQ(gen.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 b892acb..93e80a6 100644
--- a/src/writer/hlsl/generator_impl_case_test.cc
+++ b/src/writer/hlsl/generator_impl_case_test.cc
@@ -33,8 +33,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitCase(out, c)) << gen.error();
-  EXPECT_EQ(result(), R"(  case 5: {
+  ASSERT_TRUE(gen.EmitCase(c)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
     break;
   }
 )");
@@ -50,8 +50,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitCase(out, c)) << gen.error();
-  EXPECT_EQ(result(), R"(  case 5: {
+  ASSERT_TRUE(gen.EmitCase(c)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
     break;
   }
 )");
@@ -68,8 +68,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitCase(out, c)) << gen.error();
-  EXPECT_EQ(result(), R"(  case 5: {
+  ASSERT_TRUE(gen.EmitCase(c)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5: {
     /* fallthrough */
   }
 )");
@@ -87,8 +87,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitCase(out, c)) << gen.error();
-  EXPECT_EQ(result(), R"(  case 5:
+  ASSERT_TRUE(gen.EmitCase(c)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  case 5:
   case 6: {
     break;
   }
@@ -104,8 +104,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitCase(out, c)) << gen.error();
-  EXPECT_EQ(result(), R"(  default: {
+  ASSERT_TRUE(gen.EmitCase(c)) << gen.error();
+  EXPECT_EQ(gen.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 f123536..b281f4b 100644
--- a/src/writer/hlsl/generator_impl_cast_test.cc
+++ b/src/writer/hlsl/generator_impl_cast_test.cc
@@ -27,8 +27,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, cast)) << gen.error();
-  EXPECT_EQ(result(), "float(1)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "float(1)");
 }
 
 TEST_F(HlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) {
@@ -37,8 +38,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, cast)) << gen.error();
-  EXPECT_EQ(result(), "float3(int3(1, 2, 3))");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
+  EXPECT_EQ(out.str(), "float3(int3(1, 2, 3))");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_constructor_test.cc b/src/writer/hlsl/generator_impl_constructor_test.cc
index 9d18226..429b86d 100644
--- a/src/writer/hlsl/generator_impl_constructor_test.cc
+++ b/src/writer/hlsl/generator_impl_constructor_test.cc
@@ -29,8 +29,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("false"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("false"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Int) {
@@ -38,8 +38,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("-12345"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("-12345"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_UInt) {
@@ -47,8 +47,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("56779u"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("56779u"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Float) {
@@ -57,8 +57,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("1073741824.0f"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Float) {
@@ -66,8 +66,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("float(-0.000012f)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) {
@@ -75,8 +75,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("bool(true)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) {
@@ -84,8 +84,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("int(-12345)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) {
@@ -93,8 +93,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("uint(12345u)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) {
@@ -102,8 +102,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("float3(1.0f, 2.0f, 3.0f)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3(1.0f, 2.0f, 3.0f)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_Empty) {
@@ -111,8 +111,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("float3(0.0f, 0.0f, 0.0f)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3(0.0f, 0.0f, 0.0f)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor,
@@ -121,8 +121,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("float3((2.0f).xxx)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("float3((2.0f).xxx)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor,
@@ -131,8 +131,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("bool3((true).xxx)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("bool3((true).xxx)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor,
@@ -141,8 +141,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("int3((2).xxx)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int3((2).xxx)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor,
@@ -151,8 +151,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("uint3((2u).xxx)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint3((2u).xxx)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Mat) {
@@ -161,10 +161,10 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
   EXPECT_THAT(
-      result(),
+      gen.result(),
       HasSubstr(
           "float2x3(float3(1.0f, 2.0f, 3.0f), float3(3.0f, 4.0f, 5.0f))"));
 }
@@ -174,9 +174,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  EXPECT_THAT(result(),
+  EXPECT_THAT(gen.result(),
               HasSubstr("float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)"));
 }
 
@@ -187,8 +187,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
               HasSubstr("{float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f),"
                         " float3(7.0f, 8.0f, 9.0f)}"));
 }
@@ -200,8 +200,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
               HasSubstr("{float3(0.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 0.0f),"
                         " float3(0.0f, 0.0f, 0.0f)}"));
 }
@@ -217,8 +217,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("{1, 2.0f, int3(3, 4, 5)}"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("{1, 2.0f, int3(3, 4, 5)}"));
 }
 
 TEST_F(HlslGeneratorImplTest_Constructor, EmitConstructor_Type_Struct_Empty) {
@@ -232,8 +232,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("{0, 0.0f, int3(0, 0, 0)}"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("{0, 0.0f, int3(0, 0, 0)}"));
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_continue_test.cc b/src/writer/hlsl/generator_impl_continue_test.cc
index 6a2482b..e312b82 100644
--- a/src/writer/hlsl/generator_impl_continue_test.cc
+++ b/src/writer/hlsl/generator_impl_continue_test.cc
@@ -29,8 +29,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, loop)) << gen.error();
-  EXPECT_EQ(result(), R"(  while (true) {
+  ASSERT_TRUE(gen.EmitStatement(loop)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
     continue;
   }
 )");
diff --git a/src/writer/hlsl/generator_impl_discard_test.cc b/src/writer/hlsl/generator_impl_discard_test.cc
index e738ef7..e7a5cca 100644
--- a/src/writer/hlsl/generator_impl_discard_test.cc
+++ b/src/writer/hlsl/generator_impl_discard_test.cc
@@ -29,8 +29,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, stmt)) << gen.error();
-  EXPECT_EQ(result(), "  discard;\n");
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  discard;\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index 7af36c4..6150010 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -38,8 +38,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(  void my_func() {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  void my_func() {
     return;
   }
 )");
@@ -55,8 +55,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(  void tint_symbol() {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(  void tint_symbol() {
     return;
   })"));
 }
@@ -72,8 +72,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(  void my_func(float a, int b) {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  void my_func(float a, int b) {
     return;
   }
 )");
@@ -88,8 +88,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(void main() {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void main() {
   return;
 }
 )");
@@ -104,8 +104,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(float f(inout float foo) {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(float f(inout float foo) {
   return foo;
 }
 )"));
@@ -122,8 +122,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(struct tint_symbol_1 {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct tint_symbol_1 {
   float foo : TEXCOORD0;
 };
 struct tint_symbol_2 {
@@ -152,8 +152,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(struct tint_symbol_1 {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct tint_symbol_1 {
   float4 coord : SV_Position;
 };
 struct tint_symbol_2 {
@@ -206,8 +206,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(struct Interface {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct Interface {
   float4 pos;
   float col1;
   float col2;
@@ -275,8 +275,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(struct VertexOutput {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct VertexOutput {
   float4 pos;
 };
 
@@ -340,8 +340,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(cbuffer cbuffer_ubo : register(b0, space1) {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_ubo : register(b0, space1) {
   uint4 ubo[1];
 };
 
@@ -381,8 +381,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(cbuffer cbuffer_uniforms : register(b0, space1) {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_uniforms : register(b0, space1) {
   uint4 uniforms[1];
 };
 
@@ -423,8 +423,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
             R"(RWByteAddressBuffer coord : register(u0, space1);
 
 void frag_main() {
@@ -463,8 +463,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
             R"(ByteAddressBuffer coord : register(t0, space1);
 
 void frag_main() {
@@ -500,8 +500,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
             R"(RWByteAddressBuffer coord : register(u0, space1);
 
 void frag_main() {
@@ -538,8 +538,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
             R"(RWByteAddressBuffer coord : register(u0, space1);
 
 void frag_main() {
@@ -578,8 +578,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(cbuffer cbuffer_coord : register(b0, space1) {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(cbuffer cbuffer_coord : register(b0, space1) {
   uint4 coord[1];
 };
 
@@ -624,8 +624,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(),
             R"(RWByteAddressBuffer coord : register(u0, space1);
 
 float sub_func(float param) {
@@ -648,8 +648,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(void tint_symbol() {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void tint_symbol() {
   return;
 }
 )");
@@ -666,8 +666,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"([numthreads(1, 1, 1)]
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
 void main() {
   return;
 }
@@ -684,8 +684,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"([numthreads(2, 4, 6)]
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(2, 4, 6)]
 void main() {
   return;
 }
@@ -705,8 +705,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(static const int width = int(2);
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(static const int width = int(2);
 static const int height = int(3);
 static const int depth = int(4);
 
@@ -730,8 +730,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(#ifndef WGSL_SPEC_CONSTANT_7
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_7
 #define WGSL_SPEC_CONSTANT_7 int(2)
 #endif
 static const int width = WGSL_SPEC_CONSTANT_7;
@@ -759,8 +759,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
 struct tint_array_wrapper {
   float arr[5];
 };
@@ -779,8 +779,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
 struct tint_array_wrapper {
   float arr[5];
 };
@@ -851,8 +851,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(RWByteAddressBuffer data : register(u0, space0);
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(RWByteAddressBuffer data : register(u0, space0);
 
 [numthreads(1, 1, 1)]
 void a() {
diff --git a/src/writer/hlsl/generator_impl_identifier_test.cc b/src/writer/hlsl/generator_impl_identifier_test.cc
index 161cab0..4929bc9 100644
--- a/src/writer/hlsl/generator_impl_identifier_test.cc
+++ b/src/writer/hlsl/generator_impl_identifier_test.cc
@@ -29,8 +29,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, i)) << gen.error();
-  EXPECT_EQ(result(), "foo");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, i)) << gen.error();
+  EXPECT_EQ(out.str(), "foo");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_if_test.cc b/src/writer/hlsl/generator_impl_if_test.cc
index f2aece3..e40f842 100644
--- a/src/writer/hlsl/generator_impl_if_test.cc
+++ b/src/writer/hlsl/generator_impl_if_test.cc
@@ -32,8 +32,8 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitStatement(out, i)) << gen.error();
-  EXPECT_EQ(result(), R"(  if (cond) {
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
     return;
   }
 )");
@@ -57,8 +57,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, i)) << gen.error();
-  EXPECT_EQ(result(), R"(  if (cond) {
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
     return;
   } else {
     if (else_cond) {
@@ -84,8 +84,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, i)) << gen.error();
-  EXPECT_EQ(result(), R"(  if (cond) {
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
     return;
   } else {
     return;
@@ -116,8 +116,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, i)) << gen.error();
-  EXPECT_EQ(result(), R"(  if (cond) {
+  ASSERT_TRUE(gen.EmitStatement(i)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  if (cond) {
     return;
   } else {
     if (else_cond) {
diff --git a/src/writer/hlsl/generator_impl_import_test.cc b/src/writer/hlsl/generator_impl_import_test.cc
index 201f345..ec81c41 100644
--- a/src/writer/hlsl/generator_impl_import_test.cc
+++ b/src/writer/hlsl/generator_impl_import_test.cc
@@ -40,8 +40,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1.0f)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f)");
 }
 INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_SingleParamTest,
@@ -78,8 +79,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1)");
 }
 INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_SingleIntParamTest,
@@ -95,8 +97,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(),
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
             std::string(param.hlsl_name) + "(float3(1.0f, 2.0f, 3.0f))");
 }
 INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
@@ -136,8 +139,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1.0f, 2.0f)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f, 2.0f)");
 }
 INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_DualParamTest,
@@ -159,8 +163,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(),
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(),
             std::string(param.hlsl_name) +
                 "(float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f))");
 }
@@ -177,8 +182,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1, 2)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1, 2)");
 }
 INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_DualParam_Int_Test,
@@ -194,8 +200,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1.0f, 2.0f, 3.0f)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1.0f, 2.0f, 3.0f)");
 }
 INSTANTIATE_TEST_SUITE_P(
     HlslGeneratorImplTest_Import,
@@ -218,8 +225,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), std::string(param.hlsl_name) + "(1, 2, 3)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), std::string(param.hlsl_name) + "(1, 2, 3)");
 }
 INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest_Import,
                          HlslImportData_TripleParam_Int_Test,
@@ -233,8 +241,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitCall(pre, out, expr)) << gen.error();
-  EXPECT_EQ(result(), std::string("determinant(var)"));
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitCall(out, expr)) << gen.error();
+  EXPECT_EQ(out.str(), 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 0e5a619..4ec4192 100644
--- a/src/writer/hlsl/generator_impl_intrinsic_test.cc
+++ b/src/writer/hlsl/generator_impl_intrinsic_test.cc
@@ -268,8 +268,9 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_EQ(result(), "dot(param1, param2)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "dot(param1, param2)");
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Select_Scalar) {
@@ -278,8 +279,9 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_EQ(result(), "(true ? 1.0f : 2.0f)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "(true ? 1.0f : 2.0f)");
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Select_Vector) {
@@ -289,8 +291,9 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_EQ(result(), "(bool2(true, false) ? int2(1, 2) : int2(3, 4))");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_EQ(out.str(), "(bool2(true, false) ? int2(1, 2) : int2(3, 4))");
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Modf_Scalar) {
@@ -300,8 +303,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("modf(1.0f, res)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("modf(1.0f, res)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Modf_Vector) {
@@ -311,8 +314,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("modf(float3(0.0f, 0.0f, 0.0f), res)"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("modf(float3(0.0f, 0.0f, 0.0f), res)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Frexp_Scalar_i32) {
@@ -322,8 +325,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
   float tint_tmp;
   float tint_tmp_1 = frexp(1.0f, tint_tmp);
   exp = int(tint_tmp);
@@ -338,8 +341,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
   float3 tint_tmp;
   float3 tint_tmp_1 = frexp(float3(0.0f, 0.0f, 0.0f), tint_tmp);
   res = int3(tint_tmp);
@@ -354,8 +357,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
   uint tint_isnormal_exponent = asuint(val) & 0x7f80000;
   uint tint_isnormal_clamped = clamp(tint_isnormal_exponent, 0x0080000, 0x7f00000);
   (tint_isnormal_clamped == tint_isnormal_exponent);
@@ -369,8 +372,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(
   uint3 tint_isnormal_exponent = asuint(val) & 0x7f80000;
   uint3 tint_isnormal_clamped = clamp(tint_isnormal_exponent, 0x0080000, 0x7f00000);
   (tint_isnormal_clamped == tint_isnormal_exponent);
@@ -384,11 +387,12 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("int4 tint_tmp = int4(round(clamp(p1, "
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int4 tint_tmp = int4(round(clamp(p1, "
                                       "-1.0, 1.0) * 127.0)) & 0xff;"));
-  EXPECT_THAT(result(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 8 | "
-                                  "tint_tmp.z << 16 | tint_tmp.w << 24)"));
+  EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 8 | "
+                                   "tint_tmp.z << 16 | tint_tmp.w << 24)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Pack4x8Unorm) {
@@ -398,11 +402,12 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("uint4 tint_tmp = uint4(round(clamp(p1, "
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint4 tint_tmp = uint4(round(clamp(p1, "
                                       "0.0, 1.0) * 255.0));"));
-  EXPECT_THAT(result(), HasSubstr("(tint_tmp.x | tint_tmp.y << 8 | "
-                                  "tint_tmp.z << 16 | tint_tmp.w << 24)"));
+  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 8 | "
+                                   "tint_tmp.z << 16 | tint_tmp.w << 24)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Pack2x16Snorm) {
@@ -412,10 +417,11 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("int2 tint_tmp = int2(round(clamp(p1, "
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int2 tint_tmp = int2(round(clamp(p1, "
                                       "-1.0, 1.0) * 32767.0)) & 0xffff;"));
-  EXPECT_THAT(result(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 16)"));
+  EXPECT_THAT(out.str(), HasSubstr("asuint(tint_tmp.x | tint_tmp.y << 16)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Pack2x16Unorm) {
@@ -425,10 +431,11 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("uint2 tint_tmp = uint2(round(clamp(p1, "
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = uint2(round(clamp(p1, "
                                       "0.0, 1.0) * 65535.0));"));
-  EXPECT_THAT(result(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
+  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Pack2x16Float) {
@@ -438,9 +445,10 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("uint2 tint_tmp = f32tof16(p1);"));
-  EXPECT_THAT(result(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint2 tint_tmp = f32tof16(p1);"));
+  EXPECT_THAT(out.str(), HasSubstr("(tint_tmp.x | tint_tmp.y << 16)"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack4x8Snorm) {
@@ -450,12 +458,13 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("int tint_tmp_1 = int(p1);"));
-  EXPECT_THAT(pre_result(),
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);"));
+  EXPECT_THAT(gen.result(),
               HasSubstr("int4 tint_tmp = int4(tint_tmp_1 << 24, tint_tmp_1 "
                         "<< 16, tint_tmp_1 << 8, tint_tmp_1) >> 24;"));
-  EXPECT_THAT(result(),
+  EXPECT_THAT(out.str(),
               HasSubstr("clamp(float4(tint_tmp) / 127.0, -1.0, 1.0)"));
 }
 
@@ -466,13 +475,14 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("uint tint_tmp_1 = p1;"));
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;"));
   EXPECT_THAT(
-      pre_result(),
+      gen.result(),
       HasSubstr("uint4 tint_tmp = uint4(tint_tmp_1 & 0xff, (tint_tmp_1 >> "
                 "8) & 0xff, (tint_tmp_1 >> 16) & 0xff, tint_tmp_1 >> 24);"));
-  EXPECT_THAT(result(), HasSubstr("float4(tint_tmp) / 255.0"));
+  EXPECT_THAT(out.str(), HasSubstr("float4(tint_tmp) / 255.0"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack2x16Snorm) {
@@ -482,12 +492,13 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("int tint_tmp_1 = int(p1);"));
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("int tint_tmp_1 = int(p1);"));
   EXPECT_THAT(
-      pre_result(),
+      gen.result(),
       HasSubstr("int2 tint_tmp = int2(tint_tmp_1 << 16, tint_tmp_1) >> 16;"));
-  EXPECT_THAT(result(),
+  EXPECT_THAT(out.str(),
               HasSubstr("clamp(float2(tint_tmp) / 32767.0, -1.0, 1.0)"));
 }
 
@@ -498,12 +509,13 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("uint tint_tmp_1 = p1;"));
-  EXPECT_THAT(pre_result(),
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp_1 = p1;"));
+  EXPECT_THAT(gen.result(),
               HasSubstr("uint2 tint_tmp = uint2(tint_tmp_1 & 0xffff, "
                         "tint_tmp_1 >> 16);"));
-  EXPECT_THAT(result(), HasSubstr("float2(tint_tmp) / 65535.0"));
+  EXPECT_THAT(out.str(), HasSubstr("float2(tint_tmp) / 65535.0"));
 }
 
 TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack2x16Float) {
@@ -513,9 +525,10 @@
   GeneratorImpl& gen = Build();
 
   gen.increment_indent();
-  ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
-  EXPECT_THAT(pre_result(), HasSubstr("uint tint_tmp = p1;"));
-  EXPECT_THAT(result(),
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("uint tint_tmp = p1;"));
+  EXPECT_THAT(out.str(),
               HasSubstr("f16tof32(uint2(tint_tmp & 0xffff, tint_tmp >> 16))"));
 }
 
@@ -526,8 +539,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"([numthreads(1, 1, 1)]
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
 void main() {
   DeviceMemoryBarrierWithGroupSync();
   return;
@@ -542,8 +555,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"([numthreads(1, 1, 1)]
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"([numthreads(1, 1, 1)]
 void main() {
   GroupMemoryBarrierWithGroupSync();
   return;
@@ -561,8 +574,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(int f(int a, int b, int c) {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(int f(int a, int b, int c) {
   return ((a + b) * c);
 }
 
diff --git a/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc b/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
index 82475fc..b868e74 100644
--- a/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
+++ b/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
@@ -381,12 +381,12 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
   auto expected = expected_texture_overload(param.overload);
 
-  EXPECT_THAT(result(), HasSubstr(expected.pre));
-  EXPECT_THAT(result(), HasSubstr(expected.out));
+  EXPECT_THAT(gen.result(), HasSubstr(expected.pre));
+  EXPECT_THAT(gen.result(), HasSubstr(expected.out));
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/src/writer/hlsl/generator_impl_loop_test.cc b/src/writer/hlsl/generator_impl_loop_test.cc
index 906d56e..a775429 100644
--- a/src/writer/hlsl/generator_impl_loop_test.cc
+++ b/src/writer/hlsl/generator_impl_loop_test.cc
@@ -33,8 +33,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, l)) << gen.error();
-  EXPECT_EQ(result(), R"(  while (true) {
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
     discard;
   }
 )");
@@ -51,8 +51,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, l)) << gen.error();
-  EXPECT_EQ(result(), R"(  while (true) {
+  ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
     discard;
     {
       return;
@@ -83,8 +83,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, outer)) << gen.error();
-  EXPECT_EQ(result(), R"(  while (true) {
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
     while (true) {
       discard;
       {
@@ -137,8 +137,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, outer)) << gen.error();
-  EXPECT_EQ(result(), R"(  while (true) {
+  ASSERT_TRUE(gen.EmitStatement(outer)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(  while (true) {
     float lhs = 2.400000095f;
     float other = 0.0f;
     {
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
index 203551d..2afc81a 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -131,8 +131,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(struct Data {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct Data {
   float mem;
 };
 
@@ -181,8 +181,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(p.expected));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -254,8 +254,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(p.expected));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(p.expected));
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -339,7 +339,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(void tint_symbol_1(RWByteAddressBuffer buffer, uint offset, float2x3 value) {
   buffer.Store3((offset + 0u), asuint(value[0u]));
@@ -353,7 +353,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
@@ -378,7 +378,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -387,7 +387,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
@@ -410,7 +410,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -419,7 +419,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
@@ -443,7 +443,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -452,7 +452,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_ToArray) {
@@ -473,7 +473,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -482,7 +482,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Load_MultiLevel) {
@@ -514,7 +514,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -523,7 +523,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
@@ -558,7 +558,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -567,7 +567,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
@@ -602,7 +602,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -611,7 +611,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
@@ -646,7 +646,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -655,7 +655,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, StorageBuffer_Store_MultiLevel) {
@@ -686,7 +686,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -695,7 +695,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
@@ -730,7 +730,7 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
   auto* expected =
       R"(RWByteAddressBuffer data : register(u0, space1);
 
@@ -739,7 +739,7 @@
   return;
 }
 )";
-  EXPECT_EQ(result(), expected);
+  EXPECT_EQ(gen.result(), expected);
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_xyz) {
@@ -749,8 +749,8 @@
   WrapInFunction(var, expr);
 
   GeneratorImpl& gen = SanitizeAndBuild();
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("my_vec.xyz"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("my_vec.xyz"));
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, Swizzle_gbr) {
@@ -760,8 +760,8 @@
   WrapInFunction(var, expr);
 
   GeneratorImpl& gen = SanitizeAndBuild();
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("my_vec.gbr"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("my_vec.gbr"));
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_module_constant_test.cc b/src/writer/hlsl/generator_impl_module_constant_test.cc
index 8ff9c88..f84f31d 100644
--- a/src/writer/hlsl/generator_impl_module_constant_test.cc
+++ b/src/writer/hlsl/generator_impl_module_constant_test.cc
@@ -28,8 +28,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitProgramConstVariable(out, var)) << gen.error();
-  EXPECT_EQ(result(), "static const float pos[3] = {1.0f, 2.0f, 3.0f};\n");
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), "static const float pos[3] = {1.0f, 2.0f, 3.0f};\n");
 }
 
 TEST_F(HlslGeneratorImplTest_ModuleConstant, Emit_SpecConstant) {
@@ -40,8 +40,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitProgramConstVariable(out, var)) << gen.error();
-  EXPECT_EQ(result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
 #define WGSL_SPEC_CONSTANT_23 3.0f
 #endif
 static const float pos = WGSL_SPEC_CONSTANT_23;
@@ -56,8 +56,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitProgramConstVariable(out, var)) << gen.error();
-  EXPECT_EQ(result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
+  ASSERT_TRUE(gen.EmitProgramConstVariable(var)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_23
 #error spec constant required for constant id 23
 #endif
 static const float pos = WGSL_SPEC_CONSTANT_23;
@@ -76,9 +76,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitProgramConstVariable(out, a)) << gen.error();
-  ASSERT_TRUE(gen.EmitProgramConstVariable(out, b)) << gen.error();
-  EXPECT_EQ(result(), R"(#ifndef WGSL_SPEC_CONSTANT_0
+  ASSERT_TRUE(gen.EmitProgramConstVariable(a)) << gen.error();
+  ASSERT_TRUE(gen.EmitProgramConstVariable(b)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#ifndef WGSL_SPEC_CONSTANT_0
 #define WGSL_SPEC_CONSTANT_0 3.0f
 #endif
 static const float a = WGSL_SPEC_CONSTANT_0;
diff --git a/src/writer/hlsl/generator_impl_return_test.cc b/src/writer/hlsl/generator_impl_return_test.cc
index 5a4bf74..177ac25 100644
--- a/src/writer/hlsl/generator_impl_return_test.cc
+++ b/src/writer/hlsl/generator_impl_return_test.cc
@@ -29,8 +29,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, r)) << gen.error();
-  EXPECT_EQ(result(), "  return;\n");
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return;\n");
 }
 
 TEST_F(HlslGeneratorImplTest_Return, Emit_ReturnWithValue) {
@@ -41,8 +41,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, r)) << gen.error();
-  EXPECT_EQ(result(), "  return 123;\n");
+  ASSERT_TRUE(gen.EmitStatement(r)) << gen.error();
+  EXPECT_EQ(gen.result(), "  return 123;\n");
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/generator_impl_sanitizer_test.cc b/src/writer/hlsl/generator_impl_sanitizer_test.cc
index e4e85c0..99cd52a 100644
--- a/src/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -45,9 +45,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
 
 void a_func() {
@@ -85,9 +85,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
 
 void a_func() {
@@ -127,9 +127,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(ByteAddressBuffer b : register(t1, space2);
 
 void a_func() {
@@ -159,9 +159,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(struct tint_array_wrapper {
   int arr[4];
 };
@@ -196,9 +196,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(struct S {
   int a;
   float3 b;
@@ -235,9 +235,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(void main() {
   int v = 0;
   int x = v;
@@ -278,9 +278,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(void main() {
   float4x4 m = float4x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
   float f = m[2][1];
@@ -322,9 +322,9 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  ASSERT_TRUE(gen.Generate()) << gen.error();
 
-  auto got = result();
+  auto got = gen.result();
   auto* expect = R"(int x(inout int p) {
   return p;
 }
diff --git a/src/writer/hlsl/generator_impl_switch_test.cc b/src/writer/hlsl/generator_impl_switch_test.cc
index 6412524..c0a92cd 100644
--- a/src/writer/hlsl/generator_impl_switch_test.cc
+++ b/src/writer/hlsl/generator_impl_switch_test.cc
@@ -46,8 +46,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, s)) << gen.error();
-  EXPECT_EQ(result(), R"(  switch(cond) {
+  ASSERT_TRUE(gen.EmitStatement(s)) << gen.error();
+  EXPECT_EQ(gen.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 adf73e4..30cf659 100644
--- a/src/writer/hlsl/generator_impl_test.cc
+++ b/src/writer/hlsl/generator_impl_test.cc
@@ -24,7 +24,7 @@
 TEST_F(HlslGeneratorImplTest, ErrorIfSanitizerNotRun) {
   auto program = std::make_unique<Program>(std::move(*this));
   GeneratorImpl gen(program.get());
-  EXPECT_FALSE(gen.Generate(out));
+  EXPECT_FALSE(gen.Generate());
   EXPECT_EQ(
       gen.error(),
       "error: HLSL writer requires the transform::Hlsl sanitizer to have been "
@@ -37,8 +37,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(void my_func() {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(void my_func() {
 }
 )");
 }
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 98709a0..feedf4e 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -37,10 +37,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
                            ast::Access::kReadWrite, "ary"))
       << gen.error();
-  EXPECT_EQ(result(), "bool ary[4]");
+  EXPECT_EQ(out.str(), "bool ary[4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArray) {
@@ -49,10 +50,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
                            ast::Access::kReadWrite, "ary"))
       << gen.error();
-  EXPECT_EQ(result(), "bool ary[5][4]");
+  EXPECT_EQ(out.str(), "bool ary[5][4]");
 }
 
 // TODO(dsinclair): Is this possible? What order should it output in?
@@ -63,10 +65,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
                            ast::Access::kReadWrite, "ary"))
       << gen.error();
-  EXPECT_EQ(result(), "bool ary[5][4][1]");
+  EXPECT_EQ(out.str(), "bool ary[5][4][1]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_ArrayOfArrayOfArray) {
@@ -75,10 +78,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
                            ast::Access::kReadWrite, "ary"))
       << gen.error();
-  EXPECT_EQ(result(), "bool ary[6][5][4]");
+  EXPECT_EQ(out.str(), "bool ary[6][5][4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
@@ -87,10 +91,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, program->TypeOf(arr), ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "bool[4]");
+  EXPECT_EQ(out.str(), "bool[4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Bool) {
@@ -98,10 +103,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, bool_, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "bool");
+  EXPECT_EQ(out.str(), "bool");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_F32) {
@@ -109,10 +115,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, f32, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "float");
+  EXPECT_EQ(out.str(), "float");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_I32) {
@@ -120,10 +127,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, i32, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "int");
+  EXPECT_EQ(out.str(), "int");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Matrix) {
@@ -133,10 +141,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, mat2x3, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "float2x3");
+  EXPECT_EQ(out.str(), "float2x3");
 }
 
 // TODO(dsinclair): How to annotate as workgroup?
@@ -147,10 +156,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, p, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "float*");
+  EXPECT_EQ(out.str(), "float*");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl) {
@@ -163,8 +173,8 @@
   GeneratorImpl& gen = Build();
 
   auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(out, sem_s)) << gen.error();
-  EXPECT_EQ(result(), R"(struct S {
+  ASSERT_TRUE(gen.EmitStructType(sem_s)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(struct S {
   int a;
   float b;
 };
@@ -187,8 +197,8 @@
   GeneratorImpl& gen = Build();
 
   auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
-  ASSERT_TRUE(gen.EmitStructType(out, sem_s)) << gen.error();
-  EXPECT_EQ(result(), "");
+  ASSERT_TRUE(gen.EmitStructType(sem_s)) << gen.error();
+  EXPECT_EQ(gen.result(), "");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct) {
@@ -201,10 +211,11 @@
   GeneratorImpl& gen = Build();
 
   auto* sem_s = program->TypeOf(s)->As<sem::Struct>();
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, sem_s, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "S");
+  EXPECT_EQ(out.str(), "S");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
@@ -216,8 +227,8 @@
 
   GeneratorImpl& gen = SanitizeAndBuild();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(struct S {
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(struct S {
   int tint_symbol;
   float tint_symbol_1;
 };
@@ -229,10 +240,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, u32, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "uint");
+  EXPECT_EQ(out.str(), "uint");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Vector) {
@@ -241,10 +253,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, vec3, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "float3");
+  EXPECT_EQ(out.str(), "float3");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Void) {
@@ -252,10 +265,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, void_, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "void");
+  EXPECT_EQ(out.str(), "void");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitSampler) {
@@ -263,10 +277,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "SamplerState");
+  EXPECT_EQ(out.str(), "SamplerState");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitSamplerComparison) {
@@ -274,10 +289,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, sampler, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "SamplerComparisonState");
+  EXPECT_EQ(out.str(), "SamplerComparisonState");
 }
 
 struct HlslDepthTextureData {
@@ -305,8 +321,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(params.result));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
 }
 INSTANTIATE_TEST_SUITE_P(
     HlslGeneratorImplTest_Type,
@@ -361,8 +377,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(params.result));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
 }
 INSTANTIATE_TEST_SUITE_P(
     HlslGeneratorImplTest_Type,
@@ -465,10 +481,11 @@
 
   GeneratorImpl& gen = Build();
 
+  std::stringstream out;
   ASSERT_TRUE(gen.EmitType(out, s, ast::StorageClass::kNone,
                            ast::Access::kReadWrite, ""))
       << gen.error();
-  EXPECT_EQ(result(), "Texture2DMS<float4>");
+  EXPECT_EQ(out.str(), "Texture2DMS<float4>");
 }
 
 struct HlslStorageTextureData {
@@ -501,8 +518,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(params.result));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(params.result));
 }
 INSTANTIATE_TEST_SUITE_P(
     HlslGeneratorImplTest_Type,
diff --git a/src/writer/hlsl/generator_impl_unary_op_test.cc b/src/writer/hlsl/generator_impl_unary_op_test.cc
index 83ead64..6674692 100644
--- a/src/writer/hlsl/generator_impl_unary_op_test.cc
+++ b/src/writer/hlsl/generator_impl_unary_op_test.cc
@@ -29,8 +29,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, op)) << gen.error();
-  EXPECT_EQ(result(), "expr");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "expr");
 }
 
 TEST_F(HlslUnaryOpTest, Complement) {
@@ -41,8 +42,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, op)) << gen.error();
-  EXPECT_EQ(result(), "~(expr)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "~(expr)");
 }
 
 TEST_F(HlslUnaryOpTest, Indirection) {
@@ -56,8 +58,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, op)) << gen.error();
-  EXPECT_EQ(result(), "expr");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "expr");
 }
 
 TEST_F(HlslUnaryOpTest, Not) {
@@ -67,8 +70,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, op)) << gen.error();
-  EXPECT_EQ(result(), "!(expr)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "!(expr)");
 }
 
 TEST_F(HlslUnaryOpTest, Negation) {
@@ -79,8 +83,9 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitExpression(pre, out, op)) << gen.error();
-  EXPECT_EQ(result(), "-(expr)");
+  std::stringstream out;
+  ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+  EXPECT_EQ(out.str(), "-(expr)");
 }
 }  // namespace
 }  // namespace hlsl
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 2ce7469..5fa6059 100644
--- a/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/writer/hlsl/generator_impl_variable_decl_statement_test.cc
@@ -34,8 +34,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, stmt)) << gen.error();
-  EXPECT_EQ(result(), "  float a = 0.0f;\n");
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  float a = 0.0f;\n");
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Const) {
@@ -47,8 +47,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.EmitStatement(out, stmt)) << gen.error();
-  EXPECT_EQ(result(), "  const float a = 0.0f;\n");
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), "  const float a = 0.0f;\n");
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl, Emit_VariableDeclStatement_Array) {
@@ -60,8 +60,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(),
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(),
               HasSubstr("  float a[5] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f};\n"));
 }
 
@@ -74,8 +74,8 @@
 
   gen.increment_indent();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("  static float a = 0.0f;\n"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("  static float a = 0.0f;\n"));
 }
 
 TEST_F(HlslGeneratorImplTest_VariableDecl,
@@ -87,8 +87,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr(R"(float a = initializer;
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr(R"(float a = initializer;
 )"));
 }
 
@@ -101,8 +101,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStatement(out, stmt)) << gen.error();
-  EXPECT_EQ(result(), R"(float3 a = float3(0.0f, 0.0f, 0.0f);
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(), R"(float3 a = float3(0.0f, 0.0f, 0.0f);
 )");
 }
 
@@ -116,8 +116,8 @@
 
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitStatement(out, stmt)) << gen.error();
-  EXPECT_EQ(result(),
+  ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
+  EXPECT_EQ(gen.result(),
             R"(float2x3 a = float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
 )");
 }
diff --git a/src/writer/hlsl/generator_impl_workgroup_var_test.cc b/src/writer/hlsl/generator_impl_workgroup_var_test.cc
index a965be7..626fc90 100644
--- a/src/writer/hlsl/generator_impl_workgroup_var_test.cc
+++ b/src/writer/hlsl/generator_impl_workgroup_var_test.cc
@@ -32,8 +32,8 @@
        {Stage(ast::PipelineStage::kCompute)});
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("groupshared float wg;\n"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n"));
 }
 
 TEST_F(HlslGeneratorImplTest_WorkgroupVar, Aliased) {
@@ -45,8 +45,8 @@
        {Stage(ast::PipelineStage::kCompute)});
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_THAT(result(), HasSubstr("groupshared float wg;\n"));
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_THAT(gen.result(), HasSubstr("groupshared float wg;\n"));
 }
 
 }  // namespace
diff --git a/src/writer/hlsl/test_helper.h b/src/writer/hlsl/test_helper.h
index 26c5563..dc9420f 100644
--- a/src/writer/hlsl/test_helper.h
+++ b/src/writer/hlsl/test_helper.h
@@ -96,16 +96,6 @@
     return *gen_;
   }
 
-  /// @returns the result string
-  std::string result() const { return out.str(); }
-
-  /// @returns the pre result string
-  std::string pre_result() const { return pre.str(); }
-
-  /// The output stream
-  std::ostringstream out;
-  /// The pre-output stream
-  std::ostringstream pre;
   /// The program built with a call to Build()
   std::unique_ptr<Program> program;
 
diff --git a/src/writer/text_generator.cc b/src/writer/text_generator.cc
index 2a26d13..624d72c 100644
--- a/src/writer/text_generator.cc
+++ b/src/writer/text_generator.cc
@@ -31,5 +31,38 @@
   }
 }
 
+TextGenerator::LineWriter::LineWriter(TextGenerator* generator)
+    : gen(generator) {}
+
+TextGenerator::LineWriter::LineWriter(LineWriter&& other) {
+  gen = other.gen;
+  other.gen = nullptr;
+}
+
+TextGenerator::LineWriter::~LineWriter() {
+  if (gen) {
+    auto str = os.str();
+    if (!str.empty()) {
+      gen->make_indent();
+      gen->out_ << str << std::endl;
+    }
+  }
+}
+
+TextGenerator::ScopedParen::ScopedParen(std::ostream& stream) : s(stream) {
+  s << "(";
+}
+TextGenerator::ScopedParen::~ScopedParen() {
+  s << ")";
+}
+
+TextGenerator::ScopedIndent::ScopedIndent(TextGenerator* generator)
+    : gen(generator) {
+  gen->increment_indent();
+}
+TextGenerator::ScopedIndent::~ScopedIndent() {
+  gen->decrement_indent();
+}
+
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/text_generator.h b/src/writer/text_generator.h
index ab6d47a..8f68385 100644
--- a/src/writer/text_generator.h
+++ b/src/writer/text_generator.h
@@ -17,6 +17,7 @@
 
 #include <sstream>
 #include <string>
+#include <utility>
 
 #include "src/diagnostic/diagnostic.h"
 
@@ -58,6 +59,72 @@
   std::string error() const { return diagnostics_.str(); }
 
  protected:
+  /// LineWriter is a helper that acts as a string buffer, who's content is
+  /// emitted to the TextGenerator as a single line on destruction.
+  struct LineWriter {
+   public:
+    /// Constructor
+    /// @param generator the TextGenerator that the LineWriter will append its
+    /// content to on destruction
+    explicit LineWriter(TextGenerator* generator);
+    /// Move constructor
+    /// @param rhs the LineWriter to move
+    LineWriter(LineWriter&& rhs);
+    /// Destructor
+    ~LineWriter();
+
+    /// @returns the ostringstream
+    operator std::ostream &() { return os; }
+
+    /// @param rhs the value to write to the line
+    /// @returns the ostream so calls can be chained
+    template <typename T>
+    std::ostream& operator<<(T&& rhs) {
+      return os << std::forward<T>(rhs);
+    }
+
+   private:
+    LineWriter(const LineWriter&) = delete;
+    LineWriter& operator=(const LineWriter&) = delete;
+
+    std::ostringstream os;
+    TextGenerator* gen;
+  };
+
+  /// Helper for writing a '(' on construction and a ')' destruction.
+  struct ScopedParen {
+    /// Constructor
+    /// @param stream the std::ostream that will be written to
+    explicit ScopedParen(std::ostream& stream);
+    /// Destructor
+    ~ScopedParen();
+
+   private:
+    ScopedParen(ScopedParen&& rhs) = delete;
+    ScopedParen(const ScopedParen&) = delete;
+    ScopedParen& operator=(const ScopedParen&) = delete;
+    std::ostream& s;
+  };
+
+  /// Helper for incrementing indentation on construction and decrementing
+  /// indentation on destruction.
+  struct ScopedIndent {
+    /// Constructor
+    /// @param generator the TextGenerator that the ScopedIndent will indent
+    explicit ScopedIndent(TextGenerator* generator);
+    /// Destructor
+    ~ScopedIndent();
+
+   private:
+    ScopedIndent(ScopedIndent&& rhs) = delete;
+    ScopedIndent(const ScopedIndent&) = delete;
+    ScopedIndent& operator=(const ScopedIndent&) = delete;
+    TextGenerator* gen;
+  };
+
+  /// @returns a new LineWriter, used for buffering and writing a line to out_
+  LineWriter line() { return LineWriter(this); }
+
   /// The text output stream
   std::ostringstream out_;
   /// Diagnostics generated by the generator
diff --git a/test/bug/tint/913.wgsl.expected.hlsl b/test/bug/tint/913.wgsl.expected.hlsl
index ada977d..1ad9ee7 100644
--- a/test/bug/tint/913.wgsl.expected.hlsl
+++ b/test/bug/tint/913.wgsl.expected.hlsl
@@ -26,29 +26,29 @@
   const float4 nonCoveredColor = float4(0.0f, 1.0f, 0.0f, 1.0f);
   bool success = true;
   const int scalar_offset = (16u) / 4;
-    bool tint_tmp_2 = (dstTexCoord.x < uniforms[scalar_offset / 4][scalar_offset % 4]);
-  if (!tint_tmp_2) {
-const int scalar_offset_1 = (20u) / 4;
-        tint_tmp_2 = (dstTexCoord.y < uniforms[scalar_offset_1 / 4][scalar_offset_1 % 4]);
-  }
-  bool tint_tmp_3 = (tint_tmp_2);
-  if (!tint_tmp_3) {
-const int scalar_offset_2 = (16u) / 4;
-    const int scalar_offset_3 = (24u) / 4;
-        tint_tmp_3 = (dstTexCoord.x >= (uniforms[scalar_offset_2 / 4][scalar_offset_2 % 4] + uniforms[scalar_offset_3 / 4][scalar_offset_3 % 4]));
-  }
-  bool tint_tmp_4 = (tint_tmp_3);
+  bool tint_tmp_4 = (dstTexCoord.x < uniforms[scalar_offset / 4][scalar_offset % 4]);
   if (!tint_tmp_4) {
-const int scalar_offset_4 = (20u) / 4;
-    const int scalar_offset_5 = (28u) / 4;
-        tint_tmp_4 = (dstTexCoord.y >= (uniforms[scalar_offset_4 / 4][scalar_offset_4 % 4] + uniforms[scalar_offset_5 / 4][scalar_offset_5 % 4]));
+    const int scalar_offset_1 = (20u) / 4;
+    tint_tmp_4 = (dstTexCoord.y < uniforms[scalar_offset_1 / 4][scalar_offset_1 % 4]);
   }
-if ((tint_tmp_4)) {
-        bool tint_tmp_5 = success;
+  bool tint_tmp_3 = (tint_tmp_4);
+  if (!tint_tmp_3) {
+    const int scalar_offset_2 = (16u) / 4;
+    const int scalar_offset_3 = (24u) / 4;
+    tint_tmp_3 = (dstTexCoord.x >= (uniforms[scalar_offset_2 / 4][scalar_offset_2 % 4] + uniforms[scalar_offset_3 / 4][scalar_offset_3 % 4]));
+  }
+  bool tint_tmp_2 = (tint_tmp_3);
+  if (!tint_tmp_2) {
+    const int scalar_offset_4 = (20u) / 4;
+    const int scalar_offset_5 = (28u) / 4;
+    tint_tmp_2 = (dstTexCoord.y >= (uniforms[scalar_offset_4 / 4][scalar_offset_4 % 4] + uniforms[scalar_offset_5 / 4][scalar_offset_5 % 4]));
+  }
+  if ((tint_tmp_2)) {
+    bool tint_tmp_5 = success;
     if (tint_tmp_5) {
       tint_tmp_5 = all((tint_symbol.Load(int3(dstTexCoord, 0), 0) == nonCoveredColor));
     }
-success = (tint_tmp_5);
+    success = (tint_tmp_5);
   } else {
     const int scalar_offset_6 = (16u) / 4;
     uint4 ubo_load = uniforms[scalar_offset_6 / 4];
@@ -63,33 +63,33 @@
     const float4 dstColor = tint_symbol.Load(int3(dstTexCoord, 0), 0);
     const int scalar_offset_9 = (4u) / 4;
     if ((uniforms[scalar_offset_9 / 4][scalar_offset_9 % 4] == 2u)) {
-            bool tint_tmp_6 = success;
-      if (tint_tmp_6) {
-        tint_tmp_6 = aboutEqual(dstColor.r, srcColor.r);
-      }
-      bool tint_tmp_7 = (tint_tmp_6);
+      bool tint_tmp_7 = success;
       if (tint_tmp_7) {
-        tint_tmp_7 = aboutEqual(dstColor.g, srcColor.g);
+        tint_tmp_7 = aboutEqual(dstColor.r, srcColor.r);
       }
-success = (tint_tmp_7);
+      bool tint_tmp_6 = (tint_tmp_7);
+      if (tint_tmp_6) {
+        tint_tmp_6 = aboutEqual(dstColor.g, srcColor.g);
+      }
+      success = (tint_tmp_6);
     } else {
-            bool tint_tmp_8 = success;
-      if (tint_tmp_8) {
-        tint_tmp_8 = aboutEqual(dstColor.r, srcColor.r);
-      }
-      bool tint_tmp_9 = (tint_tmp_8);
-      if (tint_tmp_9) {
-        tint_tmp_9 = aboutEqual(dstColor.g, srcColor.g);
-      }
-      bool tint_tmp_10 = (tint_tmp_9);
-      if (tint_tmp_10) {
-        tint_tmp_10 = aboutEqual(dstColor.b, srcColor.b);
-      }
-      bool tint_tmp_11 = (tint_tmp_10);
+      bool tint_tmp_11 = success;
       if (tint_tmp_11) {
-        tint_tmp_11 = aboutEqual(dstColor.a, srcColor.a);
+        tint_tmp_11 = aboutEqual(dstColor.r, srcColor.r);
       }
-success = (tint_tmp_11);
+      bool tint_tmp_10 = (tint_tmp_11);
+      if (tint_tmp_10) {
+        tint_tmp_10 = aboutEqual(dstColor.g, srcColor.g);
+      }
+      bool tint_tmp_9 = (tint_tmp_10);
+      if (tint_tmp_9) {
+        tint_tmp_9 = aboutEqual(dstColor.b, srcColor.b);
+      }
+      bool tint_tmp_8 = (tint_tmp_9);
+      if (tint_tmp_8) {
+        tint_tmp_8 = aboutEqual(dstColor.a, srcColor.a);
+      }
+      success = (tint_tmp_8);
     }
   }
   const uint outputIndex = ((GlobalInvocationID.y * uint(dstSize.x)) + GlobalInvocationID.x);
diff --git a/test/bug/tint/914.wgsl.expected.hlsl b/test/bug/tint/914.wgsl.expected.hlsl
index 3a6738e..33cd2bb 100644
--- a/test/bug/tint/914.wgsl.expected.hlsl
+++ b/test/bug/tint/914.wgsl.expected.hlsl
@@ -7,12 +7,12 @@
 
 float mm_readA(uint row, uint col) {
   const int scalar_offset = (0u) / 4;
-    bool tint_tmp = (row < uniforms[scalar_offset / 4][scalar_offset % 4]);
+  bool tint_tmp = (row < uniforms[scalar_offset / 4][scalar_offset % 4]);
   if (tint_tmp) {
-const int scalar_offset_1 = (4u) / 4;
-        tint_tmp = (col < uniforms[scalar_offset_1 / 4][scalar_offset_1 % 4]);
+    const int scalar_offset_1 = (4u) / 4;
+    tint_tmp = (col < uniforms[scalar_offset_1 / 4][scalar_offset_1 % 4]);
   }
-if ((tint_tmp)) {
+  if ((tint_tmp)) {
     const int scalar_offset_2 = (4u) / 4;
     const float result = asfloat(firstMatrix.Load((4u * ((row * uniforms[scalar_offset_2 / 4][scalar_offset_2 % 4]) + col))));
     return result;
@@ -22,12 +22,12 @@
 
 float mm_readB(uint row, uint col) {
   const int scalar_offset_3 = (4u) / 4;
-    bool tint_tmp_1 = (row < uniforms[scalar_offset_3 / 4][scalar_offset_3 % 4]);
+  bool tint_tmp_1 = (row < uniforms[scalar_offset_3 / 4][scalar_offset_3 % 4]);
   if (tint_tmp_1) {
-const int scalar_offset_4 = (8u) / 4;
-        tint_tmp_1 = (col < uniforms[scalar_offset_4 / 4][scalar_offset_4 % 4]);
+    const int scalar_offset_4 = (8u) / 4;
+    tint_tmp_1 = (col < uniforms[scalar_offset_4 / 4][scalar_offset_4 % 4]);
   }
-if ((tint_tmp_1)) {
+  if ((tint_tmp_1)) {
     const int scalar_offset_5 = (8u) / 4;
     const float result = asfloat(secondMatrix.Load((4u * ((row * uniforms[scalar_offset_5 / 4][scalar_offset_5 % 4]) + col))));
     return result;
@@ -37,12 +37,12 @@
 
 void mm_write(uint row, uint col, float value) {
   const int scalar_offset_6 = (0u) / 4;
-    bool tint_tmp_2 = (row < uniforms[scalar_offset_6 / 4][scalar_offset_6 % 4]);
+  bool tint_tmp_2 = (row < uniforms[scalar_offset_6 / 4][scalar_offset_6 % 4]);
   if (tint_tmp_2) {
-const int scalar_offset_7 = (8u) / 4;
-        tint_tmp_2 = (col < uniforms[scalar_offset_7 / 4][scalar_offset_7 % 4]);
+    const int scalar_offset_7 = (8u) / 4;
+    tint_tmp_2 = (col < uniforms[scalar_offset_7 / 4][scalar_offset_7 % 4]);
   }
-if ((tint_tmp_2)) {
+  if ((tint_tmp_2)) {
     const int scalar_offset_8 = (8u) / 4;
     const uint index = (col + (row * uniforms[scalar_offset_8 / 4][scalar_offset_8 % 4]));
     resultMatrix.Store((4u * index), asuint(value));
diff --git a/test/intrinsics/gen/pack2x16float/0e97b3.wgsl.expected.hlsl b/test/intrinsics/gen/pack2x16float/0e97b3.wgsl.expected.hlsl
index 9132660..c06e032 100644
--- a/test/intrinsics/gen/pack2x16float/0e97b3.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/pack2x16float/0e97b3.wgsl.expected.hlsl
@@ -1,6 +1,6 @@
 void pack2x16float_0e97b3() {
   uint2 tint_tmp = f32tof16(float2(0.0f, 0.0f));
-uint res = (tint_tmp.x | tint_tmp.y << 16);
+  uint res = (tint_tmp.x | tint_tmp.y << 16);
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl b/test/intrinsics/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl
index c2d596b..cccbc2d 100644
--- a/test/intrinsics/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/pack2x16snorm/6c169b.wgsl.expected.hlsl
@@ -1,6 +1,6 @@
 void pack2x16snorm_6c169b() {
   int2 tint_tmp = int2(round(clamp(float2(0.0f, 0.0f), -1.0, 1.0) * 32767.0)) & 0xffff;
-uint res = asuint(tint_tmp.x | tint_tmp.y << 16);
+  uint res = asuint(tint_tmp.x | tint_tmp.y << 16);
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl b/test/intrinsics/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl
index cf0dbe7..3495457 100644
--- a/test/intrinsics/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/pack2x16unorm/0f08e4.wgsl.expected.hlsl
@@ -1,6 +1,6 @@
 void pack2x16unorm_0f08e4() {
   uint2 tint_tmp = uint2(round(clamp(float2(0.0f, 0.0f), 0.0, 1.0) * 65535.0));
-uint res = (tint_tmp.x | tint_tmp.y << 16);
+  uint res = (tint_tmp.x | tint_tmp.y << 16);
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl b/test/intrinsics/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl
index d495e8d..93c5fcf 100644
--- a/test/intrinsics/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/pack4x8snorm/4d22e7.wgsl.expected.hlsl
@@ -1,6 +1,6 @@
 void pack4x8snorm_4d22e7() {
   int4 tint_tmp = int4(round(clamp(float4(0.0f, 0.0f, 0.0f, 0.0f), -1.0, 1.0) * 127.0)) & 0xff;
-uint res = asuint(tint_tmp.x | tint_tmp.y << 8 | tint_tmp.z << 16 | tint_tmp.w << 24);
+  uint res = asuint(tint_tmp.x | tint_tmp.y << 8 | tint_tmp.z << 16 | tint_tmp.w << 24);
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/pack4x8unorm/95c456.wgsl.expected.hlsl b/test/intrinsics/gen/pack4x8unorm/95c456.wgsl.expected.hlsl
index 6fca709..f22557c 100644
--- a/test/intrinsics/gen/pack4x8unorm/95c456.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/pack4x8unorm/95c456.wgsl.expected.hlsl
@@ -1,6 +1,6 @@
 void pack4x8unorm_95c456() {
   uint4 tint_tmp = uint4(round(clamp(float4(0.0f, 0.0f, 0.0f, 0.0f), 0.0, 1.0) * 255.0));
-uint res = (tint_tmp.x | tint_tmp.y << 8 | tint_tmp.z << 16 | tint_tmp.w << 24);
+  uint res = (tint_tmp.x | tint_tmp.y << 8 | tint_tmp.z << 16 | tint_tmp.w << 24);
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl b/test/intrinsics/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl
index 6dc4bda..7107517 100644
--- a/test/intrinsics/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/unpack2x16float/32a5cf.wgsl.expected.hlsl
@@ -1,6 +1,6 @@
 void unpack2x16float_32a5cf() {
   uint tint_tmp = 1u;
-float2 res = f16tof32(uint2(tint_tmp & 0xffff, tint_tmp >> 16));
+  float2 res = f16tof32(uint2(tint_tmp & 0xffff, tint_tmp >> 16));
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl b/test/intrinsics/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl
index d5314f5..ad0d7b3 100644
--- a/test/intrinsics/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/unpack2x16snorm/b4aea6.wgsl.expected.hlsl
@@ -1,7 +1,7 @@
 void unpack2x16snorm_b4aea6() {
   int tint_tmp_1 = int(1u);
-int2 tint_tmp = int2(tint_tmp_1 << 16, tint_tmp_1) >> 16;
-float2 res = clamp(float2(tint_tmp) / 32767.0, -1.0, 1.0);
+  int2 tint_tmp = int2(tint_tmp_1 << 16, tint_tmp_1) >> 16;
+  float2 res = clamp(float2(tint_tmp) / 32767.0, -1.0, 1.0);
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl b/test/intrinsics/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl
index 3fdceee..1afbba3 100644
--- a/test/intrinsics/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/unpack2x16unorm/7699c0.wgsl.expected.hlsl
@@ -1,7 +1,7 @@
 void unpack2x16unorm_7699c0() {
   uint tint_tmp_1 = 1u;
-uint2 tint_tmp = uint2(tint_tmp_1 & 0xffff, tint_tmp_1 >> 16);
-float2 res = float2(tint_tmp) / 65535.0;
+  uint2 tint_tmp = uint2(tint_tmp_1 & 0xffff, tint_tmp_1 >> 16);
+  float2 res = float2(tint_tmp) / 65535.0;
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl b/test/intrinsics/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl
index e02cf52..721a798 100644
--- a/test/intrinsics/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/unpack4x8snorm/523fb3.wgsl.expected.hlsl
@@ -1,7 +1,7 @@
 void unpack4x8snorm_523fb3() {
   int tint_tmp_1 = int(1u);
-int4 tint_tmp = int4(tint_tmp_1 << 24, tint_tmp_1 << 16, tint_tmp_1 << 8, tint_tmp_1) >> 24;
-float4 res = clamp(float4(tint_tmp) / 127.0, -1.0, 1.0);
+  int4 tint_tmp = int4(tint_tmp_1 << 24, tint_tmp_1 << 16, tint_tmp_1 << 8, tint_tmp_1) >> 24;
+  float4 res = clamp(float4(tint_tmp) / 127.0, -1.0, 1.0);
 }
 
 struct tint_symbol {
diff --git a/test/intrinsics/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl b/test/intrinsics/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl
index af35ee2..e3a10fe 100644
--- a/test/intrinsics/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl
+++ b/test/intrinsics/gen/unpack4x8unorm/750c74.wgsl.expected.hlsl
@@ -1,7 +1,7 @@
 void unpack4x8unorm_750c74() {
   uint tint_tmp_1 = 1u;
-uint4 tint_tmp = uint4(tint_tmp_1 & 0xff, (tint_tmp_1 >> 8) & 0xff, (tint_tmp_1 >> 16) & 0xff, tint_tmp_1 >> 24);
-float4 res = float4(tint_tmp) / 255.0;
+  uint4 tint_tmp = uint4(tint_tmp_1 & 0xff, (tint_tmp_1 >> 8) & 0xff, (tint_tmp_1 >> 16) & 0xff, tint_tmp_1 >> 24);
+  float4 res = float4(tint_tmp) / 255.0;
 }
 
 struct tint_symbol {