CloneContext: Move hot code out of template members

This is causing code bloat. Move common code out to a single method that's implemented in the .cc file.
Saves about 10k from the all-features-enabled Release build of tint for x64.

Bug: tint:1226
Change-Id: I80b76e69521c3cc890c74b4bc73a68f1c9bdd3df
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/66446
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/clone_context.cc b/src/clone_context.cc
index 993431e..ed85d49 100644
--- a/src/clone_context.cc
+++ b/src/clone_context.cc
@@ -69,6 +69,42 @@
   return out;
 }
 
+tint::Cloneable* CloneContext::CloneCloneable(Cloneable* object) {
+  // If the input is nullptr, there's nothing to clone - just return nullptr.
+  if (object == nullptr) {
+    return nullptr;
+  }
+
+  // Was Replace() called for this object?
+  auto it = replacements_.find(object);
+  if (it != replacements_.end()) {
+    return it->second();
+  }
+
+  // Attempt to clone using the registered replacer functions.
+  auto& typeinfo = object->TypeInfo();
+  for (auto& transform : transforms_) {
+    if (typeinfo.Is(*transform.typeinfo)) {
+      if (auto* transformed = transform.function(object)) {
+        return transformed;
+      }
+      break;
+    }
+  }
+
+  // No transform for this type, or the transform returned nullptr.
+  // Clone with T::Clone().
+  return object->Clone(this);
+}
+
+void CloneContext::CheckedCastFailure(Cloneable* got,
+                                      const TypeInfo& expected) {
+  TINT_ICE(Clone, Diagnostics())
+      << "Cloned object was not of the expected type\n"
+      << "got:      " << got->TypeInfo().name << "\n"
+      << "expected: " << expected.name;
+}
+
 diag::List& CloneContext::Diagnostics() const {
   return dst->Diagnostics();
 }
diff --git a/src/clone_context.h b/src/clone_context.h
index c5437a5..4f72d2f 100644
--- a/src/clone_context.h
+++ b/src/clone_context.h
@@ -24,6 +24,7 @@
 
 #include "src/castable.h"
 #include "src/debug.h"
+#include "src/program_id.h"
 #include "src/symbol.h"
 #include "src/traits.h"
 
@@ -39,7 +40,7 @@
 }  // namespace ast
 
 ProgramID ProgramIDOf(const Program*);
-ProgramID ProgramIDOf(const ast::Node*);
+ProgramID ProgramIDOf(const ProgramBuilder*);
 
 /// Cloneable is the base class for all objects that can be cloned
 class Cloneable : public Castable<Cloneable> {
@@ -93,50 +94,19 @@
   /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
   /// the Node or sem::Type `a` must be owned by the Program #src.
   ///
-  /// @param a the `Node` or `sem::Type` to clone
+  /// @param object the type deriving from Cloneable to clone
   /// @return the cloned node
   template <typename T>
-  T* Clone(T* a) {
-    // If the input is nullptr, there's nothing to clone - just return nullptr.
-    if (a == nullptr) {
-      return nullptr;
-    }
-
+  T* Clone(T* object) {
     if (src) {
-      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, a);
+      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object);
     }
-
-    // Was Replace() called for this object?
-    auto it = replacements_.find(a);
-    if (it != replacements_.end()) {
-      auto* replacement = it->second();
-      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, replacement);
-      return CheckedCast<T>(replacement);
+    if (auto* cloned = CloneCloneable(object)) {
+      auto* out = CheckedCast<T>(cloned);
+      TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out);
+      return out;
     }
-
-    Cloneable* cloned = nullptr;
-
-    // Attempt to clone using the registered replacer functions.
-    auto& typeinfo = a->TypeInfo();
-    for (auto& transform : transforms_) {
-      if (!typeinfo.Is(*transform.typeinfo)) {
-        continue;
-      }
-      cloned = transform.function(a);
-      break;
-    }
-
-    if (!cloned) {
-      // No transform for this type, or the transform returned nullptr.
-      // Clone with T::Clone().
-      cloned = a->Clone(this);
-    }
-
-    auto* out = CheckedCast<T>(cloned);
-
-    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out);
-
-    return out;
+    return nullptr;
   }
 
   /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is
@@ -148,7 +118,7 @@
   /// If the CloneContext is cloning from a Program to a ProgramBuilder, then
   /// the Node or sem::Type `a` must be owned by the Program #src.
   ///
-  /// @param a the `Node` or `sem::Type` to clone
+  /// @param a the type deriving from Cloneable to clone
   /// @return the cloned node
   template <typename T>
   T* CloneWithoutTransform(T* a) {
@@ -519,20 +489,25 @@
     if (TO* cast = obj->template As<TO>()) {
       return cast;
     }
-    TINT_ICE(Clone, Diagnostics())
-        << "Cloned object was not of the expected type\n"
-        << "got:      " << obj->TypeInfo().name << "\n"
-        << "expected: " << TypeInfo::Of<TO>().name;
+    CheckedCastFailure(obj, TypeInfo::Of<TO>());
     return nullptr;
   }
 
+  /// Clones a Cloneable object, using any replacements or transforms that have
+  /// been configured.
+  tint::Cloneable* CloneCloneable(Cloneable* object);
+
+  /// Adds an error diagnostic to Diagnostics() that the cloned object was not
+  /// of the expected type.
+  void CheckedCastFailure(Cloneable* got, const TypeInfo& expected);
+
   /// @returns the diagnostic list of #dst
   diag::List& Diagnostics() const;
 
   /// A vector of Cloneable*
   using CloneableList = std::vector<Cloneable*>;
 
-  // Transformations to be applied to a list (vector)
+  /// Transformations to be applied to a list (vector)
   struct ListTransforms {
     /// Constructor
     ListTransforms();
diff --git a/src/clone_context_test.cc b/src/clone_context_test.cc
index 391c7c6..e3720cb 100644
--- a/src/clone_context_test.cc
+++ b/src/clone_context_test.cc
@@ -844,7 +844,7 @@
         Allocator allocator;
         ctx.Clone(allocator.Create<ProgramNode>(ProgramID::New(), dst.ID()));
       },
-      R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, a))");
+      R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object))");
 }
 
 TEST_F(CloneContextTest, ProgramIDs_Clone_ObjectNotOwnedByDst) {