Add NNAPI delegate support for Fill

PiperOrigin-RevId: 314116176
Change-Id: I8216b776ec823ff9c0696410091291f6b2d3a385
diff --git a/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc b/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc
index 46a6a72..7800e98 100644
--- a/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc
+++ b/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc
@@ -173,6 +173,11 @@
 # Only constant tensors models
 ExpandDimsOpTest/.+/1,29
 
+# fill_test
+FillOpTest/FillOpTest/FillInt32/0,30
+FillOpTest/FillOpTest/FillFloat/0,30
+FillOpTest/FillOpTest/FillFloatInt32Dims/0,30
+
 # floor_test
 FloorOpTest/.+
 
diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc
index bf3714a..bdcb728 100644
--- a/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc
+++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate.cc
@@ -2343,6 +2343,50 @@
       ExpectMinAndroidSdkVersion(android_sdk_version, kMinSdkVersionForNNAPI13,
                                  &val_ctx);
     } break;
+    case kTfLiteBuiltinFill: {
+      ExpectOpVersion(version, 1, &val_ctx);
+      ExpectMinAndroidSdkVersion(android_sdk_version, kMinSdkVersionForNNAPI13,
+                                 &val_ctx);
+      const auto& dims_tensor = context->tensors[node->inputs->data[0]];
+      Expect(IsConstantTensor(&dims_tensor),
+             NNAPIValidationFailureType::kUnsupportedInputType,
+             "NNAPI doesn't support dynamic dimensions tensor.", &val_ctx);
+      EXPECT_INPUT_TYPE_IN(dims_tensor.type, kTfLiteInt32, kTfLiteInt64);
+      if (IsConstantTensor(&dims_tensor)) {
+        Expect(dims_tensor.dims->data[0] != 0,
+               NNAPIValidationFailureType::kUnsupportedOperandValue,
+               "NNAPI doesn't support generating scalars from FILL", &val_ctx);
+        if (dims_tensor.type == kTfLiteInt64) {
+          bool fit_in_int32 =
+              std::all_of(dims_tensor.data.i64,
+                          dims_tensor.data.i64 + dims_tensor.dims->data[0],
+                          [](int64_t dim) {
+                            return std::numeric_limits<int32_t>::min() <= dim &&
+                                   dim <= std::numeric_limits<int32_t>::max();
+                          });
+          Expect(fit_in_int32,
+                 NNAPIValidationFailureType::kUnsupportedOperandValue,
+                 "NNAPI only supports int32 dimensions tensor. If the "
+                 "dimensions type is int64 and they are constant we can "
+                 "convert them to int32 if the value isn't too large.",
+                 &val_ctx);
+        }
+      }
+      const auto& value_tensor = context->tensors[node->inputs->data[1]];
+      EXPECT_INPUT_TYPE_IN(value_tensor.type, kTfLiteFloat32, kTfLiteInt32,
+                           kTfLiteInt64);
+      if (value_tensor.type == kTfLiteInt64) {
+        Expect(
+            IsConstantTensor(&value_tensor) &&
+                *value_tensor.data.i64 <= std::numeric_limits<int32_t>::max() &&
+                *value_tensor.data.i64 >= std::numeric_limits<int32_t>::min(),
+            NNAPIValidationFailureType::kUnsupportedInputType,
+            "NNAPI only supports int32 input. If the input type is int64 and "
+            "constant we can convert it to int32 if the value isn't too "
+            "large.",
+            &val_ctx);
+      }
+    } break;
     default:
       // All other operators are not mapped.
       AddValidationFailure(NNAPIValidationFailureType::kUnsupportedOperator,
@@ -3127,6 +3171,9 @@
       mapping_args.builder->AddScalarFloat32Operand(1.0);
       *nn_op_type = ANEURALNETWORKS_ELU;
     } break;
+    case kTfLiteBuiltinFill: {
+      *nn_op_type = ANEURALNETWORKS_FILL;
+    } break;
     default:
       // All other operators are not mapped.
       return kTfLiteError;
@@ -3882,6 +3929,52 @@
           TF_LITE_ENSURE_STATUS(builder.AddTensorInput(input_index, hybrid_op,
                                                        input_tensor_flags));
         }
+      } else if (reg->builtin_code == kTfLiteBuiltinFill) {
+        if (input_pos == 0) {
+          const int dims_id = node->inputs->data[0];
+          const TfLiteTensor& dims_tensor = context->tensors[dims_id];
+          switch (dims_tensor.type) {
+            case kTfLiteInt32:
+              TF_LITE_ENSURE_STATUS(
+                  builder.AddTensorInput(input_index, hybrid_op));
+              break;
+            case kTfLiteInt64: {
+              // We made sure that dimensions are constant and fit into int32 in
+              // Map(), so we can safely create a new tensor with casted values.
+              const int dims_size = dims_tensor.dims->data[0];
+              std::vector<int32_t> dims_int32(dims_size);
+              std::copy(dims_tensor.data.i64, dims_tensor.data.i64 + dims_size,
+                        dims_int32.begin());
+              int new_tensor_index = -1;
+              builder.AddNewInputConstantTensor(
+                  ANEURALNETWORKS_TENSOR_INT32, kTfLiteInt32, dims_tensor.dims,
+                  dims_int32, dims_tensor.params, &new_tensor_index);
+            } break;
+            default:
+              return kTfLiteError;
+          }
+        } else {
+          const int value_id = node->inputs->data[1];
+          const TfLiteTensor& value_tensor = context->tensors[value_id];
+          switch (value_tensor.type) {
+            case kTfLiteFloat32:
+              TF_LITE_ENSURE_STATUS(
+                  builder.AddScalarFloat32Operand(*value_tensor.data.f));
+              break;
+            case kTfLiteInt32:
+              TF_LITE_ENSURE_STATUS(
+                  builder.AddScalarInt32Operand(*value_tensor.data.i32));
+              break;
+            case kTfLiteInt64:
+              // Map() function already makes sure int64 input is constant and
+              // fits into int32.
+              TF_LITE_ENSURE_STATUS(builder.AddScalarInt32Operand(
+                  static_cast<int32_t>(*value_tensor.data.i64)));
+              break;
+            default:
+              return kTfLiteError;
+          }
+        }
       } else {
         TF_LITE_ENSURE_STATUS(
             builder.AddTensorInput(input_index, hybrid_op, input_tensor_flags));
diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD
index 657b5d8..eb62a33 100644
--- a/tensorflow/lite/kernels/BUILD
+++ b/tensorflow/lite/kernels/BUILD
@@ -2014,6 +2014,7 @@
     name = "fill_test",
     size = "small",
     srcs = ["fill_test.cc"],
+    tags = ["tflite_nnapi"],
     deps = [
         ":builtin_ops",
         ":test_main",
diff --git a/tensorflow/lite/kernels/fill_test.cc b/tensorflow/lite/kernels/fill_test.cc
index 4ab013b..0717a31 100644
--- a/tensorflow/lite/kernels/fill_test.cc
+++ b/tensorflow/lite/kernels/fill_test.cc
@@ -24,87 +24,115 @@
 using ::testing::ElementsAreArray;
 using ::testing::IsEmpty;
 
+enum class TestType {
+  kConst = 0,
+  kDynamic = 1,
+};
+
+template <typename dims_type, typename value_type>
 class FillOpModel : public SingleOpModel {
  public:
-  explicit FillOpModel(const TensorData& input1, const TensorData& input2) {
-    input1_ = AddInput(input1);
-    input2_ = AddInput(input2);
-    output_ = AddOutput(input1);
+  explicit FillOpModel(TensorType dims_tensor_type,
+                       std::initializer_list<int> dims_shape,
+                       std::initializer_list<dims_type> dims_data,
+                       value_type value, TestType input_tensor_types) {
+    if (input_tensor_types == TestType::kDynamic) {
+      dims_ = AddInput(dims_tensor_type);
+      value_ = AddInput(GetTensorType<value_type>());
+    } else {
+      dims_ = AddConstInput(dims_tensor_type, dims_data, dims_shape);
+      value_ = AddConstInput(GetTensorType<value_type>(), {value}, {});
+    }
+    output_ = AddOutput(GetTensorType<value_type>());
     SetBuiltinOp(BuiltinOperator_FILL, BuiltinOptions_FillOptions,
                  CreateFillOptions(builder_).Union());
-    BuildInterpreter({GetShape(input1_), GetShape(input2_)});
+    BuildInterpreter({dims_shape, {}});
+
+    if (input_tensor_types == TestType::kDynamic) {
+      if (dims_data.size() > 0) {
+        PopulateTensor<dims_type>(dims_, dims_data);
+      }
+      PopulateTensor<value_type>(value_, {value});
+    }
   }
 
-  int input1() { return input1_; }
-  int input2() { return input2_; }
-  int output() { return output_; }
+  std::vector<value_type> GetOutput() {
+    return ExtractVector<value_type>(output_);
+  }
+  std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
 
  protected:
-  int input1_;
-  int input2_;
+  int dims_;
+  int value_;
   int output_;
 };
 
-TEST(FillOpModel, FillInt32) {
-  FillOpModel m({TensorType_INT32, {2}}, {TensorType_INT32});
-  m.PopulateTensor<int32_t>(m.input1(), {2, 3});
-  m.PopulateTensor<int32_t>(m.input2(), {-11});
+class FillOpTest : public ::testing::TestWithParam<TestType> {};
+
+TEST_P(FillOpTest, FillInt32) {
+  FillOpModel<int32_t, int32_t> m(TensorType_INT32, {2}, {2, 3}, -11,
+                                  GetParam());
   m.Invoke();
-  EXPECT_THAT(m.ExtractVector<int32_t>(m.output()),
-              ElementsAreArray({-11, -11, -11, -11, -11, -11}));
-  EXPECT_THAT(m.GetTensorShape(m.output()), ElementsAreArray({2, 3}));
+  EXPECT_THAT(m.GetOutput(), ElementsAreArray({-11, -11, -11, -11, -11, -11}));
+  EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 3}));
 }
 
-TEST(FillOpModel, FillInt64) {
-  FillOpModel m({TensorType_INT32, {2}}, {TensorType_INT64});
-  m.PopulateTensor<int32_t>(m.input1(), {2, 4});
-  m.PopulateTensor<int64_t>(m.input2(), {1LL << 45});
+TEST_P(FillOpTest, FillInt64) {
+  FillOpModel<int64_t, int64_t> m(TensorType_INT64, {2}, {2, 4}, 1LL << 45,
+                                  GetParam());
   m.Invoke();
-  EXPECT_THAT(m.ExtractVector<int64_t>(m.output()),
+  EXPECT_THAT(m.GetOutput(),
               ElementsAreArray({1LL << 45, 1LL << 45, 1LL << 45, 1LL << 45,
                                 1LL << 45, 1LL << 45, 1LL << 45, 1LL << 45}));
-  EXPECT_THAT(m.GetTensorShape(m.output()), ElementsAreArray({2, 4}));
+  EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 4}));
 }
 
-TEST(FillOpModel, FillFloat) {
-  FillOpModel m({TensorType_INT64, {3}}, {TensorType_FLOAT32});
-  m.PopulateTensor<int64_t>(m.input1(), {2, 2, 2});
-  m.PopulateTensor<float>(m.input2(), {4.0});
+TEST_P(FillOpTest, FillFloat) {
+  FillOpModel<int64_t, float> m(TensorType_INT64, {3}, {2, 2, 2}, 4.0,
+                                GetParam());
   m.Invoke();
-  EXPECT_THAT(m.ExtractVector<float>(m.output()),
+  EXPECT_THAT(m.GetOutput(),
               ElementsAreArray({4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0}));
-  EXPECT_THAT(m.GetTensorShape(m.output()), ElementsAreArray({2, 2, 2}));
+  EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
 }
 
-TEST(FillOpModel, FillOutputScalar) {
-  FillOpModel m({TensorType_INT64, {0}}, {TensorType_FLOAT32});
-  m.PopulateTensor<float>(m.input2(), {4.0});
+TEST_P(FillOpTest, FillFloatInt32Dims) {
+  FillOpModel<int32_t, float> m(TensorType_INT32, {3}, {2, 2, 2}, 4.0,
+                                GetParam());
   m.Invoke();
-  EXPECT_THAT(m.ExtractVector<float>(m.output()), ElementsAreArray({4.0}));
-  EXPECT_THAT(m.GetTensorShape(m.output()), IsEmpty());
+  EXPECT_THAT(m.GetOutput(),
+              ElementsAreArray({4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0}));
+  EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
 }
 
-TEST(FillOpModel, FillBool) {
-  FillOpModel m({TensorType_INT64, {3}}, {TensorType_BOOL});
-  m.PopulateTensor<int64_t>(m.input1(), {2, 2, 2});
-  m.PopulateTensor<bool>(m.input2(), {true});
+TEST_P(FillOpTest, FillOutputScalar) {
+  FillOpModel<int64_t, float> m(TensorType_INT64, {0}, {}, 4.0, GetParam());
   m.Invoke();
-  EXPECT_THAT(
-      m.ExtractVector<bool>(m.output()),
-      ElementsAreArray({true, true, true, true, true, true, true, true}));
-  EXPECT_THAT(m.GetTensorShape(m.output()), ElementsAreArray({2, 2, 2}));
+  EXPECT_THAT(m.GetOutput(), ElementsAreArray({4.0}));
+  EXPECT_THAT(m.GetOutputShape(), IsEmpty());
 }
 
-TEST(FillOpModel, FillString) {
-  FillOpModel m({TensorType_INT64, {3}}, {TensorType_STRING});
-  m.PopulateTensor<int64_t>(m.input1(), {2, 2, 2});
-  m.PopulateTensor<std::string>(m.input2(), {"AB"});
+TEST_P(FillOpTest, FillBool) {
+  FillOpModel<int64_t, bool> m(TensorType_INT64, {3}, {2, 2, 2}, true,
+                               GetParam());
   m.Invoke();
-  EXPECT_THAT(
-      m.ExtractVector<std::string>(m.output()),
-      ElementsAreArray({"AB", "AB", "AB", "AB", "AB", "AB", "AB", "AB"}));
-  EXPECT_THAT(m.GetTensorShape(m.output()), ElementsAreArray({2, 2, 2}));
+  EXPECT_THAT(m.GetOutput(), ElementsAreArray({true, true, true, true, true,
+                                               true, true, true}));
+  EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
 }
 
+TEST(FillOpTest, FillString) {
+  FillOpModel<int64_t, std::string> m(TensorType_INT64, {3}, {2, 2, 2}, "AB",
+                                      TestType::kDynamic);
+  m.Invoke();
+  EXPECT_THAT(m.GetOutput(), ElementsAreArray({"AB", "AB", "AB", "AB", "AB",
+                                               "AB", "AB", "AB"}));
+  EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
+}
+
+INSTANTIATE_TEST_SUITE_P(FillOpTest, FillOpTest,
+                         ::testing::Values(TestType::kConst,
+                                           TestType::kDynamic));
+
 }  // namespace
 }  // namespace tflite
diff --git a/tensorflow/lite/kernels/test_util.h b/tensorflow/lite/kernels/test_util.h
index 90a4df5..ac1ad5d 100644
--- a/tensorflow/lite/kernels/test_util.h
+++ b/tensorflow/lite/kernels/test_util.h
@@ -17,6 +17,7 @@
 
 #include <cmath>
 #include <complex>
+#include <type_traits>
 #include <vector>
 
 #include <gmock/gmock.h>
@@ -812,6 +813,7 @@
   if (std::is_same<T, int64_t>::value) return TensorType_INT64;
   if (std::is_same<T, uint8_t>::value) return TensorType_UINT8;
   if (std::is_same<T, string>::value) return TensorType_STRING;
+  if (std::is_same<T, bool>::value) return TensorType_BOOL;
   return TensorType_MIN;  // default value
 }