[ir][fuzz] Enable fuzzing of buffers

* Update proto to support override-sized buffers
* Enable decoding of buffer types
* Enable the buffer_view feature in wgsl fuzzing
* Add a couple extra IR validation checks for buffer types
* Add a new property to restrict certain fuzzing

Fix: 474324630
Change-Id: I87d9b34963c3fd211af4cb41a2308cb67b72c1c8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/319355
Commit-Queue: James Price <jrprice@google.com>
Commit-Queue: Alan Baker <alanbaker@google.com>
Reviewed-by: James Price <jrprice@google.com>
Auto-Submit: Alan Baker <alanbaker@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.cc b/src/tint/cmd/fuzz/wgsl/fuzz.cc
index 0d76557..80d587c 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.cc
@@ -156,8 +156,6 @@
     // Parse the WGSL program.
     tint::wgsl::reader::Options parse_options;
     parse_options.allowed_features = tint::wgsl::AllowedFeatures::Everything();
-    // buffer_view is not ready for fuzzing yet.
-    parse_options.allowed_features.features.erase(tint::wgsl::LanguageFeature::kBufferView);
     auto program = tint::wgsl::reader::Parse(&file, parse_options);
     if (!program.IsValid()) {
         if (options.verbose) {
diff --git a/src/tint/lang/core/ir/binary/decode.cc b/src/tint/lang/core/ir/binary/decode.cc
index 2b85c9c..fdc2ac3 100644
--- a/src/tint/lang/core/ir/binary/decode.cc
+++ b/src/tint/lang/core/ir/binary/decode.cc
@@ -1149,9 +1149,31 @@
                                                 subgroup_matrix.rows());
     }
 
-    const core::type::Type* CreateTypeBuffer(const pb::TypeBuffer&) {
-        err_ << "buffer types are not supported\n";
-        return mod_out_.Types().invalid();
+    const core::type::Type* CreateTypeBuffer(const pb::TypeBuffer& buffer) {
+        mod_out_.properties.Add(core::ir::Property::kAllowBufferTypes);
+        auto size = buffer.size();
+        switch (buffer.size_kind()) {
+            case pb::Runtime:
+            case pb::Constant:
+                // Handled below to avoid duplicating the check for the size limit.
+                break;
+            case pb::Override: {
+                auto* value_size =
+                    mod_out_.Types().Get<core::ir::type::ValueArrayCount>(Value(size));
+                return mod_out_.Types().Get<core::type::Buffer>(value_size);
+            }
+            case pb::ArrayCountKind_INT_MIN_SENTINEL_DO_NOT_USE_:
+            case pb::ArrayCountKind_INT_MAX_SENTINEL_DO_NOT_USE_:
+                TINT_UNREACHABLE();
+        }
+
+        if (size >= internal_limits::kMaxArrayElementCount) {
+            err_ << "buffer size (" << size << ") must be less than "
+                 << internal_limits::kMaxArrayElementCount << "\n";
+            return mod_out_.Types().invalid();
+        }
+
+        return size > 0 ? mod_out_.Types().buffer(size) : mod_out_.Types().unsized_buffer();
     }
 
     const core::type::Type* CreateTypeBuiltinStruct(pb::TypeBuiltinStruct builtin_struct_in) {
diff --git a/src/tint/lang/core/ir/binary/encode.cc b/src/tint/lang/core/ir/binary/encode.cc
index 1304bef..9a1a377 100644
--- a/src/tint/lang/core/ir/binary/encode.cc
+++ b/src/tint/lang/core/ir/binary/encode.cc
@@ -624,13 +624,21 @@
         tint::Switch(
             buffer_in->Count(),  //
             [&](const core::type::ConstantArrayCount* c) {
-                buffer_out.set_count(c->value);
+                buffer_out.set_size(c->value);
+                buffer_out.set_size_kind(pb::ArrayCountKind::Constant);
                 if (c->value >= internal_limits::kMaxArrayElementCount) {
-                    err_ << "array count (" << c->value << ") must be less than "
+                    err_ << "buffer size (" << c->value << ") must be less than "
                          << internal_limits::kMaxArrayElementCount << "\n";
                 }
             },
-            [&](const core::type::RuntimeArrayCount*) { buffer_out.set_count(0); },
+            [&](const core::ir::type::ValueArrayCount* c) {
+                buffer_out.set_size(Value(c->value));
+                buffer_out.set_size_kind(pb::ArrayCountKind::Override);
+            },
+            [&](const core::type::RuntimeArrayCount*) {
+                buffer_out.set_size(0);
+                buffer_out.set_size_kind(pb::ArrayCountKind::Runtime);
+            },
             TINT_ICE_ON_NO_MATCH);
     }
 
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index c1375bf..d242f3d 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -92,6 +92,8 @@
     kAllowUnannotatedModuleIOVariables,
     /// Allows access instructions to create pointers to vector elements.
     kAllowVectorElementPointer,
+    /// Allows use of buffer types.
+    kAllowBufferTypes,
 
     /// Disallow use of the min/max/clamp builtins with vector types.
     kDisallowVectorMinMaxClamp,
@@ -129,6 +131,7 @@
         CASE(AllowStructMatrixDecorations);
         CASE(AllowUnannotatedModuleIOVariables);
         CASE(AllowVectorElementPointer);
+        CASE(AllowBufferTypes);
         CASE(DisallowVectorMinMaxClamp);
     }
 #undef CASE
diff --git a/src/tint/lang/core/ir/structural_validator.cc b/src/tint/lang/core/ir/structural_validator.cc
index 92bbd4a..779a913 100644
--- a/src/tint/lang/core/ir/structural_validator.cc
+++ b/src/tint/lang/core/ir/structural_validator.cc
@@ -692,6 +692,10 @@
                         diag() << "struct member " << member->Index() << " cannot have void type";
                         return false;
                     }
+                    if (member->Type()->Is<core::type::Buffer>()) {
+                        diag() << "struct member " << member->Index() << " cannot have buffer type";
+                        return false;
+                    }
 
                     if (!CheckStructMemberAttributes(member, diag)) {
                         return false;
@@ -1028,6 +1032,13 @@
                 }
                 return true;
             },
+            [&](const core::type::Buffer*) {
+                if (!ir_.properties.Contains(Property::kAllowBufferTypes)) {
+                    diag() << "buffer types are not allowed in this context";
+                    return false;
+                }
+                return true;
+            },
             [](Default) { return true; });
     };
 
diff --git a/src/tint/lang/core/ir/transform/array_length_from_immediate_test.cc b/src/tint/lang/core/ir/transform/array_length_from_immediate_test.cc
index 4612366..50bae8c 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_immediate_test.cc
+++ b/src/tint/lang/core/ir/transform/array_length_from_immediate_test.cc
@@ -38,7 +38,9 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
-using IR_ArrayLengthFromImmediatesTest = TransformTest;
+struct IR_ArrayLengthFromImmediatesTest : public TransformTest {
+    void SetUp() override { mod.properties.Add(Property::kAllowBufferTypes); }
+};
 
 uint32_t GetBufferSizesNumElements(
     const std::unordered_map<BindingPoint, uint32_t>& bindpoint_to_size_index) {
diff --git a/src/tint/lang/core/ir/transform/array_length_from_uniform_test.cc b/src/tint/lang/core/ir/transform/array_length_from_uniform_test.cc
index 1abb9aa..3b80650 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_uniform_test.cc
+++ b/src/tint/lang/core/ir/transform/array_length_from_uniform_test.cc
@@ -36,7 +36,9 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
-using IR_ArrayLengthFromUniformTest = TransformTest;
+struct IR_ArrayLengthFromUniformTest : public TransformTest {
+    void SetUp() override { mod.properties.Add(Property::kAllowBufferTypes); }
+};
 
 TEST_F(IR_ArrayLengthFromUniformTest, NoModify_UserFunction) {
     auto* arr = ty.array<i32>();
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc b/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
index 34a3ad5..48fa47b 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
@@ -39,5 +39,9 @@
 }  // namespace
 }  // namespace tint::core::ir::transform
 
+constexpr auto kUnsupportedProperties = tint::core::ir::Properties{
+    tint::core::ir::Property::kAllowBufferTypes,
+};
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::BlockDecoratedStructsFuzzer,
-                      tint::core::ir::transform::kBlockDecoratedStructsCapabilities);
+                      tint::core::ir::transform::kBlockDecoratedStructsCapabilities,
+                      kUnsupportedProperties);
diff --git a/src/tint/lang/core/ir/transform/decompose_access.cc b/src/tint/lang/core/ir/transform/decompose_access.cc
index 85ed753..c35fbbe 100644
--- a/src/tint/lang/core/ir/transform/decompose_access.cc
+++ b/src/tint/lang/core/ir/transform/decompose_access.cc
@@ -1555,6 +1555,8 @@
         return Failure{res.Failure().reason.Str()};
     }
 
+    ir.properties.Remove(core::ir::Property::kAllowBufferTypes);
+
     return Success;
 }
 
diff --git a/src/tint/lang/core/ir/transform/decompose_access_test.cc b/src/tint/lang/core/ir/transform/decompose_access_test.cc
index 0e09d70..0e1c714 100644
--- a/src/tint/lang/core/ir/transform/decompose_access_test.cc
+++ b/src/tint/lang/core/ir/transform/decompose_access_test.cc
@@ -40,7 +40,9 @@
 namespace tint::core::ir::transform {
 namespace {
 
-using IR_DecomposeAccessTest = core::ir::transform::TransformTest;
+struct IR_DecomposeAccessTest : public core::ir::transform::TransformTest {
+    void SetUp() override { mod.properties.Add(Property::kAllowBufferTypes); }
+};
 
 TEST_F(IR_DecomposeAccessTest, OverflowArraySize) {
     auto* S =
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
index db48364..54f4a3e 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
@@ -8252,7 +8252,9 @@
 ////////////////////////////////////////////////////////////////////////////////
 namespace buffer_view_test {
 
-using IR_DirectVariableAccessTest_BufferView = TransformTest;
+struct IR_DirectVariableAccessTest_BufferView : public TransformTest {
+    void SetUp() override { mod.properties.Add(Property::kAllowBufferTypes); }
+};
 
 TEST_F(IR_DirectVariableAccessTest_BufferView, Simple_BufferLength) {
     auto* v = b.Var("v", ty.ptr(storage, ty.unsized_buffer()));
diff --git a/src/tint/lang/core/ir/transform/propagate_buffer_sizes_test.cc b/src/tint/lang/core/ir/transform/propagate_buffer_sizes_test.cc
index f18ac32..b35bf24 100644
--- a/src/tint/lang/core/ir/transform/propagate_buffer_sizes_test.cc
+++ b/src/tint/lang/core/ir/transform/propagate_buffer_sizes_test.cc
@@ -35,7 +35,9 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
-using IR_PropagateBufferSizesTest = TransformTest;
+struct IR_PropagateBufferSizesTest : public TransformTest {
+    void SetUp() override { mod.properties.Add(Property::kAllowBufferTypes); }
+};
 
 TEST_F(IR_PropagateBufferSizesTest, BufferLength_GlobalVariable_Noop) {
     auto* gv = b.Var("gv", ty.ptr(storage, ty.unsized_buffer(), read_write));
diff --git a/src/tint/lang/core/ir/transform/robustness_test.cc b/src/tint/lang/core/ir/transform/robustness_test.cc
index aaeeaca..f06f579 100644
--- a/src/tint/lang/core/ir/transform/robustness_test.cc
+++ b/src/tint/lang/core/ir/transform/robustness_test.cc
@@ -5729,6 +5729,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_RootVar) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* b1 = b.Var("b1", ty.ptr(storage, ty.unsized_buffer()));
     b1->SetBindingPoint(0, 0);
     mod.root_block->Append(b1);
@@ -5807,6 +5808,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_u32_ConstOffsetAndLength_InRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* func = b.Function("foo", ty.void_());
     auto* p = b.FunctionParam("p", ty.ptr(storage, ty.unsized_buffer()));
     func->SetParams({p});
@@ -5835,6 +5837,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_u32_ConstOffsetAndLength_OutOfRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* func = b.Function("foo", ty.void_());
     auto* p = b.FunctionParam("p", ty.ptr(storage, ty.unsized_buffer()));
     func->SetParams({p});
@@ -5872,6 +5875,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_RuntimeStruct_ConstOffsetAndLength_InRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* s = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.vec4(ty.u32())},
@@ -5910,6 +5914,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_RuntimeStruct_ConstOffsetAndLength_OutOfRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* s = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.vec4(ty.u32())},
@@ -5962,6 +5967,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_RuntimeStruct_NonConst) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* s = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.vec4(ty.u32())},
@@ -6019,6 +6025,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_RuntimeStruct_ConstOffset) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* s = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.vec4(ty.u32())},
@@ -6074,6 +6081,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferView_RuntimeStruct_ConstLength) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* s = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.vec4(ty.u32())},
@@ -6131,6 +6139,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferArrayView_ArrayU32_ConstOffsetAndSizeAndLength_InRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* func = b.Function("foo", ty.void_());
     auto* p = b.FunctionParam("p", ty.ptr(storage, ty.unsized_buffer()));
     func->SetParams({p});
@@ -6160,6 +6169,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferArrayView_ArrayU32_ConstOffsetAndSizeAndLength_OutOfRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* func = b.Function("foo", ty.void_());
     auto* p = b.FunctionParam("p", ty.ptr(storage, ty.unsized_buffer()));
     func->SetParams({p});
@@ -6198,6 +6208,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferArrayView_RuntimeStruct_ConstOffsetAndSizeAndLength_InRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* s = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.array(ty.u32(), 5)},
@@ -6236,6 +6247,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferArrayView_RuntimeStruct_ConstOffsetAndSizeAndLength_OutOfRange) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* s = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.array(ty.u32(), 5)},
@@ -6288,6 +6300,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferArrayView_RuntimeStruct_NonConst) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* S = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.array(ty.u32(), 5)},
@@ -6350,6 +6363,7 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferArrayView_RuntimeStruct_ConstOffset) {
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* S = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.array(ty.u32(), 5)},
@@ -6410,6 +6424,8 @@
 }
 
 TEST_P(IR_RobustnessTest, BufferArrayView_RuntimeStruct_ConstSize) {
+    mod.properties.Add(Property::kAllowBufferTypes);
+    mod.properties.Add(Property::kAllowBufferTypes);
     auto* S = ty.Struct(mod.symbols.Register("S"),
                         {
                             {mod.symbols.Register("a"), ty.array(ty.u32(), 5)},
diff --git a/src/tint/lang/core/ir/transform/single_entry_point_test.cc b/src/tint/lang/core/ir/transform/single_entry_point_test.cc
index fa93976..4f25c23 100644
--- a/src/tint/lang/core/ir/transform/single_entry_point_test.cc
+++ b/src/tint/lang/core/ir/transform/single_entry_point_test.cc
@@ -43,7 +43,8 @@
   protected:
     void SetUp() override {
         TransformTest::SetUp();
-        mod.properties.Add(Property::kAllowMultipleEntryPoints, Property::kAllowOverrides);
+        mod.properties.Add(Property::kAllowMultipleEntryPoints, Property::kAllowOverrides,
+                           Property::kAllowBufferTypes);
     }
 
     /// @returns a new entry point called @p name that references @p refs
diff --git a/src/tint/lang/core/ir/transform/substitute_overrides_test.cc b/src/tint/lang/core/ir/transform/substitute_overrides_test.cc
index a8b7df7..33a3231 100644
--- a/src/tint/lang/core/ir/transform/substitute_overrides_test.cc
+++ b/src/tint/lang/core/ir/transform/substitute_overrides_test.cc
@@ -48,7 +48,8 @@
   protected:
     void SetUp() override {
         TransformTest::SetUp();
-        mod.properties.Add(core::ir::Property::kAllowOverrides);
+        mod.properties.Add(core::ir::Property::kAllowOverrides,
+                           core::ir::Property::kAllowBufferTypes);
     }
 };
 
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
index 0a5803b..0e4b0f9 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
@@ -38,6 +38,9 @@
 using namespace tint::core::number_suffixes;  // NOLINT
 
 class IR_ZeroInitWorkgroupMemoryTest : public TransformTest {
+  public:
+    void SetUp() override { mod.properties.Add(Property::kAllowBufferTypes); }
+
   protected:
     Function* MakeEntryPoint(const char* name,
                              uint32_t wgsize_x,
diff --git a/src/tint/lang/core/ir/validator_type_test.cc b/src/tint/lang/core/ir/validator_type_test.cc
index 1a20877..9dbbf4c 100644
--- a/src/tint/lang/core/ir/validator_type_test.cc
+++ b/src/tint/lang/core/ir/validator_type_test.cc
@@ -267,6 +267,23 @@
 )")) << res.Failure();
 }
 
+TEST_F(IR_ValidatorTest, StructMember_Buffer) {
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"), {
+                                                   {mod.symbols.New("v"), ty.buffer(16), {}},
+                                               });
+    auto* v = b.Var(ty.ptr(private_, str_ty));
+    mod.root_block->Append(v);
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason,
+                testing::HasSubstr(R"(:6:3 error: var: struct member 0 cannot have buffer type
+  %1:ptr<private, MyStruct, read_write> = var undef
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+)")) << res.Failure();
+}
+
 TEST_F(IR_ValidatorTest, StructMember_AlignZero) {
     core::IOAttributes attrs = {};
     tint::Vector<const core::type::StructMember*, 4> members;
@@ -749,12 +766,40 @@
         << res.Failure();
 }
 
+TEST_F(IR_ValidatorTest, BufferDisallowed) {
+    auto* v = b.Var("v", ty.ptr(storage, ty.unsized_buffer()));
+    v->SetBindingPoint(0, 0);
+    mod.root_block->Append(v);
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason,
+                testing::HasSubstr("buffer types are not allowed in this context"));
+}
+
 using TypeTest = IRTestParamHelper<std::tuple<
     /* allowed */ bool,
     /* type_builder */ TypeBuilderFn>>;
 
 using Type_ArrayElements = TypeTest;
 
+TEST_F(Type_ArrayElements, Buffer) {
+    auto* buffer = ty.buffer(16);
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Var("v", AddressSpace::kFunction, ty.array(buffer, 4));
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success) << res.Failure();
+    EXPECT_THAT(res.Failure().reason,
+                testing::HasSubstr(R"(:3:5 error: var: array elements, ')" +
+                                   ty.array(buffer, 4)->FriendlyName() +
+                                   R"(', must have creation-fixed footprint)"))
+        << res.Failure();
+}
+
 TEST_P(Type_ArrayElements, Test) {
     bool allowed = std::get<0>(GetParam());
     auto* type = std::get<1>(GetParam())(ty);
diff --git a/src/tint/lang/glsl/writer/writer_test.cc b/src/tint/lang/glsl/writer/writer_test.cc
index 3ba99f8..5e0de9a 100644
--- a/src/tint/lang/glsl/writer/writer_test.cc
+++ b/src/tint/lang/glsl/writer/writer_test.cc
@@ -305,6 +305,7 @@
 }
 
 TEST_F(GlslWriterTest, U16_Via_BufferView) {
+    mod.properties.Add(core::ir::Property::kAllowBufferTypes);
     auto* v = b.Var(ty.ptr(storage, ty.unsized_buffer()));
     v->SetBindingPoint(0, 0);
     mod.root_block->Append(v);
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
index daa014d..0c411d5 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
@@ -44,7 +44,9 @@
 namespace tint::hlsl::writer::raise {
 namespace {
 
-using HlslWriterDecomposeStorageAccessTest = core::ir::transform::TransformTest;
+struct HlslWriterDecomposeStorageAccessTest : public core::ir::transform::TransformTest {
+    void SetUp() override { mod.properties.Add(core::ir::Property::kAllowBufferTypes); }
+};
 
 TEST_F(HlslWriterDecomposeStorageAccessTest, NoBufferAccess) {
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
diff --git a/src/tint/lang/msl/writer/raise/decompose_buffer.cc b/src/tint/lang/msl/writer/raise/decompose_buffer.cc
index 25d18a2..6cdda51 100644
--- a/src/tint/lang/msl/writer/raise/decompose_buffer.cc
+++ b/src/tint/lang/msl/writer/raise/decompose_buffer.cc
@@ -232,6 +232,7 @@
     State{ir}.Process();
 
     ir.properties.Add(core::ir::Property::kAllow8BitIntegers);
+    ir.properties.Remove(core::ir::Property::kAllowBufferTypes);
 
     return Success;
 }
diff --git a/src/tint/lang/msl/writer/raise/decompose_buffer_test.cc b/src/tint/lang/msl/writer/raise/decompose_buffer_test.cc
index e7fe50d..04f85d4 100644
--- a/src/tint/lang/msl/writer/raise/decompose_buffer_test.cc
+++ b/src/tint/lang/msl/writer/raise/decompose_buffer_test.cc
@@ -38,7 +38,10 @@
 
 class MslWriter_DecomposeBufferTest : public core::ir::transform::TransformTest {
   protected:
-    void SetUp() override { mod.properties.Add(core::ir::Property::kAllowMslEntryPointInterface); }
+    void SetUp() override {
+        mod.properties.Add(core::ir::Property::kAllowMslEntryPointInterface,
+                           core::ir::Property::kAllowBufferTypes);
+    }
 };
 
 TEST_F(MslWriter_DecomposeBufferTest, BufferView_u32_ZeroOffset) {
diff --git a/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc b/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc
index 0c9c3cc..31a2028 100644
--- a/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc
+++ b/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc
@@ -40,6 +40,9 @@
 namespace {
 
 class MslWriter_FixTypeLayoutTest : public core::ir::transform::TransformTest {
+  public:
+    void SetUp() override { mod.properties.Add(core::ir::Property::kAllowBufferTypes); }
+
   protected:
     FixTypeLayoutOptions options;
     void Run() { core::ir::transform::TransformTest::Run(FixTypeLayout, options); }
diff --git a/src/tint/lang/msl/writer/writer_test.cc b/src/tint/lang/msl/writer/writer_test.cc
index a64c196..a152bdf 100644
--- a/src/tint/lang/msl/writer/writer_test.cc
+++ b/src/tint/lang/msl/writer/writer_test.cc
@@ -573,6 +573,7 @@
 }
 
 TEST_F(MslWriterTest, BufferView_Workgroup) {
+    mod.properties.Add(core::ir::Property::kAllowBufferTypes);
     auto* v = b.Var("v", ty.ptr(workgroup, ty.buffer(32)));
     mod.root_block->Append(v);
 
@@ -638,6 +639,7 @@
 }
 
 TEST_F(MslWriterTest, BufferView_HostStruct_SubFunction) {
+    mod.properties.Add(core::ir::Property::kAllowBufferTypes);
     Vector<const core::type::StructMember*, 8> members{
         ty.Get<core::type::StructMember>(mod.symbols.New("a"), ty.u32(), 0u, 0u, 4u, 4u,
                                          core::IOAttributes{}),
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index eca4ba8..4ccd73e 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -369,6 +369,10 @@
 
             scopes_.Set(p->name->symbol, param);
             params.Push(param);
+
+            if (ty->UnwrapPtr()->Is<core::type::Buffer>()) {
+                mod.properties.Add(core::ir::Property::kAllowBufferTypes);
+            }
         }
         ir_func->SetParams(params);
 
@@ -1402,6 +1406,10 @@
                 // Record the original name and source of the var
                 builder_.ir.SetName(val, v->name->symbol.Name());
                 builder_.ir.SetSource(val, v->source);
+
+                if (store_ty->Is<core::type::Buffer>()) {
+                    mod.properties.Add(core::ir::Property::kAllowBufferTypes);
+                }
             },
             [&](const ast::Let* l) {
                 auto init = EmitValueExpression(l->initializer);
diff --git a/src/tint/utils/protos/ir/ir.proto b/src/tint/utils/protos/ir/ir.proto
index 2251c0e..0ab55ee 100644
--- a/src/tint/utils/protos/ir/ir.proto
+++ b/src/tint/utils/protos/ir/ir.proto
@@ -183,7 +183,10 @@
 }
 
 message TypeBuffer {
-    uint32 count = 1;
+    reserved 1;
+    reserved "count";
+    uint32 size = 2;
+    ArrayCountKind size_kind = 3;
 }
 
 enum TypeBuiltinStruct {