[type-determination] Adding builtin texture operations

That includes texture_load, texture_sample, texture_sample_level,
texture_sample_bias, texture_sample_compare. Also determining the
result type.

Change-Id: I9e6fb19964fa171ee9b0594633b2dfe95e3e38b1
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/27360
Commit-Queue: Tomek Ponitka <tommek@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/ast/intrinsic.cc b/src/ast/intrinsic.cc
index 226581b..164632a 100644
--- a/src/ast/intrinsic.cc
+++ b/src/ast/intrinsic.cc
@@ -37,9 +37,16 @@
          name == "is_normal";
 }
 
+bool IsTextureOperationIntrinsic(const std::string& name) {
+  return name == "texture_load" || name == "texture_sample" ||
+         name == "texture_sample_level" || name == "texture_sample_bias" ||
+         name == "texture_sample_compare";
+}
+
 bool IsIntrinsic(const std::string& name) {
   return IsDerivative(name) || name == "all" || name == "any" ||
-         IsFloatClassificationIntrinsic(name) || name == "dot" ||
+         IsFloatClassificationIntrinsic(name) ||
+         IsTextureOperationIntrinsic(name) || name == "dot" ||
          name == "outer_product" || name == "select";
 }
 
diff --git a/src/ast/intrinsic.h b/src/ast/intrinsic.h
index b11a73c..1196db9 100644
--- a/src/ast/intrinsic.h
+++ b/src/ast/intrinsic.h
@@ -41,6 +41,11 @@
 /// @returns true if the given |name| is a float intrinsic
 bool IsFloatClassificationIntrinsic(const std::string& name);
 
+/// Determines if the given |name| is a texture operation intrinsic
+/// @param name the name to check
+/// @returns true if the given |name| is a texture operation intrinsic
+bool IsTextureOperationIntrinsic(const std::string& name);
+
 /// Determines if the given |name| is an intrinsic
 /// @param name the name to check
 /// @returns true if the given |name| is an intrinsic
diff --git a/src/type_determiner.cc b/src/type_determiner.cc
index 9f2e90d..04ec17b 100644
--- a/src/type_determiner.cc
+++ b/src/type_determiner.cc
@@ -44,7 +44,10 @@
 #include "src/ast/type/i32_type.h"
 #include "src/ast/type/matrix_type.h"
 #include "src/ast/type/pointer_type.h"
+#include "src/ast/type/sampled_texture_type.h"
+#include "src/ast/type/storage_texture_type.h"
 #include "src/ast/type/struct_type.h"
+#include "src/ast/type/texture_type.h"
 #include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type_constructor_expression.h"
@@ -540,6 +543,7 @@
   return true;
 }
 
+// TODO(tommek): Update names to camel case
 bool TypeDeterminer::DetermineIntrinsic(const std::string& name,
                                         ast::CallExpression* expr) {
   if (ast::intrinsic::IsDerivative(name)) {
@@ -584,6 +588,46 @@
     }
     return true;
   }
+  if (ast::intrinsic::IsTextureOperationIntrinsic(name)) {
+    uint32_t num_of_params =
+        (name == "texture_load" || name == "texture_sample") ? 3 : 4;
+    if (expr->params().size() != num_of_params) {
+      set_error(expr->source(),
+                "incorrect number of parameters for " + name + ", got " +
+                    std::to_string(expr->params().size()) + " and expected " +
+                    std::to_string(num_of_params));
+      return false;
+    }
+
+    if (name == "texture_sample_compare") {
+      expr->func()->set_result_type(
+          ctx_.type_mgr().Get(std::make_unique<ast::type::F32Type>()));
+      return true;
+    }
+
+    auto& texture_param = expr->params()[0];
+    if (!DetermineResultType(texture_param.get())) {
+      return false;
+    }
+    if (!texture_param->result_type()->UnwrapPtrIfNeeded()->IsTexture()) {
+      set_error(expr->source(), "invalid first argument for " + name);
+      return false;
+    }
+    ast::type::TextureType* texture =
+        texture_param->result_type()->UnwrapPtrIfNeeded()->AsTexture();
+
+    if (!texture->IsStorage() && !texture->IsSampled()) {
+      set_error(expr->source(), "invalid texture for " + name);
+      return false;
+    }
+
+    expr->func()->set_result_type(
+        ctx_.type_mgr().Get(std::make_unique<ast::type::VectorType>(
+            texture->IsStorage() ? texture->AsStorage()->type()
+                                 : texture->AsSampled()->type(),
+            4)));
+    return true;
+  }
   if (name == "dot") {
     expr->func()->set_result_type(
         ctx_.type_mgr().Get(std::make_unique<ast::type::F32Type>()));
diff --git a/src/type_determiner_test.cc b/src/type_determiner_test.cc
index 092e947..0ba43ef 100644
--- a/src/type_determiner_test.cc
+++ b/src/type_determiner_test.cc
@@ -46,11 +46,16 @@
 #include "src/ast/type/alias_type.h"
 #include "src/ast/type/array_type.h"
 #include "src/ast/type/bool_type.h"
+#include "src/ast/type/depth_texture_type.h"
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type/i32_type.h"
 #include "src/ast/type/matrix_type.h"
 #include "src/ast/type/pointer_type.h"
+#include "src/ast/type/sampled_texture_type.h"
+#include "src/ast/type/sampler_type.h"
+#include "src/ast/type/storage_texture_type.h"
 #include "src/ast/type/struct_type.h"
+#include "src/ast/type/texture_type.h"
 #include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type_constructor_expression.h"
@@ -80,6 +85,7 @@
 
   TypeDeterminer* td() const { return td_.get(); }
   ast::Module* mod() { return &mod_; }
+  Context* ctx() { return &ctx_; }
 
  private:
   Context ctx_;
@@ -1801,6 +1807,382 @@
     Intrinsic_FloatMethod,
     testing::Values("is_inf", "is_nan", "is_finite", "is_normal"));
 
+enum class TextureType { kF32, kI32, kU32 };
+inline std::ostream& operator<<(std::ostream& out, TextureType data) {
+  if (data == TextureType::kF32) {
+    out << "f32";
+  } else if (data == TextureType::kI32) {
+    out << "i32";
+  } else {
+    out << "u32";
+  }
+  return out;
+}
+
+struct TextureTestParams {
+  ast::type::TextureDimension dim;
+  TextureType type = TextureType::kF32;
+  ast::type::ImageFormat format = ast::type::ImageFormat::kR16Float;
+};
+inline std::ostream& operator<<(std::ostream& out, TextureTestParams data) {
+  out << data.dim << "_" << data.type;
+  return out;
+}
+
+class Intrinsic_TextureOperation
+    : public TypeDeterminerTestWithParam<TextureTestParams> {
+ public:
+  std::unique_ptr<ast::type::Type> get_coords_type(
+      ast::type::TextureDimension dim,
+      ast::type::Type* type) {
+    if (dim == ast::type::TextureDimension::k1d) {
+      if (type->IsI32()) {
+        return std::make_unique<ast::type::I32Type>();
+      } else if (type->IsU32()) {
+        return std::make_unique<ast::type::U32Type>();
+      } else {
+        return std::make_unique<ast::type::F32Type>();
+      }
+    } else if (dim == ast::type::TextureDimension::k1dArray ||
+               dim == ast::type::TextureDimension::k2d ||
+               dim == ast::type::TextureDimension::k2dMs) {
+      return std::make_unique<ast::type::VectorType>(type, 2);
+    } else if (dim == ast::type::TextureDimension::kCubeArray) {
+      return std::make_unique<ast::type::VectorType>(type, 4);
+    } else {
+      return std::make_unique<ast::type::VectorType>(type, 3);
+    }
+  }
+
+  void add_call_param(std::string name,
+                      ast::type::Type* type,
+                      ast::ExpressionList* call_params) {
+    auto var =
+        std::make_unique<ast::Variable>(name, ast::StorageClass::kNone, type);
+    mod()->AddGlobalVariable(std::move(var));
+    call_params->push_back(std::make_unique<ast::IdentifierExpression>(name));
+  }
+
+  std::unique_ptr<ast::type::Type> subtype(TextureType type) {
+    if (type == TextureType::kF32) {
+      return std::make_unique<ast::type::F32Type>();
+    }
+    if (type == TextureType::kI32) {
+      return std::make_unique<ast::type::I32Type>();
+    }
+    return std::make_unique<ast::type::U32Type>();
+  }
+};
+
+using Intrinsic_StorageTextureOperation = Intrinsic_TextureOperation;
+TEST_P(Intrinsic_StorageTextureOperation, TextureLoadRo) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+  auto format = GetParam().format;
+
+  ast::type::I32Type i32;
+  auto coords_type = get_coords_type(dim, &i32);
+
+  ast::type::Type* texture_type =
+      ctx()->type_mgr().Get(std::make_unique<ast::type::StorageTextureType>(
+          dim, ast::type::StorageAccess::kRead, format));
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("lod", &i32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_load"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->Determine());
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  ASSERT_NE(expr.result_type(), nullptr);
+  ASSERT_TRUE(expr.result_type()->IsVector());
+  if (type == TextureType::kF32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsF32());
+  } else if (type == TextureType::kI32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsI32());
+  } else {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsU32());
+  }
+  EXPECT_EQ(expr.result_type()->AsVector()->size(), 4u);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TypeDeterminerTest,
+    Intrinsic_StorageTextureOperation,
+    testing::Values(
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kF32,
+                          ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kI32,
+                          ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kU32,
+                          ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kF32, ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kI32, ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kU32, ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kF32,
+                          ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kI32,
+                          ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kU32,
+                          ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kF32, ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kI32, ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kU32, ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kF32,
+                          ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kI32,
+                          ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kU32,
+                          ast::type::ImageFormat::kR8Unorm}));
+
+using Intrinsic_SampledTextureOperation = Intrinsic_TextureOperation;
+TEST_P(Intrinsic_SampledTextureOperation, TextureLoadSampled) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  ast::type::I32Type i32;
+  std::unique_ptr<ast::type::Type> s = subtype(type);
+  auto coords_type = get_coords_type(dim, &i32);
+  auto texture_type =
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get());
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("lod", &i32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_load"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->Determine());
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  ASSERT_NE(expr.result_type(), nullptr);
+  ASSERT_TRUE(expr.result_type()->IsVector());
+  if (type == TextureType::kF32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsF32());
+  } else if (type == TextureType::kI32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsI32());
+  } else {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsU32());
+  }
+  EXPECT_EQ(expr.result_type()->AsVector()->size(), 4u);
+}
+
+TEST_P(Intrinsic_SampledTextureOperation, TextureSample) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  auto s = subtype(type);
+  ast::type::F32Type f32;
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kSampler);
+  auto coords_type = get_coords_type(dim, &f32);
+  auto texture_type =
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get());
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type.get(), &call_params);
+  add_call_param("sampler", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->Determine());
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  ASSERT_NE(expr.result_type(), nullptr);
+  ASSERT_TRUE(expr.result_type()->IsVector());
+  if (type == TextureType::kF32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsF32());
+  } else if (type == TextureType::kI32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsI32());
+  } else {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsU32());
+  }
+  EXPECT_EQ(expr.result_type()->AsVector()->size(), 4u);
+}
+
+TEST_P(Intrinsic_SampledTextureOperation, TextureSampleLevel) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  ast::type::F32Type f32;
+  auto s = subtype(type);
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kSampler);
+  auto coords_type = get_coords_type(dim, &f32);
+  auto texture_type =
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get());
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type.get(), &call_params);
+  add_call_param("sampler", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("lod", &f32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_level"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->Determine());
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  ASSERT_NE(expr.result_type(), nullptr);
+  ASSERT_TRUE(expr.result_type()->IsVector());
+  if (type == TextureType::kF32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsF32());
+  } else if (type == TextureType::kI32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsI32());
+  } else {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsU32());
+  }
+  EXPECT_EQ(expr.result_type()->AsVector()->size(), 4u);
+}
+
+TEST_P(Intrinsic_SampledTextureOperation, TextureSampleBias) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  ast::type::F32Type f32;
+  auto s = subtype(type);
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kSampler);
+  auto coords_type = get_coords_type(dim, &f32);
+  auto texture_type =
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get());
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type.get(), &call_params);
+  add_call_param("sampler", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("bias", &f32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_bias"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->Determine());
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  ASSERT_NE(expr.result_type(), nullptr);
+  ASSERT_TRUE(expr.result_type()->IsVector());
+  if (type == TextureType::kF32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsF32());
+  } else if (type == TextureType::kI32) {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsI32());
+  } else {
+    EXPECT_TRUE(expr.result_type()->AsVector()->type()->IsU32());
+  }
+  EXPECT_EQ(expr.result_type()->AsVector()->size(), 4u);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TypeDeterminerTest,
+    Intrinsic_SampledTextureOperation,
+    testing::Values(
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k2dMs,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k2dMs,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k2dMs,
+                          TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k2dMsArray,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k2dMsArray,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k2dMsArray,
+                          TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::kCube,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::kCube,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::kCube,
+                          TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::kCubeArray,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::kCubeArray,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::kCubeArray,
+                          TextureType::kU32}));
+
+using Intrinsic_DepthTextureOperation = Intrinsic_TextureOperation;
+TEST_P(Intrinsic_DepthTextureOperation, TextureSampleCompare) {
+  auto dim = GetParam().dim;
+
+  ast::type::F32Type f32;
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kComparisonSampler);
+  auto coords_type = get_coords_type(dim, &f32);
+  auto texture_type = std::make_unique<ast::type::DepthTextureType>(dim);
+
+  ast::ExpressionList call_params;
+
+  add_call_param("texture", texture_type.get(), &call_params);
+  add_call_param("sampler_comparison", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("depth_reference", &f32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_compare"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->Determine());
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  ASSERT_NE(expr.result_type(), nullptr);
+  EXPECT_TRUE(expr.result_type()->IsF32());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TypeDeterminerTest,
+    Intrinsic_DepthTextureOperation,
+    testing::Values(TextureTestParams{ast::type::TextureDimension::k2d},
+                    TextureTestParams{ast::type::TextureDimension::k2dArray},
+                    TextureTestParams{ast::type::TextureDimension::kCube},
+                    TextureTestParams{
+                        ast::type::TextureDimension::kCubeArray}));
+
 TEST_F(TypeDeterminerTest, Intrinsic_Dot) {
   ast::type::F32Type f32;
   ast::type::VectorType vec3(&f32, 3);