[hlsl] Emit module scoped variables in IR backend.

This CL adds support for module scoped variables in the HLSL IR backend.

Bug: 42251045
Change-Id: I2367054c482b4f99e1bcd7f30a2a62cb6bc0fdf1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/194480
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/hlsl/writer/printer/printer.cc b/src/tint/lang/hlsl/writer/printer/printer.cc
index fd3411d..1373d9d 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.cc
+++ b/src/tint/lang/hlsl/writer/printer/printer.cc
@@ -116,6 +116,28 @@
 namespace tint::hlsl::writer {
 namespace {
 
+// Helper for writing " : register(RX, spaceY)", where R is the register, X is
+// the binding point binding value, and Y is the binding point group value.
+struct RegisterAndSpace {
+    RegisterAndSpace(char r, BindingPoint bp) : reg(r), binding_point(bp) {}
+
+    const char reg;
+    BindingPoint const binding_point;
+};
+
+StringStream& operator<<(StringStream& s, const RegisterAndSpace& rs) {
+    s << " : register(" << rs.reg << rs.binding_point.binding;
+    // Omit the space if it's 0, as it's the default.
+    // SM 5.0 doesn't support spaces, so we don't emit them if group is 0 for better
+    // compatibility.
+    if (rs.binding_point.group == 0) {
+        s << ")";
+    } else {
+        s << ", space" << rs.binding_point.group << ")";
+    }
+    return s;
+}
+
 /// PIMPL class for the HLSL generator
 class Printer : public tint::TextGenerator {
   public:
@@ -130,7 +152,8 @@
             return std::move(valid.Failure());
         }
 
-        // TOOD(dsinclair): EmitRootBlock
+        // Emit module-scope declarations.
+        EmitRootBlock(ir_.root_block);
 
         // Emit functions.
         for (auto* func : ir_.DependencyOrderedFunctions()) {
@@ -168,6 +191,17 @@
     /// Block to emit for a continuing
     std::function<void()> emit_continuing_;
 
+    /// Emit the root block.
+    /// @param root_block the root block to emit
+    void EmitRootBlock(core::ir::Block* root_block) {
+        for (auto* inst : *root_block) {
+            Switch(
+                inst,                                                //
+                [&](core::ir::Var* v) { return EmitGlobalVar(v); },  //
+                TINT_ICE_ON_NO_MATCH);
+        }
+    }
+
     void EmitFunction(const core::ir::Function* func) {
         TINT_SCOPED_ASSIGNMENT(current_function_, func);
 
@@ -400,6 +434,101 @@
         }
     }
 
+    void EmitGlobalVar(const core::ir::Var* var) {
+        auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
+        TINT_ASSERT(ptr);
+
+        auto space = ptr->AddressSpace();
+
+        switch (space) {
+            case core::AddressSpace::kUniform:
+                EmitUniformVariable(var);
+                break;
+            case core::AddressSpace::kStorage:
+                EmitStorageVariable(var);
+                break;
+            case core::AddressSpace::kHandle:
+                EmitHandleVariable(var);
+                break;
+            case core::AddressSpace::kPrivate: {
+                Line() << "static";
+                EmitVar(var);
+                break;
+            }
+            case core::AddressSpace::kWorkgroup:
+                Line() << "groupshared";
+                EmitVar(var);
+                break;
+            case core::AddressSpace::kPushConstant:
+            default: {
+                TINT_ICE() << "unhandled address space " << space;
+            }
+        }
+    }
+
+    void EmitUniformVariable(const core::ir::Var* var) {
+        auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
+        TINT_ASSERT(ptr);
+
+        auto bp = var->BindingPoint();
+        TINT_ASSERT(bp.has_value());
+
+        Line() << "cbuffer cbuffer_" << NameOf(var->Result(0)) << RegisterAndSpace('b', bp.value())
+               << " {";
+        {
+            const ScopedIndent si(this);
+
+            auto out = Line();
+            EmitTypeAndName(out, ptr->StoreType(), core::AddressSpace::kUniform, ptr->Access(),
+                            NameOf(var->Result(0)));
+
+            out << ";";
+        }
+        Line() << "};";
+    }
+
+    void EmitStorageVariable(const core::ir::Var* var) {
+        auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
+        TINT_ASSERT(ptr);
+
+        auto out = Line();
+        EmitTypeAndName(out, var->Result(0)->Type(), core::AddressSpace::kStorage, ptr->Access(),
+                        NameOf(var->Result(0)));
+
+        auto bp = var->BindingPoint();
+        TINT_ASSERT(bp.has_value());
+
+        out << RegisterAndSpace(ptr->Access() == core::Access::kRead ? 't' : 'u', bp.value())
+            << ";";
+    }
+
+    void EmitHandleVariable(const core::ir::Var* var) {
+        auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
+        TINT_ASSERT(ptr);
+
+        char register_space = ' ';
+        if (ptr->StoreType()->Is<core::type::Texture>()) {
+            register_space = 't';
+
+            auto* st = ptr->StoreType()->As<core::type::StorageTexture>();
+            if (st && st->access() != core::Access::kRead) {
+                register_space = 'u';
+            }
+        } else if (ptr->StoreType()->Is<core::type::Sampler>()) {
+            register_space = 's';
+        }
+        TINT_ASSERT(register_space != ' ');
+
+        auto bp = var->BindingPoint();
+        TINT_ASSERT(bp.has_value());
+
+        // TODO(dsinclair): Handle PixelLocal::RasterizerOrderedView attribute
+        auto out = Line();
+        EmitTypeAndName(out, var->Result(0)->Type(), ptr->AddressSpace(), ptr->Access(),
+                        NameOf(var->Result(0)));
+        out << RegisterAndSpace(register_space, bp.value()) << ";";
+    }
+
     void EmitVar(const core::ir::Var* var) {
         auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
         TINT_ASSERT(ptr);
@@ -408,13 +537,14 @@
 
         auto out = Line();
         EmitTypeAndName(out, var->Result(0)->Type(), space, ptr->Access(), NameOf(var->Result(0)));
-        out << " = ";
 
         if (var->Initializer()) {
+            out << " = ";
             EmitValue(out, var->Initializer());
         } else if (space == core::AddressSpace::kPrivate ||
                    space == core::AddressSpace::kFunction ||
                    space == core::AddressSpace::kUndefined) {
+            out << " = ";
             EmitZeroValue(out, ptr->UnwrapPtr());
         }
         out << ";";
diff --git a/src/tint/lang/hlsl/writer/var_let_test.cc b/src/tint/lang/hlsl/writer/var_let_test.cc
index 8677eb1..7c1109c 100644
--- a/src/tint/lang/hlsl/writer/var_let_test.cc
+++ b/src/tint/lang/hlsl/writer/var_let_test.cc
@@ -26,7 +26,14 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "src/tint/lang/core/ir/var.h"
+#include "src/tint/lang/core/type/depth_multisampled_texture.h"
+#include "src/tint/lang/core/type/depth_texture.h"
 #include "src/tint/lang/core/type/f32.h"
+#include "src/tint/lang/core/type/multisampled_texture.h"
+#include "src/tint/lang/core/type/sampled_texture.h"
+#include "src/tint/lang/core/type/sampler.h"
+#include "src/tint/lang/core/type/storage_texture.h"
+#include "src/tint/lang/core/type/texture_dimension.h"
 #include "src/tint/lang/core/type/type.h"
 #include "src/tint/lang/hlsl/writer/helper_test.h"
 
@@ -90,5 +97,447 @@
 )");
 }
 
+TEST_F(HlslWriterTest, VarSampler) {
+    auto* s = b.Var("s", ty.ptr<handle>(ty.sampler()));
+    s->SetBindingPoint(1, 0);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+SamplerState s : register(s0, space1);
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, VarSamplerComparison) {
+    auto* s = b.Var("s", ty.ptr<handle>(ty.comparison_sampler()));
+    s->SetBindingPoint(0, 0);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+SamplerComparisonState s : register(s0);
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+struct HlslDepthTextureData {
+    core::type::TextureDimension dim;
+    std::string result;
+};
+inline std::ostream& operator<<(std::ostream& out, HlslDepthTextureData data) {
+    StringStream str;
+    str << data.dim;
+    out << str.str();
+    return out;
+}
+
+using VarDepthTextureTest = HlslWriterTestWithParam<HlslDepthTextureData>;
+TEST_P(VarDepthTextureTest, Emit) {
+    auto params = GetParam();
+
+    auto* s = b.Var("tex", ty.ptr<handle>(ty.Get<core::type::DepthTexture>(params.dim)));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, "\n" + params.result + R"(
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslWriterTest,
+    VarDepthTextureTest,
+    testing::Values(HlslDepthTextureData{core::type::TextureDimension::k2d,
+                                         "Texture2D tex : register(t1, space2);"},
+                    HlslDepthTextureData{core::type::TextureDimension::k2dArray,
+                                         "Texture2DArray tex : register(t1, space2);"},
+                    HlslDepthTextureData{core::type::TextureDimension::kCube,
+                                         "TextureCube tex : register(t1, space2);"},
+                    HlslDepthTextureData{core::type::TextureDimension::kCubeArray,
+                                         "TextureCubeArray tex : register(t1, space2);"}));
+
+TEST_F(HlslWriterTest, VarDepthMultiSampled) {
+    auto* s = b.Var("tex", ty.ptr<handle>(ty.Get<core::type::DepthMultisampledTexture>(
+                               core::type::TextureDimension::k2d)));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+Texture2DMS<float4> tex : register(t1, space2);
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+enum class TextureDataType : uint8_t { F32, U32, I32 };
+struct HlslSampledTextureData {
+    core::type::TextureDimension dim;
+    TextureDataType datatype;
+    std::string result;
+};
+
+inline std::ostream& operator<<(std::ostream& out, HlslSampledTextureData data) {
+    StringStream str;
+    str << data.dim;
+    out << str.str();
+    return out;
+}
+
+using VarSampledTextureTest = HlslWriterTestWithParam<HlslSampledTextureData>;
+TEST_P(VarSampledTextureTest, Emit) {
+    auto params = GetParam();
+
+    const core::type::Type* datatype;
+    switch (params.datatype) {
+        case TextureDataType::F32:
+            datatype = ty.f32();
+            break;
+        case TextureDataType::U32:
+            datatype = ty.u32();
+            break;
+        case TextureDataType::I32:
+            datatype = ty.i32();
+            break;
+    }
+
+    auto* s =
+        b.Var("tex", ty.ptr<handle>(ty.Get<core::type::SampledTexture>(params.dim, datatype)));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, "\n" + params.result + R"(
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+INSTANTIATE_TEST_SUITE_P(HlslWriterTest,
+                         VarSampledTextureTest,
+                         testing::Values(
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k1d,
+                                 TextureDataType::F32,
+                                 "Texture1D<float4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k2d,
+                                 TextureDataType::F32,
+                                 "Texture2D<float4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k2dArray,
+                                 TextureDataType::F32,
+                                 "Texture2DArray<float4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k3d,
+                                 TextureDataType::F32,
+                                 "Texture3D<float4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::kCube,
+                                 TextureDataType::F32,
+                                 "TextureCube<float4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::kCubeArray,
+                                 TextureDataType::F32,
+                                 "TextureCubeArray<float4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k1d,
+                                 TextureDataType::U32,
+                                 "Texture1D<uint4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k2d,
+                                 TextureDataType::U32,
+                                 "Texture2D<uint4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k2dArray,
+                                 TextureDataType::U32,
+                                 "Texture2DArray<uint4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k3d,
+                                 TextureDataType::U32,
+                                 "Texture3D<uint4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::kCube,
+                                 TextureDataType::U32,
+                                 "TextureCube<uint4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::kCubeArray,
+                                 TextureDataType::U32,
+                                 "TextureCubeArray<uint4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k1d,
+                                 TextureDataType::I32,
+                                 "Texture1D<int4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k2d,
+                                 TextureDataType::I32,
+                                 "Texture2D<int4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k2dArray,
+                                 TextureDataType::I32,
+                                 "Texture2DArray<int4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::k3d,
+                                 TextureDataType::I32,
+                                 "Texture3D<int4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::kCube,
+                                 TextureDataType::I32,
+                                 "TextureCube<int4> tex : register(t1, space2);",
+                             },
+                             HlslSampledTextureData{
+                                 core::type::TextureDimension::kCubeArray,
+                                 TextureDataType::I32,
+                                 "TextureCubeArray<int4> tex : register(t1, space2);",
+                             }));
+
+TEST_F(HlslWriterTest, VarMultisampledTexture) {
+    auto* s = b.Var("tex", ty.ptr<handle>(ty.Get<core::type::MultisampledTexture>(
+                               core::type::TextureDimension::k2d, ty.f32())));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+Texture2DMS<float4> tex : register(t1, space2);
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+struct HlslStorageTextureData {
+    core::type::TextureDimension dim;
+    core::TexelFormat imgfmt;
+    core::Access access;
+    std::string result;
+};
+
+inline std::ostream& operator<<(std::ostream& out, HlslStorageTextureData data) {
+    StringStream str;
+    str << data.dim;
+    out << str.str();
+    return out;
+}
+
+using VarStorageTextureTest = HlslWriterTestWithParam<HlslStorageTextureData>;
+TEST_P(VarStorageTextureTest, Emit) {
+    auto params = GetParam();
+
+    auto* s = b.Var("tex", ty.ptr<handle>(ty.Get<core::type::StorageTexture>(
+                               params.dim, params.imgfmt, params.access, ty.f32())));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, "\n" + params.result + R"(
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    HlslWriterTest,
+    VarStorageTextureTest,
+    testing::Values(
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba8Unorm,
+                               core::Access::kWrite,
+                               "RWTexture1D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2d, core::TexelFormat::kRgba16Float,
+                               core::Access::kWrite,
+                               "RWTexture2D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2dArray, core::TexelFormat::kR32Float,
+                               core::Access::kWrite,
+                               "RWTexture2DArray<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k3d, core::TexelFormat::kRg32Float,
+                               core::Access::kWrite,
+                               "RWTexture3D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba32Float,
+                               core::Access::kWrite,
+                               "RWTexture1D<float4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2d, core::TexelFormat::kRgba16Uint,
+                               core::Access::kWrite,
+                               "RWTexture2D<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2dArray, core::TexelFormat::kR32Uint,
+                               core::Access::kWrite,
+                               "RWTexture2DArray<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k3d, core::TexelFormat::kRg32Uint,
+                               core::Access::kWrite,
+                               "RWTexture3D<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba32Uint,
+                               core::Access::kWrite,
+                               "RWTexture1D<uint4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2d, core::TexelFormat::kRgba16Sint,
+                               core::Access::kWrite,
+                               "RWTexture2D<int4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2dArray, core::TexelFormat::kR32Sint,
+                               core::Access::kWrite,
+                               "RWTexture2DArray<int4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k3d, core::TexelFormat::kRg32Sint,
+                               core::Access::kWrite,
+                               "RWTexture3D<int4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba32Sint,
+                               core::Access::kWrite,
+                               "RWTexture1D<int4> tex : register(u1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba8Unorm,
+                               core::Access::kRead,
+                               "Texture1D<float4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2d, core::TexelFormat::kRgba16Float,
+                               core::Access::kRead,
+                               "Texture2D<float4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2dArray, core::TexelFormat::kR32Float,
+                               core::Access::kRead,
+                               "Texture2DArray<float4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k3d, core::TexelFormat::kRg32Float,
+                               core::Access::kRead,
+                               "Texture3D<float4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba32Float,
+                               core::Access::kRead,
+                               "Texture1D<float4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2d, core::TexelFormat::kRgba16Uint,
+                               core::Access::kRead, "Texture2D<uint4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2dArray, core::TexelFormat::kR32Uint,
+                               core::Access::kRead,
+                               "Texture2DArray<uint4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k3d, core::TexelFormat::kRg32Uint,
+                               core::Access::kRead, "Texture3D<uint4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba32Uint,
+                               core::Access::kRead, "Texture1D<uint4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2d, core::TexelFormat::kRgba16Sint,
+                               core::Access::kRead, "Texture2D<int4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k2dArray, core::TexelFormat::kR32Sint,
+                               core::Access::kRead,
+                               "Texture2DArray<int4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k3d, core::TexelFormat::kRg32Sint,
+                               core::Access::kRead, "Texture3D<int4> tex : register(t1, space2);"},
+        HlslStorageTextureData{core::type::TextureDimension::k1d, core::TexelFormat::kRgba32Sint,
+                               core::Access::kRead, "Texture1D<int4> tex : register(t1, space2);"}
+
+        ));
+
+TEST_F(HlslWriterTest, VarUniform) {
+    auto* s = b.Var("u", ty.ptr<uniform>(ty.vec4<f32>()));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+cbuffer cbuffer_u : register(b1, space2) {
+  uint4 u[1];
+};
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, VarStorageRead) {
+    auto* s = b.Var("u", ty.ptr<storage, core::Access::kRead>(ty.vec4<f32>()));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+ByteAddressBuffer u : register(t1, space2);
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, VarStorageReadWrite) {
+    auto* s = b.Var("u", ty.ptr<storage, core::Access::kReadWrite>(ty.vec4<f32>()));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer u : register(u1, space2);
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, VarPrivate) {
+    auto* s = b.Var("u", ty.ptr<private_>(ty.vec4<f32>()));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+static
+float4 u = (0.0f).xxxx;
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, VarWorkgroup) {
+    auto* s = b.Var("u", ty.ptr<workgroup>(ty.vec4<f32>()));
+    s->SetBindingPoint(2, 1);
+
+    b.ir.root_block->Append(s);
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+groupshared
+float4 u;
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
 }  // namespace
 }  // namespace tint::hlsl::writer