[tint] Remove ASTTextGenerator

The mix of Program and ProgramBuilder was messy, and the rest of the base class is trivially inlinable.

Bug: tint:1988
Change-Id: I50af6bd38fde1e02153987e17d37120c7da4ca8c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/143040
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 773d2ac..1d6791f 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -982,8 +982,6 @@
     "utils/text/text_generator.h",
     "writer/array_length_from_uniform_options.cc",
     "writer/array_length_from_uniform_options.h",
-    "writer/ast_text_generator.cc",
-    "writer/ast_text_generator.h",
     "writer/binding_point.h",
     "writer/binding_remapper_options.cc",
     "writer/binding_remapper_options.h",
@@ -1175,6 +1173,8 @@
     "lang/glsl/ast_writer/generator.h",
     "lang/glsl/ast_writer/generator_impl.cc",
     "lang/glsl/ast_writer/generator_impl.h",
+    "lang/glsl/ast_writer/options.h",
+    "lang/glsl/ast_writer/version.h",
   ]
 
   deps = [
@@ -1934,7 +1934,6 @@
       "lang/wgsl/helpers/check_supported_extensions_test.cc",
       "lang/wgsl/helpers/flatten_bindings_test.cc",
       "utils/text/float_to_string_test.cc",
-      "writer/ast_text_generator_test.cc",
     ]
     deps = [
       ":libtint_unittests_ast_helper",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 2b5b4b8..60f5d39 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -572,8 +572,6 @@
   utils/traits/traits.h
   writer/array_length_from_uniform_options.cc
   writer/array_length_from_uniform_options.h
-  writer/ast_text_generator.cc
-  writer/ast_text_generator.h
   writer/binding_point.h
   writer/binding_remapper_options.cc
   writer/binding_remapper_options.h
@@ -726,6 +724,7 @@
     lang/glsl/ast_writer/generator.h
     lang/glsl/ast_writer/generator_impl.cc
     lang/glsl/ast_writer/generator_impl.h
+    lang/glsl/ast_writer/options.h
     lang/glsl/ast_writer/version.h
   )
 endif()
@@ -1156,7 +1155,6 @@
     utils/text/symbol_test.cc
     utils/text/unicode_test.cc
     utils/traits/traits_test.cc
-    writer/ast_text_generator_test.cc
   )
 
   # Noet, the source files are included here otherwise the cmd sources would not be included in the
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index 33ca800..e8e194e 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -55,7 +55,7 @@
 #include "src/tint/lang/core/ir/disassembler.h"  // nogncheck
 #include "src/tint/lang/core/ir/from_program.h"  // nogncheck
 #include "src/tint/lang/core/ir/module.h"        // nogncheck
-#endif                                 // TINT_BUILD_IR
+#endif                                           // TINT_BUILD_IR
 
 #if TINT_BUILD_SPV_WRITER
 #define SPV_WRITER_ONLY(x) x
diff --git a/src/tint/lang/glsl/ast_writer/generator.h b/src/tint/lang/glsl/ast_writer/generator.h
index 03626f1..8ca98fa 100644
--- a/src/tint/lang/glsl/ast_writer/generator.h
+++ b/src/tint/lang/glsl/ast_writer/generator.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "src/tint/lang/core/builtin/access.h"
+#include "src/tint/lang/glsl/ast_writer/options.h"
 #include "src/tint/lang/glsl/ast_writer/version.h"
 #include "src/tint/lang/wgsl/ast/pipeline_stage.h"
 #include "src/tint/lang/wgsl/sem/binding_point.h"
@@ -36,58 +37,6 @@
 
 namespace tint::writer::glsl {
 
-using BindingMap = std::unordered_map<sem::SamplerTexturePair, std::string>;
-
-/// Configuration options used for generating GLSL.
-struct Options {
-    /// Constructor
-    Options();
-
-    /// Destructor
-    ~Options();
-
-    /// Copy constructor
-    Options(const Options&);
-
-    /// A map of SamplerTexturePair to combined sampler names for the
-    /// CombineSamplers transform
-    BindingMap binding_map;
-
-    /// The binding point to use for placeholder samplers.
-    sem::BindingPoint placeholder_binding_point;
-
-    /// A map of old binding point to new binding point for the BindingRemapper
-    /// transform
-    std::unordered_map<sem::BindingPoint, sem::BindingPoint> binding_points;
-
-    /// A map of old binding point to new access control for the BindingRemapper
-    /// transform
-    std::unordered_map<sem::BindingPoint, builtin::Access> access_controls;
-
-    /// Set to `true` to disable software robustness that prevents out-of-bounds accesses.
-    bool disable_robustness = false;
-
-    /// If true, then validation will be disabled for binding point collisions
-    /// generated by the BindingRemapper transform
-    bool allow_collisions = false;
-
-    /// Set to `true` to disable workgroup memory zero initialization
-    bool disable_workgroup_init = false;
-
-    /// Options used in the binding mappings for external textures
-    ExternalTextureOptions external_texture_options = {};
-
-    /// The GLSL version to emit
-    Version version;
-
-    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
-    TINT_REFLECT(disable_robustness,
-                 allow_collisions,
-                 disable_workgroup_init,
-                 external_texture_options,
-                 version);
-};
-
 /// The result produced when generating GLSL.
 struct Result {
     /// Constructor
diff --git a/src/tint/lang/glsl/ast_writer/generator_impl.cc b/src/tint/lang/glsl/ast_writer/generator_impl.cc
index 3b5d4ad..0681815 100644
--- a/src/tint/lang/glsl/ast_writer/generator_impl.cc
+++ b/src/tint/lang/glsl/ast_writer/generator_impl.cc
@@ -32,6 +32,7 @@
 #include "src/tint/lang/core/type/sampled_texture.h"
 #include "src/tint/lang/core/type/storage_texture.h"
 #include "src/tint/lang/core/type/texture_dimension.h"
+#include "src/tint/lang/glsl/ast_writer/options.h"
 #include "src/tint/lang/wgsl/ast/call_statement.h"
 #include "src/tint/lang/wgsl/ast/id_attribute.h"
 #include "src/tint/lang/wgsl/ast/internal_attribute.h"
@@ -65,6 +66,7 @@
 #include "src/tint/lang/wgsl/ast/variable_decl_statement.h"
 #include "src/tint/lang/wgsl/helpers/append_vector.h"
 #include "src/tint/lang/wgsl/sem/block_statement.h"
+#include "src/tint/lang/wgsl/sem/builtin_enum_expression.h"
 #include "src/tint/lang/wgsl/sem/call.h"
 #include "src/tint/lang/wgsl/sem/function.h"
 #include "src/tint/lang/wgsl/sem/member_accessor_expression.h"
@@ -254,7 +256,7 @@
 }
 
 GeneratorImpl::GeneratorImpl(const Program* program, const Version& version)
-    : ASTTextGenerator(program), version_(version) {}
+    : builder_(ProgramBuilder::Wrap(program)), version_(version) {}
 
 GeneratorImpl::~GeneratorImpl() = default;
 
@@ -2044,7 +2046,7 @@
     auto* decl = var->Declaration();
 
     if (auto* attr = ast::GetAttribute<ast::BuiltinAttribute>(decl->attributes)) {
-        auto builtin = program_->Sem().Get(attr)->Value();
+        auto builtin = builder_.Sem().Get(attr)->Value();
         // Use of gl_SampleID requires the GL_OES_sample_variables extension
         if (RequiresOESSampleVariables(builtin)) {
             requires_oes_sample_variables_ = true;
@@ -2073,7 +2075,7 @@
     utils::VectorRef<const ast::Attribute*> attributes) {
     for (auto* attr : attributes) {
         if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
-            auto& sem = program_->Sem();
+            auto& sem = builder_.Sem();
             auto i_type =
                 sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationType>>(interpolate->type)
                     ->Value();
@@ -2964,4 +2966,8 @@
     }
 }
 
+std::string GeneratorImpl::UniqueIdentifier(const std::string& prefix /* = "" */) {
+    return builder_.Symbols().New(prefix).Name();
+}
+
 }  // namespace tint::writer::glsl
diff --git a/src/tint/lang/glsl/ast_writer/generator_impl.h b/src/tint/lang/glsl/ast_writer/generator_impl.h
index 49ba42f..2cb0269 100644
--- a/src/tint/lang/glsl/ast_writer/generator_impl.h
+++ b/src/tint/lang/glsl/ast_writer/generator_impl.h
@@ -22,25 +22,12 @@
 #include <utility>
 
 #include "src/tint/lang/core/builtin/builtin_value.h"
-#include "src/tint/lang/glsl/ast_writer/generator.h"
 #include "src/tint/lang/glsl/ast_writer/version.h"
-#include "src/tint/lang/wgsl/ast/assignment_statement.h"
-#include "src/tint/lang/wgsl/ast/bitcast_expression.h"
-#include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/continue_statement.h"
-#include "src/tint/lang/wgsl/ast/discard_statement.h"
-#include "src/tint/lang/wgsl/ast/for_loop_statement.h"
-#include "src/tint/lang/wgsl/ast/if_statement.h"
-#include "src/tint/lang/wgsl/ast/loop_statement.h"
-#include "src/tint/lang/wgsl/ast/return_statement.h"
-#include "src/tint/lang/wgsl/ast/switch_statement.h"
-#include "src/tint/lang/wgsl/ast/transform/decompose_memory_access.h"
-#include "src/tint/lang/wgsl/ast/unary_op_expression.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/utils/containers/scope_stack.h"
 #include "src/tint/utils/math/hash.h"
 #include "src/tint/utils/text/string_stream.h"
-#include "src/tint/writer/ast_text_generator.h"
+#include "src/tint/utils/text/text_generator.h"
 
 // Forward declarations
 namespace tint::sem {
@@ -49,6 +36,9 @@
 class ValueConstructor;
 class ValueConversion;
 }  // namespace tint::sem
+namespace tint::writer::glsl {
+struct Options;
+}
 
 namespace tint::writer::glsl {
 
@@ -75,7 +65,7 @@
                          const std::string& entry_point);
 
 /// Implementation class for GLSL generator
-class GeneratorImpl : public ASTTextGenerator {
+class GeneratorImpl : public utils::TextGenerator {
   public:
     /// Constructor
     /// @param program the program to generate
@@ -454,6 +444,16 @@
     /// @returns the corresponding uint type
     type::Type* BoolTypeToUint(const type::Type* type);
 
+    /// @copydoc utils::TextWrtiter::UniqueIdentifier
+    std::string UniqueIdentifier(const std::string& prefix = "") override;
+
+    /// Alias for builder_.TypeOf(ptr)
+    template <typename T>
+    auto TypeOf(T* ptr) {
+        return builder_.TypeOf(ptr);
+    }
+
+    ProgramBuilder builder_;
     TextBuffer helpers_;  // Helper functions emitted at the top of the output
     std::function<void()> emit_continuing_;
     std::unordered_map<const sem::Builtin*, std::string> builtins_;
diff --git a/src/tint/lang/glsl/ast_writer/generator_impl_type_test.cc b/src/tint/lang/glsl/ast_writer/generator_impl_type_test.cc
index ebd7fd8..d19b02f 100644
--- a/src/tint/lang/glsl/ast_writer/generator_impl_type_test.cc
+++ b/src/tint/lang/glsl/ast_writer/generator_impl_type_test.cc
@@ -169,7 +169,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(s)->As<type::Struct>();
     gen.EmitStructType(&buf, str);
     EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty());
@@ -223,7 +223,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(s)->As<type::Struct>();
     gen.EmitStructType(&buf, str);
     EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty());
diff --git a/src/tint/lang/glsl/ast_writer/options.h b/src/tint/lang/glsl/ast_writer/options.h
new file mode 100644
index 0000000..c120bb6
--- /dev/null
+++ b/src/tint/lang/glsl/ast_writer/options.h
@@ -0,0 +1,82 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_LANG_GLSL_AST_WRITER_OPTIONS_H_
+#define SRC_TINT_LANG_GLSL_AST_WRITER_OPTIONS_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/lang/core/builtin/access.h"
+#include "src/tint/lang/glsl/ast_writer/version.h"
+#include "src/tint/lang/wgsl/sem/sampler_texture_pair.h"
+#include "src/tint/writer/external_texture_options.h"
+
+namespace tint::writer::glsl {
+
+using BindingMap = std::unordered_map<sem::SamplerTexturePair, std::string>;
+
+/// Configuration options used for generating GLSL.
+struct Options {
+    /// Constructor
+    Options();
+
+    /// Destructor
+    ~Options();
+
+    /// Copy constructor
+    Options(const Options&);
+
+    /// A map of SamplerTexturePair to combined sampler names for the
+    /// CombineSamplers transform
+    BindingMap binding_map;
+
+    /// The binding point to use for placeholder samplers.
+    sem::BindingPoint placeholder_binding_point;
+
+    /// A map of old binding point to new binding point for the BindingRemapper
+    /// transform
+    std::unordered_map<sem::BindingPoint, sem::BindingPoint> binding_points;
+
+    /// A map of old binding point to new access control for the BindingRemapper
+    /// transform
+    std::unordered_map<sem::BindingPoint, builtin::Access> access_controls;
+
+    /// Set to `true` to disable software robustness that prevents out-of-bounds accesses.
+    bool disable_robustness = false;
+
+    /// If true, then validation will be disabled for binding point collisions
+    /// generated by the BindingRemapper transform
+    bool allow_collisions = false;
+
+    /// Set to `true` to disable workgroup memory zero initialization
+    bool disable_workgroup_init = false;
+
+    /// Options used in the binding mappings for external textures
+    ExternalTextureOptions external_texture_options = {};
+
+    /// The GLSL version to emit
+    Version version;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(disable_robustness,
+                 allow_collisions,
+                 disable_workgroup_init,
+                 external_texture_options,
+                 version);
+};
+
+}  // namespace tint::writer::glsl
+
+#endif  // SRC_TINT_LANG_GLSL_AST_WRITER_OPTIONS_H_
diff --git a/src/tint/lang/glsl/ast_writer/test_helper.h b/src/tint/lang/glsl/ast_writer/test_helper.h
index 80bf543..c6d5ce1 100644
--- a/src/tint/lang/glsl/ast_writer/test_helper.h
+++ b/src/tint/lang/glsl/ast_writer/test_helper.h
@@ -20,7 +20,9 @@
 #include <utility>
 
 #include "gtest/gtest.h"
+#include "src/tint/lang/glsl/ast_writer/generator.h"
 #include "src/tint/lang/glsl/ast_writer/generator_impl.h"
+#include "src/tint/lang/glsl/ast_writer/version.h"
 #include "src/tint/lang/wgsl/ast/transform/manager.h"
 
 namespace tint::writer::glsl {
diff --git a/src/tint/lang/hlsl/ast_writer/generator_impl.cc b/src/tint/lang/hlsl/ast_writer/generator_impl.cc
index f1a2572..7beebb2 100644
--- a/src/tint/lang/hlsl/ast_writer/generator_impl.cc
+++ b/src/tint/lang/hlsl/ast_writer/generator_impl.cc
@@ -328,12 +328,12 @@
     return result;
 }
 
-GeneratorImpl::GeneratorImpl(const Program* program) : ASTTextGenerator(program) {}
+GeneratorImpl::GeneratorImpl(const Program* program) : builder_(ProgramBuilder::Wrap(program)) {}
 
 GeneratorImpl::~GeneratorImpl() = default;
 
 bool GeneratorImpl::Generate() {
-    if (!CheckSupportedExtensions("HLSL", program_->AST(), diagnostics_,
+    if (!CheckSupportedExtensions("HLSL", builder_.AST(), diagnostics_,
                                   utils::Vector{
                                       builtin::Extension::kChromiumDisableUniformityAnalysis,
                                       builtin::Extension::kChromiumExperimentalDp4A,
@@ -4613,4 +4613,8 @@
     return true;
 }
 
+std::string GeneratorImpl::UniqueIdentifier(const std::string& prefix /* = "" */) {
+    return builder_.Symbols().New(prefix).Name();
+}
+
 }  // namespace tint::writer::hlsl
diff --git a/src/tint/lang/hlsl/ast_writer/generator_impl.h b/src/tint/lang/hlsl/ast_writer/generator_impl.h
index 05bee69..e9945a0 100644
--- a/src/tint/lang/hlsl/ast_writer/generator_impl.h
+++ b/src/tint/lang/hlsl/ast_writer/generator_impl.h
@@ -23,24 +23,13 @@
 
 #include "src/tint/lang/core/builtin/builtin_value.h"
 #include "src/tint/lang/hlsl/ast_writer/generator.h"
-#include "src/tint/lang/wgsl/ast/assignment_statement.h"
-#include "src/tint/lang/wgsl/ast/bitcast_expression.h"
-#include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/continue_statement.h"
-#include "src/tint/lang/wgsl/ast/discard_statement.h"
-#include "src/tint/lang/wgsl/ast/for_loop_statement.h"
-#include "src/tint/lang/wgsl/ast/if_statement.h"
-#include "src/tint/lang/wgsl/ast/loop_statement.h"
-#include "src/tint/lang/wgsl/ast/return_statement.h"
-#include "src/tint/lang/wgsl/ast/switch_statement.h"
 #include "src/tint/lang/wgsl/ast/transform/decompose_memory_access.h"
-#include "src/tint/lang/wgsl/ast/unary_op_expression.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/sem/binding_point.h"
 #include "src/tint/utils/containers/scope_stack.h"
 #include "src/tint/utils/math/hash.h"
+#include "src/tint/utils/text/text_generator.h"
 #include "src/tint/writer/array_length_from_uniform_options.h"
-#include "src/tint/writer/ast_text_generator.h"
 
 // Forward declarations
 namespace tint::sem {
@@ -75,7 +64,7 @@
 SanitizedResult Sanitize(const Program* program, const Options& options);
 
 /// Implementation class for HLSL generator
-class GeneratorImpl : public ASTTextGenerator {
+class GeneratorImpl : public utils::TextGenerator {
   public:
     /// Constructor
     /// @param program the program to generate
@@ -565,6 +554,16 @@
                            const sem::Builtin* builtin,
                            F&& build);
 
+    /// @copydoc utils::TextWrtiter::UniqueIdentifier
+    std::string UniqueIdentifier(const std::string& prefix = "") override;
+
+    /// Alias for builder_.TypeOf(ptr)
+    template <typename T>
+    auto TypeOf(T* ptr) {
+        return builder_.TypeOf(ptr);
+    }
+
+    ProgramBuilder builder_;
     TextBuffer helpers_;  // Helper functions emitted at the top of the output
     std::function<bool()> emit_continuing_;
     std::unordered_map<const type::Matrix*, std::string> matrix_scalar_inits_;
diff --git a/src/tint/lang/hlsl/ast_writer/generator_impl_type_test.cc b/src/tint/lang/hlsl/ast_writer/generator_impl_type_test.cc
index 326e778..b58d4a1 100644
--- a/src/tint/lang/hlsl/ast_writer/generator_impl_type_test.cc
+++ b/src/tint/lang/hlsl/ast_writer/generator_impl_type_test.cc
@@ -170,7 +170,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(s)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
     EXPECT_EQ(buf.String(), R"(struct S {
@@ -237,7 +237,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(s)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
     EXPECT_EQ(buf.String(), R"(struct S {
diff --git a/src/tint/lang/msl/ast_writer/generator_impl.cc b/src/tint/lang/msl/ast_writer/generator_impl.cc
index c1ec636..9918493 100644
--- a/src/tint/lang/msl/ast_writer/generator_impl.cc
+++ b/src/tint/lang/msl/ast_writer/generator_impl.cc
@@ -232,12 +232,12 @@
     return result;
 }
 
-GeneratorImpl::GeneratorImpl(const Program* program) : ASTTextGenerator(program) {}
+GeneratorImpl::GeneratorImpl(const Program* program) : builder_(ProgramBuilder::Wrap(program)) {}
 
 GeneratorImpl::~GeneratorImpl() = default;
 
 bool GeneratorImpl::Generate() {
-    if (!CheckSupportedExtensions("MSL", program_->AST(), diagnostics_,
+    if (!CheckSupportedExtensions("MSL", builder_.AST(), diagnostics_,
                                   utils::Vector{
                                       builtin::Extension::kChromiumDisableUniformityAnalysis,
                                       builtin::Extension::kChromiumExperimentalFullPtrParameters,
@@ -606,7 +606,7 @@
 }
 
 bool GeneratorImpl::EmitCall(utils::StringStream& out, const ast::CallExpression* expr) {
-    auto* call = program_->Sem().Get<sem::Call>(expr);
+    auto* call = builder_.Sem().Get<sem::Call>(expr);
     auto* target = call->Target();
     return Switch(
         target, [&](const sem::Function* func) { return EmitFunctionCall(out, call, func); },
@@ -1833,7 +1833,7 @@
 }
 
 bool GeneratorImpl::EmitFunction(const ast::Function* func) {
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = builder_.Sem().Get(func);
 
     {
         auto out = Line();
@@ -1849,7 +1849,7 @@
             }
             first = false;
 
-            auto* type = program_->Sem().Get(v)->Type();
+            auto* type = builder_.Sem().Get(v)->Type();
 
             if (!EmitType(out, type)) {
                 return false;
@@ -1886,7 +1886,7 @@
                 << "missing binding attributes for entry point parameter";
             return kInvalidBindingIndex;
         }
-        auto* param_sem = program_->Sem().Get<sem::Parameter>(param);
+        auto* param_sem = builder_.Sem().Get<sem::Parameter>(param);
         auto bp = param_sem->BindingPoint();
         if (TINT_UNLIKELY(bp->group != 0)) {
             TINT_ICE(Writer, diagnostics_) << "encountered non-zero resource group index (use "
@@ -1914,7 +1914,7 @@
             }
             first = false;
 
-            auto* type = program_->Sem().Get(param)->Type()->UnwrapRef();
+            auto* type = builder_.Sem().Get(param)->Type()->UnwrapRef();
 
             if (!EmitType(out, type)) {
                 return false;
@@ -1977,7 +1977,7 @@
                         if (!builtin_attr) {
                             continue;
                         }
-                        auto builtin = program_->Sem().Get(builtin_attr)->Value();
+                        auto builtin = builder_.Sem().Get(builtin_attr)->Value();
 
                         builtin_found = true;
 
@@ -2687,7 +2687,7 @@
         std::string name;
         do {
             name = UniqueIdentifier("tint_pad");
-        } while (str->FindMember(program_->Symbols().Get(name)));
+        } while (str->FindMember(builder_.Symbols().Get(name)));
 
         auto out = Line(b);
         add_byte_offset_comment(out, msl_offset);
@@ -2877,7 +2877,7 @@
 }
 
 bool GeneratorImpl::EmitVar(const ast::Var* var) {
-    auto* sem = program_->Sem().Get(var);
+    auto* sem = builder_.Sem().Get(var);
     auto* type = sem->Type()->UnwrapRef();
 
     auto out = Line();
@@ -2921,7 +2921,7 @@
 }
 
 bool GeneratorImpl::EmitLet(const ast::Let* let) {
-    auto* sem = program_->Sem().Get(let);
+    auto* sem = builder_.Sem().Get(let);
     auto* type = sem->Type();
 
     auto out = Line();
@@ -3042,4 +3042,8 @@
     return array_template_name_;
 }
 
+std::string GeneratorImpl::UniqueIdentifier(const std::string& prefix /* = "" */) {
+    return builder_.Symbols().New(prefix).Name();
+}
+
 }  // namespace tint::writer::msl
diff --git a/src/tint/lang/msl/ast_writer/generator_impl.h b/src/tint/lang/msl/ast_writer/generator_impl.h
index 115b75a..0dd560b 100644
--- a/src/tint/lang/msl/ast_writer/generator_impl.h
+++ b/src/tint/lang/msl/ast_writer/generator_impl.h
@@ -23,29 +23,13 @@
 
 #include "src/tint/lang/core/builtin/builtin_value.h"
 #include "src/tint/lang/msl/ast_writer/generator.h"
-#include "src/tint/lang/wgsl/ast/assignment_statement.h"
-#include "src/tint/lang/wgsl/ast/binary_expression.h"
-#include "src/tint/lang/wgsl/ast/bitcast_expression.h"
-#include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/continue_statement.h"
-#include "src/tint/lang/wgsl/ast/discard_statement.h"
-#include "src/tint/lang/wgsl/ast/expression.h"
-#include "src/tint/lang/wgsl/ast/if_statement.h"
-#include "src/tint/lang/wgsl/ast/index_accessor_expression.h"
-#include "src/tint/lang/wgsl/ast/interpolate_attribute.h"
-#include "src/tint/lang/wgsl/ast/loop_statement.h"
-#include "src/tint/lang/wgsl/ast/member_accessor_expression.h"
-#include "src/tint/lang/wgsl/ast/return_statement.h"
-#include "src/tint/lang/wgsl/ast/switch_statement.h"
-#include "src/tint/lang/wgsl/ast/unary_op_expression.h"
-#include "src/tint/lang/wgsl/program/program.h"
+#include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/sem/struct.h"
 #include "src/tint/utils/containers/scope_stack.h"
 #include "src/tint/utils/text/string_stream.h"
+#include "src/tint/utils/text/text_generator.h"
 #include "src/tint/writer/array_length_from_uniform_options.h"
-#include "src/tint/writer/ast_text_generator.h"
 
-// Forward declarations
 namespace tint::sem {
 class Builtin;
 class Call;
@@ -80,7 +64,7 @@
 SanitizedResult Sanitize(const Program* program, const Options& options);
 
 /// Implementation class for MSL generator
-class GeneratorImpl : public ASTTextGenerator {
+class GeneratorImpl : public utils::TextGenerator {
   public:
     /// Constructor
     /// @param program the program to generate
@@ -384,6 +368,17 @@
     /// first call.
     const std::string& ArrayType();
 
+    /// @copydoc utils::TextWrtiter::UniqueIdentifier
+    std::string UniqueIdentifier(const std::string& prefix = "") override;
+
+    /// Alias for builder_.TypeOf(ptr)
+    template <typename T>
+    auto TypeOf(T* ptr) {
+        return builder_.TypeOf(ptr);
+    }
+
+    ProgramBuilder builder_;
+
     TextBuffer helpers_;  // Helper functions emitted at the top of the output
 
     std::function<bool()> emit_continuing_;
diff --git a/src/tint/lang/msl/ast_writer/generator_impl_type_test.cc b/src/tint/lang/msl/ast_writer/generator_impl_type_test.cc
index 0025f27..b058c33 100644
--- a/src/tint/lang/msl/ast_writer/generator_impl_type_test.cc
+++ b/src/tint/lang/msl/ast_writer/generator_impl_type_test.cc
@@ -244,7 +244,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(s)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
     EXPECT_EQ(buf.String(), R"(struct S {
@@ -291,7 +291,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(type)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
 
@@ -400,7 +400,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(type)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
 
@@ -492,7 +492,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(type)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
 
@@ -576,7 +576,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(type)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
 
@@ -638,7 +638,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(type)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
     EXPECT_EQ(buf.String(), R"(struct S {
@@ -697,7 +697,7 @@
 
     GeneratorImpl& gen = Build();
 
-    TextGenerator::TextBuffer buf;
+    utils::TextGenerator::TextBuffer buf;
     auto* str = program->TypeOf(type)->As<type::Struct>();
     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
     EXPECT_EQ(buf.String(), R"(struct S {
diff --git a/src/tint/lang/spirv/ast_writer/generator.cc b/src/tint/lang/spirv/ast_writer/generator.cc
index 5fcb703..308bef8 100644
--- a/src/tint/lang/spirv/ast_writer/generator.cc
+++ b/src/tint/lang/spirv/ast_writer/generator.cc
@@ -19,8 +19,8 @@
 #include "src/tint/lang/spirv/ast_writer/generator_impl.h"
 #if TINT_BUILD_IR
 #include "src/tint/lang/core/ir/from_program.h"  // nogncheck
-#include "src/tint/lang/spirv/writer/writer.h"  // nogncheck
-#endif                                          // TINT_BUILD_IR
+#include "src/tint/lang/spirv/writer/writer.h"   // nogncheck
+#endif                                           // TINT_BUILD_IR
 
 namespace tint::writer::spirv {
 
diff --git a/src/tint/lang/wgsl/ast/transform/decompose_strided_matrix_test.cc b/src/tint/lang/wgsl/ast/transform/decompose_strided_matrix_test.cc
index f58dd44..d4326e9 100644
--- a/src/tint/lang/wgsl/ast/transform/decompose_strided_matrix_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/decompose_strided_matrix_test.cc
@@ -90,7 +90,7 @@
     auto* expect = R"(
 struct S {
   @size(16)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(16) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
@@ -148,7 +148,7 @@
     auto* expect = R"(
 struct S {
   @size(16)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(16) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
@@ -201,7 +201,7 @@
     auto* expect = R"(
 struct S {
   @size(16)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(16u) */
   @stride(8) @internal(disable_validation__ignore_stride)
   m : mat2x2<f32>,
@@ -256,7 +256,7 @@
     auto* expect = R"(
 struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
@@ -315,7 +315,7 @@
     auto* expect = R"(
 struct S {
   @size(16)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(16) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
@@ -371,7 +371,7 @@
     auto* expect = R"(
 struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
@@ -430,7 +430,7 @@
     auto* expect = R"(
 struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
@@ -497,7 +497,7 @@
     auto* expect = R"(
 struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
@@ -562,7 +562,7 @@
     auto* expect = R"(
 struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8u) */
   @stride(32) @internal(disable_validation__ignore_stride)
   m : mat2x2<f32>,
@@ -618,7 +618,7 @@
     auto* expect = R"(
 struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8u) */
   @stride(32) @internal(disable_validation__ignore_stride)
   m : mat2x2<f32>,
diff --git a/src/tint/lang/wgsl/ast/transform/packed_vec3_test.cc b/src/tint/lang/wgsl/ast/transform/packed_vec3_test.cc
index 356103a..391cd8d 100644
--- a/src/tint/lang/wgsl/ast/transform/packed_vec3_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/packed_vec3_test.cc
@@ -4185,7 +4185,7 @@
 struct S_tint_packed_vec3 {
   a : u32,
   @size(28)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(32) */
   v : __packed_vec3<f32>,
   b : u32,
@@ -4198,12 +4198,12 @@
 struct S {
   a : u32,
   @size(16)
-  padding_2 : u32,
+  padding_0 : u32,
   /* @offset(32) */
   v : vec3<f32>,
   b : u32,
   @size(80)
-  padding_3 : u32,
+  padding_1 : u32,
   /* @offset(128) */
   arr : array<vec3<f32>, 4>,
 }
diff --git a/src/tint/lang/wgsl/ast_writer/generator_impl.cc b/src/tint/lang/wgsl/ast_writer/generator_impl.cc
index 43dfcd8..74d7036 100644
--- a/src/tint/lang/wgsl/ast_writer/generator_impl.cc
+++ b/src/tint/lang/wgsl/ast_writer/generator_impl.cc
@@ -16,22 +16,57 @@
 
 #include <algorithm>
 
+#include "src/tint/lang/core/builtin/texel_format.h"
+#include "src/tint/lang/wgsl/ast/accessor_expression.h"
 #include "src/tint/lang/wgsl/ast/alias.h"
+#include "src/tint/lang/wgsl/ast/assignment_statement.h"
+#include "src/tint/lang/wgsl/ast/binary_expression.h"
+#include "src/tint/lang/wgsl/ast/bitcast_expression.h"
 #include "src/tint/lang/wgsl/ast/bool_literal_expression.h"
+#include "src/tint/lang/wgsl/ast/break_if_statement.h"
+#include "src/tint/lang/wgsl/ast/break_statement.h"
+#include "src/tint/lang/wgsl/ast/call_expression.h"
 #include "src/tint/lang/wgsl/ast/call_statement.h"
+#include "src/tint/lang/wgsl/ast/compound_assignment_statement.h"
+#include "src/tint/lang/wgsl/ast/const.h"
+#include "src/tint/lang/wgsl/ast/continue_statement.h"
+#include "src/tint/lang/wgsl/ast/diagnostic_attribute.h"
+#include "src/tint/lang/wgsl/ast/diagnostic_rule_name.h"
+#include "src/tint/lang/wgsl/ast/discard_statement.h"
 #include "src/tint/lang/wgsl/ast/float_literal_expression.h"
+#include "src/tint/lang/wgsl/ast/for_loop_statement.h"
 #include "src/tint/lang/wgsl/ast/id_attribute.h"
+#include "src/tint/lang/wgsl/ast/identifier.h"
+#include "src/tint/lang/wgsl/ast/identifier_expression.h"
+#include "src/tint/lang/wgsl/ast/if_statement.h"
+#include "src/tint/lang/wgsl/ast/increment_decrement_statement.h"
+#include "src/tint/lang/wgsl/ast/index_accessor_expression.h"
+#include "src/tint/lang/wgsl/ast/index_attribute.h"
+#include "src/tint/lang/wgsl/ast/int_literal_expression.h"
 #include "src/tint/lang/wgsl/ast/internal_attribute.h"
 #include "src/tint/lang/wgsl/ast/interpolate_attribute.h"
 #include "src/tint/lang/wgsl/ast/invariant_attribute.h"
+#include "src/tint/lang/wgsl/ast/let.h"
+#include "src/tint/lang/wgsl/ast/loop_statement.h"
+#include "src/tint/lang/wgsl/ast/member_accessor_expression.h"
 #include "src/tint/lang/wgsl/ast/module.h"
+#include "src/tint/lang/wgsl/ast/must_use_attribute.h"
+#include "src/tint/lang/wgsl/ast/override.h"
+#include "src/tint/lang/wgsl/ast/phony_expression.h"
+#include "src/tint/lang/wgsl/ast/return_statement.h"
 #include "src/tint/lang/wgsl/ast/stage_attribute.h"
 #include "src/tint/lang/wgsl/ast/stride_attribute.h"
 #include "src/tint/lang/wgsl/ast/struct_member_align_attribute.h"
 #include "src/tint/lang/wgsl/ast/struct_member_offset_attribute.h"
 #include "src/tint/lang/wgsl/ast/struct_member_size_attribute.h"
+#include "src/tint/lang/wgsl/ast/switch_statement.h"
+#include "src/tint/lang/wgsl/ast/templated_identifier.h"
+#include "src/tint/lang/wgsl/ast/unary_op_expression.h"
+#include "src/tint/lang/wgsl/ast/var.h"
 #include "src/tint/lang/wgsl/ast/variable_decl_statement.h"
+#include "src/tint/lang/wgsl/ast/while_statement.h"
 #include "src/tint/lang/wgsl/ast/workgroup_attribute.h"
+#include "src/tint/lang/wgsl/program/program.h"
 #include "src/tint/lang/wgsl/sem/struct.h"
 #include "src/tint/lang/wgsl/sem/switch_statement.h"
 #include "src/tint/utils/macros/defer.h"
@@ -39,10 +74,11 @@
 #include "src/tint/utils/math/math.h"
 #include "src/tint/utils/rtti/switch.h"
 #include "src/tint/utils/text/float_to_string.h"
+#include "src/tint/utils/text/string.h"
 
 namespace tint::writer::wgsl {
 
-GeneratorImpl::GeneratorImpl(const Program* program) : ASTTextGenerator(program) {}
+GeneratorImpl::GeneratorImpl(const Program* program) : program_(program) {}
 
 GeneratorImpl::~GeneratorImpl() = default;
 
@@ -298,12 +334,26 @@
     }
     Line() << "struct " << str->name->symbol.Name() << " {";
 
+    utils::Hashset<std::string_view, 8> member_names;
+    for (auto* mem : str->members) {
+        member_names.Add(mem->name->symbol.NameView());
+    }
+    size_t padding_idx = 0;
+    auto new_padding_name = [&] {
+        while (true) {
+            auto name = "padding_" + utils::ToString(padding_idx++);
+            if (member_names.Add(name)) {
+                return name;
+            }
+        }
+    };
+
     auto add_padding = [&](uint32_t size) {
         Line() << "@size(" << size << ")";
 
         // Note: u32 is the smallest primitive we currently support. When WGSL
         // supports smaller types, this will need to be updated.
-        Line() << UniqueIdentifier("padding") << " : u32,";
+        Line() << new_padding_name() << " : u32,";
     };
 
     IncrementIndent();
diff --git a/src/tint/lang/wgsl/ast_writer/generator_impl.h b/src/tint/lang/wgsl/ast_writer/generator_impl.h
index 9b0cfde..41e769b 100644
--- a/src/tint/lang/wgsl/ast_writer/generator_impl.h
+++ b/src/tint/lang/wgsl/ast_writer/generator_impl.h
@@ -17,31 +17,59 @@
 
 #include <string>
 
-#include "src/tint/lang/wgsl/ast/assignment_statement.h"
-#include "src/tint/lang/wgsl/ast/binary_expression.h"
-#include "src/tint/lang/wgsl/ast/bitcast_expression.h"
-#include "src/tint/lang/wgsl/ast/break_if_statement.h"
-#include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/compound_assignment_statement.h"
-#include "src/tint/lang/wgsl/ast/continue_statement.h"
-#include "src/tint/lang/wgsl/ast/discard_statement.h"
-#include "src/tint/lang/wgsl/ast/for_loop_statement.h"
-#include "src/tint/lang/wgsl/ast/if_statement.h"
-#include "src/tint/lang/wgsl/ast/index_accessor_expression.h"
-#include "src/tint/lang/wgsl/ast/loop_statement.h"
-#include "src/tint/lang/wgsl/ast/member_accessor_expression.h"
-#include "src/tint/lang/wgsl/ast/return_statement.h"
-#include "src/tint/lang/wgsl/ast/switch_statement.h"
-#include "src/tint/lang/wgsl/ast/unary_op_expression.h"
-#include "src/tint/lang/wgsl/program/program.h"
 #include "src/tint/lang/wgsl/sem/struct.h"
 #include "src/tint/utils/text/string_stream.h"
-#include "src/tint/writer/ast_text_generator.h"
+#include "src/tint/utils/text/text_generator.h"
+
+// Forward declarations
+namespace tint::ast {
+class AssignmentStatement;
+class Attribute;
+class BinaryExpression;
+enum class BinaryOp;
+class BitcastExpression;
+class BlockStatement;
+class BlockStatement;
+class BreakIfStatement;
+class BreakStatement;
+class CallExpression;
+class CaseStatement;
+class CompoundAssignmentStatement;
+class ConstAssert;
+class ContinueStatement;
+struct DiagnosticControl;
+class DiscardStatement;
+class Enable;
+class Expression;
+class ForLoopStatement;
+class Function;
+class Identifier;
+class IdentifierExpression;
+class IfStatement;
+class IncrementDecrementStatement;
+class IndexAccessorExpression;
+class LiteralExpression;
+class LoopStatement;
+class MemberAccessorExpression;
+class ReturnStatement;
+class Statement;
+class Statement;
+class Statement;
+class Struct;
+class SwitchStatement;
+class TypeDecl;
+class UnaryOpExpression;
+class Variable;
+class WhileStatement;
+}  // namespace tint::ast
+namespace tint::builtin {
+enum class TexelFormat;
+}  // namespace tint::builtin
 
 namespace tint::writer::wgsl {
 
 /// Implementation class for WGSL generator
-class GeneratorImpl : public ASTTextGenerator {
+class GeneratorImpl : public utils::TextGenerator {
   public:
     /// Constructor
     /// @param program the program
@@ -184,6 +212,9 @@
     /// @param out the output stream
     /// @param attrs the attribute list
     void EmitAttributes(utils::StringStream& out, utils::VectorRef<const ast::Attribute*> attrs);
+
+  private:
+    Program const* const program_;
 };
 
 }  // namespace tint::writer::wgsl
diff --git a/src/tint/lang/wgsl/ast_writer/generator_impl_type_test.cc b/src/tint/lang/wgsl/ast_writer/generator_impl_type_test.cc
index bd2c011..3fa7a73 100644
--- a/src/tint/lang/wgsl/ast_writer/generator_impl_type_test.cc
+++ b/src/tint/lang/wgsl/ast_writer/generator_impl_type_test.cc
@@ -195,7 +195,7 @@
     EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty());
     EXPECT_EQ(gen.Result(), R"(struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8) */
   a : i32,
   @size(4)
@@ -219,7 +219,7 @@
     EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty());
     EXPECT_EQ(gen.Result(), R"(struct S {
   @size(8)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(8) */
   tint_0_padding : i32,
   @size(4)
diff --git a/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.cc b/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.cc
index c04b075..ef27e67 100644
--- a/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.cc
+++ b/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.cc
@@ -41,7 +41,7 @@
 
 namespace tint::writer::syntax_tree {
 
-GeneratorImpl::GeneratorImpl(const Program* program) : ASTTextGenerator(program) {}
+GeneratorImpl::GeneratorImpl(const Program* program) : program_(program) {}
 
 GeneratorImpl::~GeneratorImpl() = default;
 
diff --git a/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.h b/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.h
index c02af16..60e35d3 100644
--- a/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.h
+++ b/src/tint/lang/wgsl/syntax_tree_writer/generator_impl.h
@@ -36,12 +36,12 @@
 #include "src/tint/lang/wgsl/program/program.h"
 #include "src/tint/lang/wgsl/sem/struct.h"
 #include "src/tint/utils/text/string_stream.h"
-#include "src/tint/writer/ast_text_generator.h"
+#include "src/tint/utils/text/text_generator.h"
 
 namespace tint::writer::syntax_tree {
 
 /// Implementation class for AST generator
-class GeneratorImpl : public ASTTextGenerator {
+class GeneratorImpl : public utils::TextGenerator {
   public:
     /// Constructor
     /// @param program the program
@@ -168,6 +168,9 @@
     /// Handles generating a attribute list
     /// @param attrs the attribute list
     void EmitAttributes(utils::VectorRef<const ast::Attribute*> attrs);
+
+  private:
+    Program const* const program_;
 };
 
 }  // namespace tint::writer::syntax_tree
diff --git a/src/tint/utils/text/text_generator.cc b/src/tint/utils/text/text_generator.cc
index 13fecb4..57fd106 100644
--- a/src/tint/utils/text/text_generator.cc
+++ b/src/tint/utils/text/text_generator.cc
@@ -20,12 +20,17 @@
 #include "src/tint/utils/containers/map.h"
 #include "src/tint/utils/debug/debug.h"
 
-namespace tint::writer {
+namespace tint::utils {
 
 TextGenerator::TextGenerator() = default;
 
 TextGenerator::~TextGenerator() = default;
 
+std::string TextGenerator::UniqueIdentifier(const std::string& /* = "" */) {
+    TINT_UNIMPLEMENTED(Utils, diagnostics_) << "UniqueIdentifier() not overridden";
+    return "<error>";
+}
+
 std::string TextGenerator::StructName(const type::Struct* s) {
     auto name = s->Name().Name();
     if (name.size() > 1 && name[0] == '_' && name[1] == '_') {
@@ -132,4 +137,4 @@
     buffer_->DecrementIndent();
 }
 
-}  // namespace tint::writer
+}  // namespace tint::utils
diff --git a/src/tint/utils/text/text_generator.h b/src/tint/utils/text/text_generator.h
index 73f28b6..7248560 100644
--- a/src/tint/utils/text/text_generator.h
+++ b/src/tint/utils/text/text_generator.h
@@ -24,7 +24,7 @@
 #include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/text/string_stream.h"
 
-namespace tint::writer {
+namespace tint::utils {
 
 /// Helper methods for generators which are creating text output
 class TextGenerator {
@@ -134,7 +134,9 @@
     /// @return a new, unique identifier with the given prefix.
     /// @param prefix optional prefix to apply to the generated identifier. If
     /// empty "tint_symbol" will be used.
-    virtual std::string UniqueIdentifier(const std::string& prefix = "") = 0;
+    /// TODO(crbug.com/tint/1992): The printers should not be creating new symbols. This should be
+    /// done as part of the IR-raise process. Remove once all writers have been moved to IR.
+    virtual std::string UniqueIdentifier(const std::string& prefix = "");
 
     /// @param s the structure
     /// @returns the name of the structure, taking special care of builtin structures that start
@@ -201,6 +203,6 @@
     std::unordered_map<const type::Struct*, std::string> builtin_struct_names_;
 };
 
-}  // namespace tint::writer
+}  // namespace tint::utils
 
 #endif  // SRC_TINT_UTILS_TEXT_TEXT_GENERATOR_H_
diff --git a/src/tint/writer/ast_text_generator.cc b/src/tint/writer/ast_text_generator.cc
deleted file mode 100644
index fe53641..0000000
--- a/src/tint/writer/ast_text_generator.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/writer/ast_text_generator.h"
-
-#include <algorithm>
-#include <limits>
-
-#include "src/tint/utils/containers/map.h"
-
-namespace tint::writer {
-
-ASTTextGenerator::ASTTextGenerator(const Program* program)
-    : program_(program), builder_(ProgramBuilder::Wrap(program)) {}
-
-ASTTextGenerator::~ASTTextGenerator() = default;
-
-std::string ASTTextGenerator::UniqueIdentifier(const std::string& prefix) {
-    return builder_.Symbols().New(prefix).Name();
-}
-
-}  // namespace tint::writer
diff --git a/src/tint/writer/ast_text_generator.h b/src/tint/writer/ast_text_generator.h
deleted file mode 100644
index 84f115e..0000000
--- a/src/tint/writer/ast_text_generator.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_WRITER_AST_TEXT_GENERATOR_H_
-#define SRC_TINT_WRITER_AST_TEXT_GENERATOR_H_
-
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "src/tint/lang/wgsl/program/program_builder.h"
-#include "src/tint/utils/text/text_generator.h"
-
-namespace tint::writer {
-
-/// Helper methods for generators which are creating text output
-class ASTTextGenerator : public TextGenerator {
-  public:
-    /// Constructor
-    /// @param program the program used by the generator
-    explicit ASTTextGenerator(const Program* program);
-    ~ASTTextGenerator() override;
-
-    /// @return a new, unique identifier with the given prefix.
-    /// @param prefix optional prefix to apply to the generated identifier. If
-    /// empty "tint_symbol" will be used.
-    std::string UniqueIdentifier(const std::string& prefix = "") override;
-
-  protected:
-    /// @returns the resolved type of the ast::Expression `expr`
-    /// @param expr the expression
-    const type::Type* TypeOf(const ast::Expression* expr) const { return builder_.TypeOf(expr); }
-
-    /// @returns the resolved type of the ast::TypeDecl `type_decl`
-    /// @param type_decl the type
-    const type::Type* TypeOf(const ast::TypeDecl* type_decl) const {
-        return builder_.TypeOf(type_decl);
-    }
-
-    /// The program
-    Program const* const program_;
-    /// A ProgramBuilder that thinly wraps program_
-    ProgramBuilder builder_;
-
-  private:
-    /// Map of builtin structure to unique generated name
-    std::unordered_map<const type::Struct*, std::string> builtin_struct_names_;
-};
-
-}  // namespace tint::writer
-
-#endif  // SRC_TINT_WRITER_AST_TEXT_GENERATOR_H_
diff --git a/src/tint/writer/ast_text_generator_test.cc b/src/tint/writer/ast_text_generator_test.cc
deleted file mode 100644
index d879f68..0000000
--- a/src/tint/writer/ast_text_generator_test.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/writer/ast_text_generator.h"
-
-#include "gtest/gtest.h"
-
-namespace tint::writer {
-namespace {
-
-TEST(ASTTextGeneratorTest, UniqueIdentifier) {
-    Program program(ProgramBuilder{});
-
-    ASTTextGenerator gen(&program);
-
-    ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident");
-    ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_1");
-}
-
-TEST(ASTTextGeneratorTest, UniqueIdentifier_ConflictWithExisting) {
-    ProgramBuilder builder;
-    builder.Symbols().Register("ident_1");
-    builder.Symbols().Register("ident_2");
-    Program program(std::move(builder));
-
-    ASTTextGenerator gen(&program);
-
-    ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident");
-    ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_3");
-    ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_4");
-    ASSERT_EQ(gen.UniqueIdentifier("ident"), "ident_5");
-}
-
-}  // namespace
-}  // namespace tint::writer
diff --git a/src/tint/writer/ir_text_generator.h b/src/tint/writer/ir_text_generator.h
index 64a9db6..9f37c27 100644
--- a/src/tint/writer/ir_text_generator.h
+++ b/src/tint/writer/ir_text_generator.h
@@ -23,7 +23,7 @@
 namespace tint::writer {
 
 /// Helper methods for generators which are creating text output
-class IRTextGenerator : public TextGenerator {
+class IRTextGenerator : public utils::TextGenerator {
   public:
     /// Constructor
     /// @param mod the IR module used by the generator
diff --git a/test/tint/bug/tint/1520.spvasm.expected.wgsl b/test/tint/bug/tint/1520.spvasm.expected.wgsl
index b3e32e9..4339e7f 100644
--- a/test/tint/bug/tint/1520.spvasm.expected.wgsl
+++ b/test/tint/bug/tint/1520.spvasm.expected.wgsl
@@ -1,6 +1,6 @@
 struct UniformBuffer {
   @size(16)
-  padding : u32,
+  padding_0 : u32,
   /* @offset(16) */
   unknownInput_S1_c0 : f32,
   /* @offset(32) */