[hlsl-writer] Emit uniform variables.

This CL adds emission of uniform storage class variables to the HLSL
backend. If the variable is a base type (float, int, etc) it is emitted
as a `cbuffer`. If the variable is a struct it will emit as a
`ConstantBuffer`.

Bug: tint:7
Change-Id: I9932d30df24c023c58d3a2ba4167bcb7ccb85dae
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/26920
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 750c52d..a5a50e9 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -787,6 +787,50 @@
     }
   }
 
+  bool emitted_uniform = false;
+  for (auto data : func->referenced_uniform_variables()) {
+    auto* var = data.first;
+    // TODO(dsinclair): We're using the binding to make up the buffer number but
+    // we should instead be using a provided mapping that uses both buffer and
+    // set. https://bugs.chromium.org/p/tint/issues/detail?id=104
+    auto* binding = data.second.binding;
+    if (binding == nullptr) {
+      error_ = "unable to find binding information for uniform: " + var->name();
+      return false;
+    }
+    // auto* set = data.second.set;
+
+    auto* type = var->type()->UnwrapAliasesIfNeeded();
+    if (type->IsStruct()) {
+      auto* strct = type->AsStruct();
+
+      out_ << "ConstantBuffer<" << strct->name() << "> " << var->name()
+           << " : register(b" << binding->value() << ");" << std::endl;
+    } else {
+      // TODO(dsinclair): There is outstanding spec work to require all uniform
+      // buffers to be [[block]] decorated, which means structs. This is
+      // currently not the case, so this code handles the cases where the data
+      // is not a block.
+      // Relevant: https://github.com/gpuweb/gpuweb/issues/1004
+      //           https://github.com/gpuweb/gpuweb/issues/1008
+      out_ << "cbuffer : register(b" << binding->value() << ") {" << std::endl;
+
+      increment_indent();
+      make_indent();
+      if (!EmitType(type, "")) {
+        return false;
+      }
+      out_ << " " << var->name() << ";" << std::endl;
+      decrement_indent();
+      out_ << "};" << std::endl;
+    }
+
+    emitted_uniform = true;
+  }
+  if (emitted_uniform) {
+    out_ << std::endl;
+  }
+
   auto ep_name = ep->name();
   if (ep_name.empty()) {
     ep_name = ep->function_name();
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index deeede9..b5c23a7 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -29,9 +29,12 @@
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/set_decoration.h"
 #include "src/ast/sint_literal.h"
+#include "src/ast/struct.h"
+#include "src/ast/type/alias_type.h"
 #include "src/ast/type/array_type.h"
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type/i32_type.h"
+#include "src/ast/type/struct_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type/void_type.h"
 #include "src/ast/variable.h"
@@ -280,7 +283,7 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest, DISABLED_Emit_Function_EntryPoint_With_Uniform) {
+TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_With_Uniform) {
   ast::type::VoidType void_type;
   ast::type::F32Type f32;
   ast::type::VectorType vec4(&f32, 4);
@@ -326,7 +329,91 @@
 
   GeneratorImpl g(&mod);
   ASSERT_TRUE(g.Generate()) << g.error();
-  EXPECT_EQ(g.result(), R"( ... )");
+  EXPECT_EQ(g.result(), R"(cbuffer : register(b0) {
+  vector<float, 4> coord;
+};
+
+void frag_main() {
+  float v = coord.x;
+  return;
+}
+
+)");
+}
+
+TEST_F(HlslGeneratorImplTest, Emit_Function_EntryPoint_With_UniformStruct) {
+  ast::type::VoidType void_type;
+  ast::type::F32Type f32;
+  ast::type::VectorType vec4(&f32, 4);
+
+  ast::StructMemberList members;
+  members.push_back(std::make_unique<ast::StructMember>(
+      "coord", &vec4, ast::StructMemberDecorationList{}));
+
+  auto str = std::make_unique<ast::Struct>();
+  str->set_members(std::move(members));
+
+  ast::type::StructType s(std::move(str));
+  s.set_name("Uniforms");
+  auto alias = std::make_unique<ast::type::AliasType>("Uniforms", &s);
+
+  Context ctx;
+  ast::Module mod;
+  TypeDeterminer td(&ctx, &mod);
+
+  auto coord_var =
+      std::make_unique<ast::DecoratedVariable>(std::make_unique<ast::Variable>(
+          "uniforms", ast::StorageClass::kUniform, alias.get()));
+
+  mod.AddAliasType(alias.get());
+
+  ast::VariableDecorationList decos;
+  decos.push_back(std::make_unique<ast::BindingDecoration>(0));
+  decos.push_back(std::make_unique<ast::SetDecoration>(1));
+  coord_var->set_decorations(std::move(decos));
+
+  td.RegisterVariableForTesting(coord_var.get());
+  mod.AddGlobalVariable(std::move(coord_var));
+
+  ast::VariableList params;
+  auto func = std::make_unique<ast::Function>("frag_main", std::move(params),
+                                              &void_type);
+
+  auto var =
+      std::make_unique<ast::Variable>("v", ast::StorageClass::kFunction, &f32);
+  var->set_constructor(std::make_unique<ast::MemberAccessorExpression>(
+      std::make_unique<ast::MemberAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("uniforms"),
+          std::make_unique<ast::IdentifierExpression>("coord")),
+      std::make_unique<ast::IdentifierExpression>("x")));
+
+  auto body = std::make_unique<ast::BlockStatement>();
+  body->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
+  body->append(std::make_unique<ast::ReturnStatement>());
+  func->set_body(std::move(body));
+
+  mod.AddFunction(std::move(func));
+
+  auto ep = std::make_unique<ast::EntryPoint>(ast::PipelineStage::kFragment, "",
+                                              "frag_main");
+  mod.AddEntryPoint(std::move(ep));
+
+  ASSERT_TRUE(td.Determine()) << td.error();
+
+  GeneratorImpl g(&mod);
+  ASSERT_TRUE(g.Generate()) << g.error();
+  EXPECT_EQ(g.result(), R"(typedef struct {
+  vector<float, 4> coord;
+} Uniforms;
+
+ConstantBuffer<Uniforms> uniforms : register(b0);
+
+void frag_main() {
+  float v = uniforms.coord.x;
+  return;
+}
+
+)");
 }
 
 TEST_F(HlslGeneratorImplTest,
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 11f29f3..4a7f48e 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -1347,6 +1347,10 @@
     // we should instead be using a provided mapping that uses both buffer and
     // set. https://bugs.chromium.org/p/tint/issues/detail?id=104
     auto* binding = data.second.binding;
+    if (binding == nullptr) {
+      error_ = "unable to find binding information for uniform: " + var->name();
+      return false;
+    }
     // auto* set = data.second.set;
 
     out_ << "constant ";