Support for TF.Gather including intermediate dialect work (#4292)

Gather operations added to the strings and tf_strings dialect with
lowerings from TensorFlow to tf_strings and tf_string to strings.
This includes tests for each and an end-to-end test. Right now the
python test does not validate values (missing conversion from
StringTensor to Numpy equivalent) but prints instead. Close enough
for current support.
diff --git a/integrations/tensorflow/compiler/Passes.cpp b/integrations/tensorflow/compiler/Passes.cpp
index e208e99..3b7593a 100644
--- a/integrations/tensorflow/compiler/Passes.cpp
+++ b/integrations/tensorflow/compiler/Passes.cpp
@@ -72,6 +72,12 @@
   pm.addPass(createCanonicalizerPass());
 
   //----------------------------------------------------------------------------
+  // Lowering module specific TF behavior to intermediate dialects.
+  //----------------------------------------------------------------------------
+  pm.addPass(tf_strings::createConvertTFToTFStringsPass());
+  pm.addPass(tf_tensorlist::createConvertTFToTFTensorListPass());
+
+  //----------------------------------------------------------------------------
   // Legalize to XLA
   //----------------------------------------------------------------------------
   pm.addPass(createConvertToMHLOPass());
@@ -93,11 +99,8 @@
   pm.addPass(createCanonicalizerPass());
 
   //----------------------------------------------------------------------------
-  // Lowering module specific TF behavior to custom dialects.
+  // Lowering intermediate dialects to module specific dialects.
   //----------------------------------------------------------------------------
-  pm.addPass(tf_strings::createConvertTFToTFStringsPass());
-  pm.addPass(tf_tensorlist::createConvertTFToTFTensorListPass());
-  pm.addPass(createCanonicalizerPass());
   pm.addPass(tf_strings::createConvertTFStringsToStringsPass());
   pm.addPass(tf_tensorlist::createConvertTFTensorListToTensorListPass());
 
diff --git a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_strings_to_strings.cc b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_strings_to_strings.cc
index 7217eb7..e043967 100644
--- a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_strings_to_strings.cc
+++ b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_strings_to_strings.cc
@@ -80,6 +80,9 @@
   patterns.insert<OpConversion<tf_strings::ToStringTensorOp,
                                iree_compiler::IREE::Strings::ToStringTensorOp>>(
       context);
+  patterns.insert<OpConversion<tf_strings::GatherOp,
+                               iree_compiler::IREE::Strings::GatherOp>>(
+      context);
   patterns.insert<
       OpConversion<tf_strings::StringTensorToStringOp,
                    iree_compiler::IREE::Strings::StringTensorToStringOp>>(
diff --git a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.cc b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.cc
index e3baa11..4fe4d7e 100644
--- a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.cc
+++ b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.cc
@@ -23,6 +23,7 @@
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/BuiltinTypes.h"
 #include "mlir/IR/Dialect.h"
+#include "mlir/IR/Matchers.h"
 #include "mlir/IR/PatternMatch.h"
 #include "mlir/Pass/Pass.h"
 #include "mlir/Pass/PassRegistry.h"
@@ -85,6 +86,44 @@
   }
 };
 
+struct GatherV2OpLowering : public OpRewritePattern<TF::GatherV2Op> {
+  using OpRewritePattern<TF::GatherV2Op>::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(TF::GatherV2Op op,
+                                PatternRewriter &rewriter) const override {
+    auto tensor = op.params();
+    auto tensorTy = tensor.getType().dyn_cast<RankedTensorType>();
+    if (!tensorTy || !tensorTy.getElementType().isa<TF::StringType>()) {
+      return failure();
+    }
+
+    DenseIntElementsAttr axis;
+    if (!matchPattern(op.axis(), m_Constant(&axis))) {
+      return failure();
+    }
+
+    if (axis.getType().cast<ShapedType>().getRank() != 0) {
+      return failure();
+    }
+
+    auto axisValue = axis.getValue<IntegerAttr>({});
+    auto axisInt = axisValue.getValue().getZExtValue();
+
+    if (axisInt != tensorTy.getRank() - 1) {
+      return failure();
+    }
+
+    auto resultTy = op.getType().cast<ShapedType>();
+    rewriter.replaceOpWithNewOp<tf_strings::GatherOp>(
+        op,
+        RankedTensorType::get(resultTy.getShape(),
+                              tf_strings::StringType::get(op.getContext())),
+        tensor, op.indices());
+
+    return success();
+  }
+};
+
 class ConvertTFToTFStringsPass
     : public ConversionPass<ConvertTFToTFStringsPass, StringTypeConverter> {
  public:
@@ -108,6 +147,7 @@
 void populateTFToTFStringsPatterns(MLIRContext *ctx,
                                    OwningRewritePatternList &patterns) {
   populateWithGenerated(ctx, patterns);
+  patterns.insert<GatherV2OpLowering>(ctx);
   patterns.insert<StringFormatOpLowering>(ctx);
 }
 
diff --git a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.td b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.td
index 83c9d6f..f20380d 100644
--- a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.td
+++ b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/convert_tf_to_tf_strings.td
@@ -17,6 +17,9 @@
 include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.td"
 include "integrations/tensorflow/compiler/dialect/tf_strings/ir/ops.td"
 
+def : Pat<(TF_IdentityOp TF_StrTensor:$value),
+           (replaceWithValue $value)>;
+
 def : Pat<(TF_PrintV2Op TF_Str:$value, $unused1, $unused2),
           (TFStrings_PrintOp $value)>;
 
diff --git a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_strings_to_strings.mlir b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_strings_to_strings.mlir
index 5492142..a220ae3 100644
--- a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_strings_to_strings.mlir
+++ b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_strings_to_strings.mlir
@@ -35,3 +35,12 @@
   // CHECK: return [[VAL0]]
   return %0 : !tf_strings.string
 }
+
+// CHECK-LABEL: @gather
+func @gather(%arg0: tensor<5x!tf_strings.string>, %arg1: tensor<3xi32>) -> tensor<3x!tf_strings.string> {
+  // CHECK-DAG: [[VAL0:%.+]] = "strings.gather"(%arg0, %arg1)
+  %0 = "tf_strings.gather"(%arg0, %arg1) : (tensor<5x!tf_strings.string>, tensor<3xi32>) -> tensor<3x!tf_strings.string>
+
+  // CHECK: return [[VAL0]]
+  return %0 : tensor<3x!tf_strings.string>
+}
diff --git a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_to_tf_strings.mlir b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_to_tf_strings.mlir
index dc63d9a..eb42d54 100644
--- a/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_to_tf_strings.mlir
+++ b/integrations/tensorflow/compiler/dialect/tf_strings/conversion/test/tf_to_tf_strings.mlir
@@ -36,3 +36,11 @@
   return
 }
 
+// CHECK-LABEL: func @gatherv2
+func @gatherv2(%arg0: tensor<5x!tf.string>, %arg1: tensor<3xi32>) {
+  %0 = "tf.Const"() {device = "", value = dense<0> : tensor<i32>} : () -> tensor<i32>
+
+  // CHECK-DAG: [[VAL0:%.+]] = "tf_strings.gather"(%arg0, %arg1)
+  %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = 0 : i64, device = ""} : (tensor<5x!tf.string>, tensor<3xi32>, tensor<i32>) -> tensor<3x!tf.string>
+  return
+}
diff --git a/integrations/tensorflow/compiler/dialect/tf_strings/ir/ops.td b/integrations/tensorflow/compiler/dialect/tf_strings/ir/ops.td
index 9cf9379..b3faff5 100644
--- a/integrations/tensorflow/compiler/dialect/tf_strings/ir/ops.td
+++ b/integrations/tensorflow/compiler/dialect/tf_strings/ir/ops.td
@@ -44,6 +44,34 @@
     OpBuilderDAG<(ins "Value":$value)>];
 }
 
+def TFStrings_GatherOp : Op<TFStrings_Dialect, "gather"> {
+  let summary = "Gathers all the strings from by index.";
+  let description = [{
+    Gathers all the strings from a Tensor using the index of the last dimension.
+  }];
+
+  let arguments = (ins TFStrings_StringTensor:$dict,
+                  TFStrings_ValueTensor:$indices);
+
+  let results = (outs
+    TFStrings_StringTensor:$result
+  );
+}
+
+def TFStrings_ConcatOp : Op<TFStrings_Dialect, "concat"> {
+  let summary = "Concatenates the strings in the tensor along the last dimension.";
+
+  let description = [{
+    Concatenates the strings in the tensor along the last dimension.
+  }];
+
+  let arguments = (ins TFStrings_StringTensor:$value);
+
+  let results = (outs
+    TFStrings_StringTensor:$result
+  );
+}
+
 def TFStrings_ToStringTensorOp : TFStrings_Op<"to_string_tensor"> {
   let summary = "Converts a tensor of values to a tensor of strings.";
   let description = [{
diff --git a/integrations/tensorflow/e2e/strings_test.py b/integrations/tensorflow/e2e/strings_test.py
index 523062d..e8ef23d 100644
--- a/integrations/tensorflow/e2e/strings_test.py
+++ b/integrations/tensorflow/e2e/strings_test.py
@@ -33,6 +33,13 @@
     string_tensor = tf.strings.as_string(ids)
     tf.print(string_tensor)
 
+  @tf.function(input_signature=[
+      tf.TensorSpec((None,), dtype=tf.int32),
+      tf.TensorSpec((None,), dtype=tf.int32),
+  ])
+  def gather(self, string_values, indices):
+    tf.print(tf.gather(tf.as_string(string_values), indices))
+
 #  @tf.function(input_signature=[
 #      tf.TensorSpec((None, None), dtype=tf.int32),
 #  ])
@@ -56,6 +63,17 @@
 
     self.compare_backends(print_ids, self._modules)
 
+  def test_gather(self):
+
+    def gather(module):
+      string_values = np.asarray([ord(c) for c in string.printable],
+                                 dtype=np.int32)
+      input_indices = np.asarray([12, 10, 29, 21, 10, 34], dtype=np.int32)
+      module.gather(string_values, input_indices)
+
+    self.compare_backends(gather, self._modules)
+
+
 #  def test_strings_to_ids(self):
 #
 #    def strings_to_ids(module):
diff --git a/iree/compiler/Dialect/Modules/Strings/Conversion/ConversionPatterns.cc b/iree/compiler/Dialect/Modules/Strings/Conversion/ConversionPatterns.cc
index e02895c..557bc85 100644
--- a/iree/compiler/Dialect/Modules/Strings/Conversion/ConversionPatterns.cc
+++ b/iree/compiler/Dialect/Modules/Strings/Conversion/ConversionPatterns.cc
@@ -29,6 +29,9 @@
   patterns.insert<HALOpConversion<IREE::Strings::ToStringTensorOp,
                                   IREE::Strings::ToStringTensorOp>>(
       context, typeConverter);
+  patterns.insert<
+      HALOpConversion<IREE::Strings::GatherOp, IREE::Strings::GatherOp>>(
+      context, typeConverter);
 }
 
 void populateStringsToVMPatterns(MLIRContext *context,
diff --git a/iree/compiler/Dialect/Modules/Strings/IR/Ops.td b/iree/compiler/Dialect/Modules/Strings/IR/Ops.td
index 743c351..92f8265 100644
--- a/iree/compiler/Dialect/Modules/Strings/IR/Ops.td
+++ b/iree/compiler/Dialect/Modules/Strings/IR/Ops.td
@@ -94,13 +94,13 @@
 }
 
 def STRINGS_GatherOp : Op<STRINGS_Dialect, "gather", [NoSideEffect]> {
-  let summary = [{gathers all the strings from a Tensor by id}];
+  let summary = "Gathers all the strings from a Tensor by index.";
   let description = [{
-    Gathers all the strings from a Tensor by ID.
+    Gathers all the strings from a Tensor by index values along the final axis.
   }];
 
   let arguments = (ins STRINGS_StringTensor:$dict,
-                  STRINGS_ValueTensor:$ids);
+                  STRINGS_ValueTensor:$indices);
 
   let results = (outs
     STRINGS_StringTensor:$result
@@ -108,9 +108,7 @@
 }
 
 def STRINGS_ConcatOp : Op<STRINGS_Dialect, "concat", [NoSideEffect]> {
-  let summary = [{
-  concatenates the strings in the tensor along the last dimension
-  }];
+  let summary = "Concatenates a tensor of strings along the last dimension";
 
   let description = [{
     Concatenates the strings in the tensor along the last dimension.
@@ -124,7 +122,7 @@
 }
 
 def STRINGS_PrintOp : Op<STRINGS_Dialect, "print"> {
-  let summary = [{prints the contents of a string}];
+  let summary = "Prints the contents of a string.";
   let description = [{
     Prints the contents of a string.
   }];