[tint] Move builtin structs to the type namespace

This code is not tied to the WGSL resolver, and we want to be able to
use it from IR code.

Change-Id: Ief220bb4cf3e7952f9588bccdfb74c6a97334a6f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/141680
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 5c8d48d..89d5f1b 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -848,8 +848,6 @@
 
 libtint_source_set("libtint_type_src") {
   sources = [
-    "resolver/builtin_structs.cc",
-    "resolver/builtin_structs.h",
     "type/abstract_float.cc",
     "type/abstract_float.h",
     "type/abstract_int.cc",
@@ -864,6 +862,8 @@
     "type/atomic.h",
     "type/bool.cc",
     "type/bool.h",
+    "type/builtin_structs.cc",
+    "type/builtin_structs.h",
     "type/clone_context.h",
     "type/depth_multisampled_texture.cc",
     "type/depth_multisampled_texture.h",
@@ -1798,6 +1798,7 @@
     sources = [
       "type/atomic_test.cc",
       "type/bool_test.cc",
+      "type/builtin_structs_test.cc",
       "type/depth_multisampled_texture_test.cc",
       "type/depth_texture_test.cc",
       "type/external_texture_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 8c6ea8a..a529f04 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -269,8 +269,6 @@
   reflection.h
   reader/reader.cc
   reader/reader.h
-  resolver/builtin_structs.cc
-  resolver/builtin_structs.h
   resolver/const_eval.cc
   resolver/const_eval.h
   resolver/dependency_graph.cc
@@ -477,6 +475,8 @@
   type/atomic.h
   type/bool.cc
   type/bool.h
+  type/builtin_structs.cc
+  type/builtin_structs.h
   type/clone_context.h
   type/depth_multisampled_texture.cc
   type/depth_multisampled_texture.h
@@ -1102,6 +1102,7 @@
     type/array_test.cc
     type/atomic_test.cc
     type/bool_test.cc
+    type/builtin_structs_test.cc
     type/depth_multisampled_texture_test.cc
     type/depth_texture_test.cc
     type/external_texture_test.cc
diff --git a/src/tint/diagnostic/diagnostic.h b/src/tint/diagnostic/diagnostic.h
index b56bd8d..bec9ab0 100644
--- a/src/tint/diagnostic/diagnostic.h
+++ b/src/tint/diagnostic/diagnostic.h
@@ -37,6 +37,7 @@
 /// diagnostic message.
 enum class System {
     AST,
+    Builtin,
     Clone,
     Constant,
     Inspector,
diff --git a/src/tint/ir/transform/demote_to_helper_test.cc b/src/tint/ir/transform/demote_to_helper_test.cc
index 9e41b6b..fe75df1 100644
--- a/src/tint/ir/transform/demote_to_helper_test.cc
+++ b/src/tint/ir/transform/demote_to_helper_test.cc
@@ -17,7 +17,7 @@
 #include <utility>
 
 #include "src/tint/ir/transform/test_helper.h"
-#include "src/tint/resolver/builtin_structs.h"
+#include "src/tint/type/builtin_structs.h"
 #include "src/tint/type/f32.h"
 #include "src/tint/type/storage_texture.h"
 
@@ -769,9 +769,8 @@
             b.Discard();
             b.ExitIf(ifelse);
         });
-        auto* result =
-            b.Call(resolver::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()),
-                   builtin::Function::kAtomicCompareExchangeWeak, buffer, 0_i, 42_i);
+        auto* result = b.Call(type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()),
+                              builtin::Function::kAtomicCompareExchangeWeak, buffer, 0_i, 42_i);
         b.Add(ty.i32(), b.Access(ty.i32(), result, 0_i), 1_i);
         b.Return(ep, 0.5_f);
     });
diff --git a/src/tint/resolver/builtin_structs.h b/src/tint/resolver/builtin_structs.h
deleted file mode 100644
index fd6d98f..0000000
--- a/src/tint/resolver/builtin_structs.h
+++ /dev/null
@@ -1,60 +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_RESOLVER_BUILTIN_STRUCTS_H_
-#define SRC_TINT_RESOLVER_BUILTIN_STRUCTS_H_
-
-// Forward declarations
-namespace tint {
-class SymbolTable;
-}  // namespace tint
-namespace tint::type {
-class Manager;
-class Struct;
-class Type;
-}  // namespace tint::type
-
-namespace tint::resolver {
-
-/**
- * @param types the type manager
- * @param symbols the symbol table
- * @param ty the type of the `fract` and `whole` struct members.
- * @return the builtin struct type for a modf() builtin call.
- */
-type::Struct* CreateModfResult(type::Manager& types, SymbolTable& symbols, const type::Type* ty);
-
-/**
- * @param types the type manager
- * @param symbols the symbol table
- * @param fract the type of the `fract` struct member.
- * @return the builtin struct type for a frexp() builtin call.
- */
-type::Struct* CreateFrexpResult(type::Manager& types,
-                                SymbolTable& symbols,
-                                const type::Type* fract);
-
-/**
- * @param types the type manager
- * @param symbols the symbol table
- * @param ty the type of the `old_value` struct member.
- * @return the builtin struct type for a atomic_compare_exchange() builtin call.
- */
-type::Struct* CreateAtomicCompareExchangeResult(type::Manager& types,
-                                                SymbolTable& symbols,
-                                                const type::Type* ty);
-
-}  // namespace tint::resolver
-
-#endif  // SRC_TINT_RESOLVER_BUILTIN_STRUCTS_H_
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index 755351f..f6bf22e 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -20,7 +20,6 @@
 
 #include "src/tint/ast/binary_expression.h"
 #include "src/tint/program_builder.h"
-#include "src/tint/resolver/builtin_structs.h"
 #include "src/tint/sem/evaluation_stage.h"
 #include "src/tint/sem/pipeline_stage_set.h"
 #include "src/tint/sem/value_constructor.h"
@@ -30,6 +29,7 @@
 #include "src/tint/type/abstract_int.h"
 #include "src/tint/type/abstract_numeric.h"
 #include "src/tint/type/atomic.h"
+#include "src/tint/type/builtin_structs.h"
 #include "src/tint/type/depth_multisampled_texture.h"
 #include "src/tint/type/depth_texture.h"
 #include "src/tint/type/external_texture.h"
@@ -821,25 +821,26 @@
 }
 
 const type::Struct* build_modf_result(MatchState& state, const type::Type* el) {
-    return CreateModfResult(state.builder.Types(), state.builder.Symbols(), el);
+    return type::CreateModfResult(state.builder.Types(), state.builder.Symbols(), el);
 }
 
 const type::Struct* build_modf_result_vec(MatchState& state, Number& n, const type::Type* el) {
     auto* vec = state.builder.create<type::Vector>(el, n.Value());
-    return CreateModfResult(state.builder.Types(), state.builder.Symbols(), vec);
+    return type::CreateModfResult(state.builder.Types(), state.builder.Symbols(), vec);
 }
 
 const type::Struct* build_frexp_result(MatchState& state, const type::Type* el) {
-    return CreateFrexpResult(state.builder.Types(), state.builder.Symbols(), el);
+    return type::CreateFrexpResult(state.builder.Types(), state.builder.Symbols(), el);
 }
 
 const type::Struct* build_frexp_result_vec(MatchState& state, Number& n, const type::Type* el) {
     auto* vec = state.builder.create<type::Vector>(el, n.Value());
-    return CreateFrexpResult(state.builder.Types(), state.builder.Symbols(), vec);
+    return type::CreateFrexpResult(state.builder.Types(), state.builder.Symbols(), vec);
 }
 
 const type::Struct* build_atomic_compare_exchange_result(MatchState& state, const type::Type* ty) {
-    return CreateAtomicCompareExchangeResult(state.builder.Types(), state.builder.Symbols(), ty);
+    return type::CreateAtomicCompareExchangeResult(state.builder.Types(), state.builder.Symbols(),
+                                                   ty);
 }
 
 /// ParameterInfo describes a parameter
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 4cc93c7..3a47118 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -43,7 +43,6 @@
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/builtin/builtin.h"
-#include "src/tint/resolver/builtin_structs.h"
 #include "src/tint/resolver/uniformity.h"
 #include "src/tint/sem/break_if_statement.h"
 #include "src/tint/sem/builtin_enum_expression.h"
@@ -70,6 +69,7 @@
 #include "src/tint/type/abstract_int.h"
 #include "src/tint/type/array.h"
 #include "src/tint/type/atomic.h"
+#include "src/tint/type/builtin_structs.h"
 #include "src/tint/type/depth_multisampled_texture.h"
 #include "src/tint/type/depth_texture.h"
 #include "src/tint/type/external_texture.h"
@@ -2824,57 +2824,59 @@
         case builtin::Builtin::kPackedVec3:
             return packed_vec3_t();
         case builtin::Builtin::kAtomicCompareExchangeResultI32:
-            return CreateAtomicCompareExchangeResult(builder_->Types(), builder_->Symbols(), i32());
+            return type::CreateAtomicCompareExchangeResult(builder_->Types(), builder_->Symbols(),
+                                                           i32());
         case builtin::Builtin::kAtomicCompareExchangeResultU32:
-            return CreateAtomicCompareExchangeResult(builder_->Types(), builder_->Symbols(), u32());
+            return type::CreateAtomicCompareExchangeResult(builder_->Types(), builder_->Symbols(),
+                                                           u32());
         case builtin::Builtin::kFrexpResultAbstract:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), af());
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), af());
         case builtin::Builtin::kFrexpResultF16:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), f16());
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), f16());
         case builtin::Builtin::kFrexpResultF32:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), f32());
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), f32());
         case builtin::Builtin::kFrexpResultVec2Abstract:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(af(), 2));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(af(), 2));
         case builtin::Builtin::kFrexpResultVec2F16:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f16(), 2));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f16(), 2));
         case builtin::Builtin::kFrexpResultVec2F32:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f32(), 2));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f32(), 2));
         case builtin::Builtin::kFrexpResultVec3Abstract:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(af(), 3));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(af(), 3));
         case builtin::Builtin::kFrexpResultVec3F16:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f16(), 3));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f16(), 3));
         case builtin::Builtin::kFrexpResultVec3F32:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f32(), 3));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f32(), 3));
         case builtin::Builtin::kFrexpResultVec4Abstract:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(af(), 4));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(af(), 4));
         case builtin::Builtin::kFrexpResultVec4F16:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f16(), 4));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f16(), 4));
         case builtin::Builtin::kFrexpResultVec4F32:
-            return CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f32(), 4));
+            return type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), vec(f32(), 4));
         case builtin::Builtin::kModfResultAbstract:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), af());
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), af());
         case builtin::Builtin::kModfResultF16:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), f16());
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), f16());
         case builtin::Builtin::kModfResultF32:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), f32());
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), f32());
         case builtin::Builtin::kModfResultVec2Abstract:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(af(), 2));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(af(), 2));
         case builtin::Builtin::kModfResultVec2F16:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f16(), 2));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f16(), 2));
         case builtin::Builtin::kModfResultVec2F32:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f32(), 2));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f32(), 2));
         case builtin::Builtin::kModfResultVec3Abstract:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(af(), 3));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(af(), 3));
         case builtin::Builtin::kModfResultVec3F16:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f16(), 3));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f16(), 3));
         case builtin::Builtin::kModfResultVec3F32:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f32(), 3));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f32(), 3));
         case builtin::Builtin::kModfResultVec4Abstract:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(af(), 4));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(af(), 4));
         case builtin::Builtin::kModfResultVec4F16:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f16(), 4));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f16(), 4));
         case builtin::Builtin::kModfResultVec4F32:
-            return CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f32(), 4));
+            return type::CreateModfResult(builder_->Types(), builder_->Symbols(), vec(f32(), 4));
         case builtin::Builtin::kUndefined:
             break;
     }
diff --git a/src/tint/resolver/builtin_structs.cc b/src/tint/type/builtin_structs.cc
similarity index 71%
rename from src/tint/resolver/builtin_structs.cc
rename to src/tint/type/builtin_structs.cc
index 0e7c659..8e54e85 100644
--- a/src/tint/resolver/builtin_structs.cc
+++ b/src/tint/type/builtin_structs.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/resolver/builtin_structs.h"
+#include "src/tint/type/builtin_structs.h"
 
 #include <algorithm>
 #include <string>
@@ -32,7 +32,7 @@
 #include "src/tint/type/vector.h"
 #include "src/tint/utils/string.h"
 
-namespace tint::resolver {
+namespace tint::type {
 
 constexpr std::array kModfVecF32Names{
     builtin::Builtin::kModfResultVec2F32,
@@ -49,16 +49,16 @@
     builtin::Builtin::kModfResultVec3Abstract,
     builtin::Builtin::kModfResultVec4Abstract,
 };
-type::Struct* CreateModfResult(type::Manager& types, SymbolTable& symbols, const type::Type* ty) {
-    auto build = [&](builtin::Builtin name, const type::Type* t) {
+Struct* CreateModfResult(Manager& types, SymbolTable& symbols, const Type* ty) {
+    auto build = [&](builtin::Builtin name, const Type* t) {
         return types.Struct(symbols.Register(utils::ToString(name)),
                             {{symbols.Register("fract"), t}, {symbols.Register("whole"), t}});
     };
     return Switch(
         ty,  //
-        [&](const type::F32*) { return build(builtin::Builtin::kModfResultF32, ty); },
-        [&](const type::F16*) { return build(builtin::Builtin::kModfResultF16, ty); },
-        [&](const type::AbstractFloat*) {
+        [&](const F32*) { return build(builtin::Builtin::kModfResultF32, ty); },
+        [&](const F16*) { return build(builtin::Builtin::kModfResultF16, ty); },
+        [&](const AbstractFloat*) {
             auto* abstract = build(builtin::Builtin::kModfResultAbstract, ty);
             abstract->SetConcreteTypes(utils::Vector{
                 build(builtin::Builtin::kModfResultF32, types.f32()),
@@ -66,13 +66,13 @@
             });
             return abstract;
         },
-        [&](const type::Vector* vec) {
+        [&](const Vector* vec) {
             auto width = vec->Width();
             return Switch(
                 vec->type(),  //
-                [&](const type::F32*) { return build(kModfVecF32Names[width - 2], vec); },
-                [&](const type::F16*) { return build(kModfVecF16Names[width - 2], vec); },
-                [&](const type::AbstractFloat*) {
+                [&](const F32*) { return build(kModfVecF32Names[width - 2], vec); },
+                [&](const F16*) { return build(kModfVecF16Names[width - 2], vec); },
+                [&](const AbstractFloat*) {
                     auto* abstract = build(kModfVecAbstractNames[width - 2], vec);
                     abstract->SetConcreteTypes(utils::Vector{
                         build(kModfVecF32Names[width - 2], types.vec(types.f32(), width)),
@@ -81,12 +81,12 @@
                     return abstract;
                 },
                 [&](Default) {
-                    TINT_ASSERT(Resolver, false && "unhandled modf type");
+                    TINT_ASSERT(Builtin, false && "unhandled modf type");
                     return nullptr;
                 });
         },
         [&](Default) {
-            TINT_ASSERT(Resolver, false && "unhandled modf type");
+            TINT_ASSERT(Builtin, false && "unhandled modf type");
             return nullptr;
         });
 }
@@ -106,17 +106,17 @@
     builtin::Builtin::kFrexpResultVec3Abstract,
     builtin::Builtin::kFrexpResultVec4Abstract,
 };
-type::Struct* CreateFrexpResult(type::Manager& types, SymbolTable& symbols, const type::Type* ty) {
-    auto build = [&](builtin::Builtin name, const type::Type* fract_ty, const type::Type* exp_ty) {
+Struct* CreateFrexpResult(Manager& types, SymbolTable& symbols, const Type* ty) {
+    auto build = [&](builtin::Builtin name, const Type* fract_ty, const Type* exp_ty) {
         return types.Struct(
             symbols.Register(utils::ToString(name)),
             {{symbols.Register("fract"), fract_ty}, {symbols.Register("exp"), exp_ty}});
     };
     return Switch(
         ty,  //
-        [&](const type::F32*) { return build(builtin::Builtin::kFrexpResultF32, ty, types.i32()); },
-        [&](const type::F16*) { return build(builtin::Builtin::kFrexpResultF16, ty, types.i32()); },
-        [&](const type::AbstractFloat*) {
+        [&](const F32*) { return build(builtin::Builtin::kFrexpResultF32, ty, types.i32()); },
+        [&](const F16*) { return build(builtin::Builtin::kFrexpResultF16, ty, types.i32()); },
+        [&](const AbstractFloat*) {
             auto* abstract = build(builtin::Builtin::kFrexpResultAbstract, ty, types.AInt());
             abstract->SetConcreteTypes(utils::Vector{
                 build(builtin::Builtin::kFrexpResultF32, types.f32(), types.i32()),
@@ -124,17 +124,17 @@
             });
             return abstract;
         },
-        [&](const type::Vector* vec) {
+        [&](const Vector* vec) {
             auto width = vec->Width();
             return Switch(
                 vec->type(),  //
-                [&](const type::F32*) {
+                [&](const F32*) {
                     return build(kFrexpVecF32Names[width - 2], ty, types.vec(types.i32(), width));
                 },
-                [&](const type::F16*) {
+                [&](const F16*) {
                     return build(kFrexpVecF16Names[width - 2], ty, types.vec(types.i32(), width));
                 },
-                [&](const type::AbstractFloat*) {
+                [&](const AbstractFloat*) {
                     auto* vec_f32 = types.vec(types.f32(), width);
                     auto* vec_f16 = types.vec(types.f16(), width);
                     auto* vec_i32 = types.vec(types.i32(), width);
@@ -147,19 +147,17 @@
                     return abstract;
                 },
                 [&](Default) {
-                    TINT_ASSERT(Resolver, false && "unhandled frexp type");
+                    TINT_ASSERT(Builtin, false && "unhandled frexp type");
                     return nullptr;
                 });
         },
         [&](Default) {
-            TINT_ASSERT(Resolver, false && "unhandled frexp type");
+            TINT_ASSERT(Builtin, false && "unhandled frexp type");
             return nullptr;
         });
 }
 
-type::Struct* CreateAtomicCompareExchangeResult(type::Manager& types,
-                                                SymbolTable& symbols,
-                                                const type::Type* ty) {
+Struct* CreateAtomicCompareExchangeResult(Manager& types, SymbolTable& symbols, const Type* ty) {
     auto build = [&](builtin::Builtin name) {
         return types.Struct(symbols.Register(utils::ToString(name)),
                             {
@@ -169,12 +167,12 @@
     };
     return Switch(
         ty,  //
-        [&](const type::I32*) { return build(builtin::Builtin::kAtomicCompareExchangeResultI32); },
-        [&](const type::U32*) { return build(builtin::Builtin::kAtomicCompareExchangeResultU32); },
+        [&](const I32*) { return build(builtin::Builtin::kAtomicCompareExchangeResultI32); },
+        [&](const U32*) { return build(builtin::Builtin::kAtomicCompareExchangeResultU32); },
         [&](Default) {
-            TINT_ASSERT(Resolver, false && "unhandled atomic_compare_exchange type");
+            TINT_ASSERT(Builtin, false && "unhandled atomic_compare_exchange type");
             return nullptr;
         });
 }
 
-}  // namespace tint::resolver
+}  // namespace tint::type
diff --git a/src/tint/type/builtin_structs.h b/src/tint/type/builtin_structs.h
new file mode 100644
index 0000000..3e376b4
--- /dev/null
+++ b/src/tint/type/builtin_structs.h
@@ -0,0 +1,50 @@
+// 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_TYPE_BUILTIN_STRUCTS_H_
+#define SRC_TINT_TYPE_BUILTIN_STRUCTS_H_
+
+// Forward declarations
+namespace tint {
+class SymbolTable;
+}  // namespace tint
+namespace tint::type {
+class Manager;
+class Struct;
+class Type;
+}  // namespace tint::type
+
+namespace tint::type {
+
+/// @param types the type manager
+/// @param symbols the symbol table
+/// @param ty the type of the `fract` and `whole` struct members.
+/// @returns the builtin struct type for a modf() builtin call.
+Struct* CreateModfResult(Manager& types, SymbolTable& symbols, const Type* ty);
+
+/// @param types the type manager
+/// @param symbols the symbol table
+/// @param fract the type of the `fract` struct member.
+/// @returns the builtin struct type for a frexp() builtin call.
+Struct* CreateFrexpResult(Manager& types, SymbolTable& symbols, const Type* fract);
+
+/// @param types the type manager
+/// @param symbols the symbol table
+/// @param ty the type of the `old_value` struct member.
+/// @returns the builtin struct type for a atomic_compare_exchange() builtin call.
+Struct* CreateAtomicCompareExchangeResult(Manager& types, SymbolTable& symbols, const Type* ty);
+
+}  // namespace tint::type
+
+#endif  // SRC_TINT_TYPE_BUILTIN_STRUCTS_H_
diff --git a/src/tint/type/builtin_structs_test.cc b/src/tint/type/builtin_structs_test.cc
new file mode 100644
index 0000000..84eaa4b
--- /dev/null
+++ b/src/tint/type/builtin_structs_test.cc
@@ -0,0 +1,164 @@
+// 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/type/builtin_structs.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/program_id.h"
+#include "src/tint/symbol_table.h"
+#include "src/tint/type/abstract_float.h"
+#include "src/tint/type/abstract_int.h"
+#include "src/tint/type/bool.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/manager.h"
+#include "src/tint/type/u32.h"
+#include "src/tint/type/vector.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::type {
+namespace {
+
+enum ElementType {
+    kAFloat,
+    kAInt,
+    kF32,
+    kF16,
+    kI32,
+    kU32,
+};
+
+template <typename T>
+class BuiltinStructsTest : public testing::TestWithParam<T> {
+  protected:
+    BuiltinStructsTest() : symbols(ProgramID::New()) {}
+
+    const Type* Make(ElementType t) {
+        switch (t) {
+            case kAFloat:
+                return ty.AFloat();
+            case kAInt:
+                return ty.AInt();
+            case kF32:
+                return ty.f32();
+            case kF16:
+                return ty.f16();
+            case kI32:
+                return ty.i32();
+            case kU32:
+                return ty.u32();
+        }
+        return nullptr;
+    }
+
+    Manager ty;
+    SymbolTable symbols;
+};
+
+struct FrexpCase {
+    ElementType in;
+    ElementType fract;
+    ElementType exp;
+};
+class BuiltinFrexpResultStructTest : public BuiltinStructsTest<FrexpCase> {
+  protected:
+    void Run(const Type* in_type, const Type* fract_type, const Type* exp_type) {
+        auto* result = CreateFrexpResult(ty, symbols, in_type);
+
+        auto* str = As<Struct>(result);
+        ASSERT_NE(str, nullptr);
+        ASSERT_EQ(str->Members().Length(), 2u);
+        EXPECT_EQ(str->Members()[0]->Name().Name(), "fract");
+        EXPECT_EQ(str->Members()[0]->Type(), fract_type);
+        EXPECT_EQ(str->Members()[1]->Name().Name(), "exp");
+        EXPECT_EQ(str->Members()[1]->Type(), exp_type);
+    }
+};
+TEST_P(BuiltinFrexpResultStructTest, Scalar) {
+    auto params = GetParam();
+    Run(Make(params.in), Make(params.fract), Make(params.exp));
+}
+TEST_P(BuiltinFrexpResultStructTest, Vec2) {
+    auto params = GetParam();
+    Run(ty.vec2(Make(params.in)), ty.vec2(Make(params.fract)), ty.vec2(Make(params.exp)));
+}
+TEST_P(BuiltinFrexpResultStructTest, Vec3) {
+    auto params = GetParam();
+    Run(ty.vec3(Make(params.in)), ty.vec3(Make(params.fract)), ty.vec3(Make(params.exp)));
+}
+TEST_P(BuiltinFrexpResultStructTest, Vec4) {
+    auto params = GetParam();
+    Run(ty.vec4(Make(params.in)), ty.vec4(Make(params.fract)), ty.vec4(Make(params.exp)));
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinFrexpResultStructTest,
+                         BuiltinFrexpResultStructTest,
+                         testing::Values(FrexpCase{kAFloat, kAFloat, kAInt},
+                                         FrexpCase{kF32, kF32, kI32},
+                                         FrexpCase{kF16, kF16, kI32}));
+
+class BuiltinModfResultStructTest : public BuiltinStructsTest<ElementType> {
+  protected:
+    void Run(const Type* type) {
+        auto* result = CreateModfResult(ty, symbols, type);
+
+        auto* str = As<Struct>(result);
+        ASSERT_NE(str, nullptr);
+        ASSERT_EQ(str->Members().Length(), 2u);
+        EXPECT_EQ(str->Members()[0]->Name().Name(), "fract");
+        EXPECT_EQ(str->Members()[0]->Type(), type);
+        EXPECT_EQ(str->Members()[1]->Name().Name(), "whole");
+        EXPECT_EQ(str->Members()[1]->Type(), type);
+    }
+};
+TEST_P(BuiltinModfResultStructTest, Scalar) {
+    Run(Make(GetParam()));
+}
+TEST_P(BuiltinModfResultStructTest, Vec2) {
+    Run(ty.vec2(Make(GetParam())));
+}
+TEST_P(BuiltinModfResultStructTest, Vec3) {
+    Run(ty.vec3(Make(GetParam())));
+}
+TEST_P(BuiltinModfResultStructTest, Vec4) {
+    Run(ty.vec4(Make(GetParam())));
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinModfResultStructTest,
+                         BuiltinModfResultStructTest,
+                         testing::Values(kAFloat, kF32, kF16));
+
+class BuiltinAtomicCompareExchangeResultStructTest : public BuiltinStructsTest<ElementType> {
+  protected:
+    void Run(const Type* type) {
+        auto* result = CreateAtomicCompareExchangeResult(ty, symbols, type);
+
+        auto* str = As<Struct>(result);
+        ASSERT_NE(str, nullptr);
+        ASSERT_EQ(str->Members().Length(), 2u);
+        EXPECT_EQ(str->Members()[0]->Name().Name(), "old_value");
+        EXPECT_EQ(str->Members()[0]->Type(), type);
+        EXPECT_EQ(str->Members()[1]->Name().Name(), "exchanged");
+        EXPECT_EQ(str->Members()[1]->Type(), ty.bool_());
+    }
+};
+TEST_P(BuiltinAtomicCompareExchangeResultStructTest, Scalar) {
+    Run(Make(GetParam()));
+}
+INSTANTIATE_TEST_SUITE_P(BuiltinAtomicCompareExchangeResultStructTest,
+                         BuiltinAtomicCompareExchangeResultStructTest,
+                         testing::Values(kI32, kU32));
+
+}  // namespace
+}  // namespace tint::type
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc
index 759ac00..acff41f 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc
@@ -15,7 +15,7 @@
 #include "src/tint/writer/spirv/ir/test_helper_ir.h"
 
 #include "src/tint/builtin/function.h"
-#include "src/tint/resolver/builtin_structs.h"
+#include "src/tint/type/builtin_structs.h"
 
 using namespace tint::number_suffixes;  // NOLINT
 
@@ -209,7 +209,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Frexp_F32) {
-    auto* str = resolver::CreateFrexpResult(ty, mod.symbols, ty.f32());
+    auto* str = type::CreateFrexpResult(ty, mod.symbols, ty.f32());
     auto* arg = b.FunctionParam("arg", ty.f32());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
@@ -224,7 +224,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Frexp_F16) {
-    auto* str = resolver::CreateFrexpResult(ty, mod.symbols, ty.f16());
+    auto* str = type::CreateFrexpResult(ty, mod.symbols, ty.f16());
     auto* arg = b.FunctionParam("arg", ty.f16());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
@@ -239,7 +239,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Frexp_Vec2f) {
-    auto* str = resolver::CreateFrexpResult(ty, mod.symbols, ty.vec2<f32>());
+    auto* str = type::CreateFrexpResult(ty, mod.symbols, ty.vec2<f32>());
     auto* arg = b.FunctionParam("arg", ty.vec2<f32>());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
@@ -254,7 +254,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Frexp_Vec3h) {
-    auto* str = resolver::CreateFrexpResult(ty, mod.symbols, ty.vec3<f16>());
+    auto* str = type::CreateFrexpResult(ty, mod.symbols, ty.vec3<f16>());
     auto* arg = b.FunctionParam("arg", ty.vec3<f16>());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
@@ -283,7 +283,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Modf_F32) {
-    auto* str = resolver::CreateModfResult(ty, mod.symbols, ty.f32());
+    auto* str = type::CreateModfResult(ty, mod.symbols, ty.f32());
     auto* arg = b.FunctionParam("arg", ty.f32());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
@@ -298,7 +298,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Modf_F16) {
-    auto* str = resolver::CreateModfResult(ty, mod.symbols, ty.f16());
+    auto* str = type::CreateModfResult(ty, mod.symbols, ty.f16());
     auto* arg = b.FunctionParam("arg", ty.f16());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
@@ -313,7 +313,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Modf_Vec2f) {
-    auto* str = resolver::CreateModfResult(ty, mod.symbols, ty.vec2<f32>());
+    auto* str = type::CreateModfResult(ty, mod.symbols, ty.vec2<f32>());
     auto* arg = b.FunctionParam("arg", ty.vec2<f32>());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
@@ -328,7 +328,7 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Modf_Vec3h) {
-    auto* str = resolver::CreateModfResult(ty, mod.symbols, ty.vec3<f16>());
+    auto* str = type::CreateModfResult(ty, mod.symbols, ty.vec3<f16>());
     auto* arg = b.FunctionParam("arg", ty.vec3<f16>());
     auto* func = b.Function("foo", str);
     func->SetParams({arg});