[StableHLO/MHLO] `tf-lower-to-mlprogram-and-hlo`
cc @matthiaskramm @gaikwadrahul8,
I was able to reproduce the issue on tensorflow v2.11 and tf-nightly. Kindly find the gist of it [here](https://colab.research.google.com/gist/tilakrayal/a6272839ae621a7b289186743d654b08/stablehlo_read_variable.ipynb). Interesting. This uses a different entry point (`pywrap_mlir.import_function` instead of `pywrap_mlir.experimental_convert_saved_model_to_mlir`). As such, the frozen function has less information:

```
@__inference_infer_func_97(
  %arg0: tensor<32x32x32x3xf32> {tf._user_specified_name = "data"}
   %arg1: tensor<*x!tf_type.resource>,
   %arg2: tensor<*x!tf_type.resource>, ...) {
     %0 = tf_executor.graph {
      %outputs, %control = tf_executor.island wraps "tf.ReadVariableOp"(%arg2)
      ...
} 
```

(I got the MLIR before the `tf-lower-to-mlprogram-and-hlo` pass by setting the pipeline to "")

Note how there's no `GlobalTensor` op corresponding to the "resource" arguments. And also, since this is not a saved_model, we don't have the `tf_saved_model.bound_input` we usually use to map arguments to resources.

What should the ReadVariable op be converted to? Should it become an argument? Or should we create a `ml_program.GlobalOp` with a machine-generated name that represents (otherwise unassigned) resource arguments to public functions?
(Either is fine with me, I'm mostly curious what kind of stablehlo/ml_program output will be the easiest to consume.) Thank you for taking a look, @matthiaskramm! @christopherbate could you opine here please? @matthiaskramm could we maybe replace the ReadVariableOp by the MHLO equivalent: https://www.tensorflow.org/mlir/hlo_ops#mhloinfeed_mlirmhloinfeedop and the StableHLO: https://github.com/openxla/stablehlo/blob/main/docs/spec.md#infeed We convert all other variable operations to `ml_program`. It would be weird to convert only some of them into `stablehlo` instead. @matthiaskramm could we work at making this not SavedModel specific maybe ?
https://github.com/tensorflow/tensorflow/blob/b737ee48f1de346151b1e32e4f2d8351071e64d7/tensorflow/compiler/mlir/tensorflow/transforms/tf_savedmodel_passes.td#L93-L113 Right, that's the idea. I think based on what we've discussed so far, the best option is to we create a ml_program.GlobalOp with a machine-generated name that represents (otherwise unassigned) resource arguments to public functions, in the case where we convert a Graph instead of a savedmodel. That's what I've seen other converters do, as well.
### Tensorflow Version

2.13.0-dev20230301 - TF Nightly

### Current Behaviour?

StableHLO / MHLO seems to not be able to convert the `ReadVariableOP` which is a big problem because it prevents to convert any `tf.function` to StableHLO/MHLO which is not first frozen.

You can find a colab here: https://colab.research.google.com/drive/1NrKZ3tihyYR9hj5r9Tfp5cPiirS-nF5u

### Standalone code to reproduce the issue

```python
import tensorflow as tf
from tensorflow.python import pywrap_mlir

USE_FREEZING = False   # Set it to `True` and the problem disappears

def generate_model():
  return tf.keras.models.Sequential([
    tf.keras.layers.Input(shape=(32, 32, 3)),
    tf.keras.layers.Conv2D(2, 3, activation='relu', padding="same")
  ])

model = generate_model()
model.trainable = False  # Freeze the outer model

@tf.function()
def infer_func(data):
  return model(data, training=False)

concrete_infer_func = infer_func.get_concrete_function(
    tf.TensorSpec((32, 32, 32, 3), tf.float32)
)

if USE_FREEZING:
    from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2_as_graph
    frozen_func, gdef = convert_variables_to_constants_v2_as_graph(
        func=concrete_infer_func,
        lower_control_flow=True, 
        aggressive_inlining=False
    )
else:
  frozen_func = concrete_infer_func


def convert_to_hlo(concrete_function, use_stablehlo=True):
  
  pipelines = ["tf-lower-to-mlprogram-and-hlo"]
  if not use_stablehlo:
    pipelines.append("stablehlo-legalize-to-hlo")

  return pywrap_mlir.import_function(
      concrete_function, 
      pass_pipeline=",".join(pipelines),
      show_debug_info=False
  )

# ---------------------------------------------------------------
# Convert saved model to Stablehlo + upstream MLIR ops
# ---------------------------------------------------------------

WORKFLOW_STEPS = [
  ("mhlo", False),
  ("stablehlo", True),
]

for extension, use_stablehlo in WORKFLOW_STEPS:
    with open(f"model.{extension}", "w") as f:
      hlo_str = convert_to_hlo(
        frozen_func, 
        use_stablehlo=use_stablehlo
      )

      f.write(hlo_str)

    import hashlib
    checksum = hashlib.md5(open(f"model.{extension}","rb").read()).hexdigest()
    print(f"File: model.{extension:10s}: {checksum}")
```


### Relevant log output

As you can see below, they both contain:
- `"tf.ReadVariableOp"`
- `tf.Conv2D`

And these are clearly not part of the StableHLO/MHLO language

```shell
$ cat model.stablehlo

module {
  func.func @__inference_infer_func_49(%arg0: tensor<32x32x32x3xf32>, %arg1: tensor<*x!tf_type.resource>, %arg2: tensor<*x!tf_type.resource>) -> tensor<32x?x?x?xf32> {
    %0 = stablehlo.constant dense<0.000000e+00> : tensor<f32>
    %1 = "tf.ReadVariableOp"(%arg2) {device = ""} : (tensor<*x!tf_type.resource>) -> tensor<*xf32>
    %2 = "tf.ReadVariableOp"(%arg1) {device = ""} : (tensor<*x!tf_type.resource>) -> tensor<*xf32>
    %3 = "tf.Conv2D"(%arg0, %2) {data_format = "NHWC", device = "", dilations = [1, 1, 1, 1], explicit_paddings = [], padding = "SAME", strides = [1, 1, 1, 1], use_cudnn_on_gpu = true} : (tensor<32x32x32x3xf32>, tensor<*xf32>) -> tensor<32x?x?x?xf32>
    %4 = shape.shape_of %3 : tensor<32x?x?x?xf32> -> tensor<4xindex>
    %5 = stablehlo.dynamic_broadcast_in_dim %1, %4, dims = [3] : (tensor<*xf32>, tensor<4xindex>) -> tensor<32x?x?x?xf32>
    %6 = stablehlo.add %3, %5 : tensor<32x?x?x?xf32>
    %7 = shape.shape_of %6 : tensor<32x?x?x?xf32> -> tensor<4xindex>
    %8 = stablehlo.dynamic_broadcast_in_dim %0, %7, dims = [] : (tensor<f32>, tensor<4xindex>) -> tensor<32x?x?x?xf32>
    %9 = stablehlo.maximum %8, %6 : tensor<32x?x?x?xf32>
    return %9 : tensor<32x?x?x?xf32>
  }
}
```

```shell
$ cat model.mhlo

module {
  func.func @__inference_infer_func_49(%arg0: tensor<32x32x32x3xf32>, %arg1: tensor<*x!tf_type.resource>, %arg2: tensor<*x!tf_type.resource>) -> tensor<32x?x?x?xf32> {
    %0 = mhlo.constant dense<0.000000e+00> : tensor<f32>
    %1 = "tf.ReadVariableOp"(%arg2) {device = ""} : (tensor<*x!tf_type.resource>) -> tensor<*xf32>
    %2 = "tf.ReadVariableOp"(%arg1) {device = ""} : (tensor<*x!tf_type.resource>) -> tensor<*xf32>
    %3 = "tf.Conv2D"(%arg0, %2) {data_format = "NHWC", device = "", dilations = [1, 1, 1, 1], explicit_paddings = [], padding = "SAME", strides = [1, 1, 1, 1], use_cudnn_on_gpu = true} : (tensor<32x32x32x3xf32>, tensor<*xf32>) -> tensor<32x?x?x?xf32>
    %4 = shape.shape_of %3 : tensor<32x?x?x?xf32> -> tensor<4xindex>
    %5 = "mhlo.dynamic_broadcast_in_dim"(%1, %4) {broadcast_dimensions = dense<3> : tensor<1xi64>} : (tensor<*xf32>, tensor<4xindex>) -> tensor<32x?x?x?xf32>
    %6 = mhlo.add %3, %5 : tensor<32x?x?x?xf32>
    %7 = shape.shape_of %6 : tensor<32x?x?x?xf32> -> tensor<4xindex>
    %8 = "mhlo.dynamic_broadcast_in_dim"(%0, %7) {broadcast_dimensions = dense<> : tensor<0xi64>} : (tensor<f32>, tensor<4xindex>) -> tensor<32x?x?x?xf32>
    %9 = mhlo.maximum %8, %6 : tensor<32x?x?x?xf32>
    return %9 : tensor<32x?x?x?xf32>
  }
}
```