/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/tools/versioning/op_version.h"

#include <algorithm>
#include <string>
#include <utility>
#include <vector>

#include "absl/memory/memory.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/schema/schema_utils.h"

namespace tflite {
namespace {

static const auto kTensorTypeNone = static_cast<::tflite::TensorType>(-1);

// Get the number of dimensions of a tensor with idx of an operator op.
inline int GetNumDims(const SubGraph* subgraph, const Operator* op, int idx) {
  return subgraph->tensors()->Get(op->inputs()->Get(idx))->shape()->size();
}

// Compare shape of two tensors with idx1 and idx2 of an operator op, return
// true if they have the same shape.
inline bool HaveSameShapes(const SubGraph* subgraph, const Operator* op,
                           int idx1, int idx2) {
  const flatbuffers::Vector<int32_t>* shape1 =
      subgraph->tensors()->Get(op->inputs()->Get(idx1))->shape();
  const flatbuffers::Vector<int32_t>* shape2 =
      subgraph->tensors()->Get(op->inputs()->Get(idx2))->shape();
  if (shape1->size() != shape2->size()) {
    return false;
  }
  return std::equal(shape1->begin(), shape1->end(), shape2->begin());
}
}  // namespace

int GetBuiltinOperatorVersion(const OpSignature& op_sig) {
  switch (op_sig.op) {
    case BuiltinOperator_CONV_2D:
      // If the op has signed int16 op_sig.inputs and op_sig.outputs, its
      // version 4.
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.input_types.at(1) == TensorType_INT16 &&
          op_sig.output_types.at(1) == TensorType_INT16) {
        return 4;
      }

      // If the op has signed int8 op_sig.inputs and op_sig.outputs, its
      // version 3.
      if (op_sig.input_types.at(0) == TensorType_INT8 &&
          op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_INT8) {
        return 3;
      }
      // If the op is a signed int8 hybrid operation, we need to return
      // version 2 or 5 if per channel.
      if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
          op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_FLOAT32) {
        if (op_sig.options.conv_2d.is_per_channel_quantized) {
          return 5;
        }
        return 2;
      }
      return 1;

    case BuiltinOperator_DEPTHWISE_CONV_2D:
      // If the op accepts int16, we return version 5.
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.input_types.at(1) == TensorType_INT16 &&
          op_sig.output_types.at(1) == TensorType_INT16) {
        return 5;
      }

      // If the op is a signed int8 hybrid operation, we need to return
      // version 4 or 6 if per-channel.
      if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
          op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_FLOAT32) {
        if (op_sig.options.depthwise_conv_2d.is_per_channel_quantized) {
          return 6;
        }
        return 4;
      }
      // If the op has signed int8 op_sig.inputs and op_sig.outputs, its
      // version 3.
      if (op_sig.input_types.at(0) == TensorType_INT8 &&
          op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_INT8) {
        return 3;
      }
      if (op_sig.options.depthwise_conv_2d.dilation_w_factor != 1 ||
          op_sig.options.depthwise_conv_2d.dilation_h_factor != 1) {
        return 2;
      }
      return 1;

    case BuiltinOperator_FAKE_QUANT:
      if (op_sig.options.fakequant.narrow_range) {
        return 2;
      }
      return 1;

    case BuiltinOperator_FULLY_CONNECTED:
      // +-----------------+--------------------+--------------------------+
      // |                 |    Weight::Default | Weight::Shuffled4x16Int8 |
      // +-----------------+--------------------+--------------------------+
      // | Float           |                  1 |                        2 |
      // | Quantized Uint8 |                  1 |                        2 |
      // | Hybrid          |                  3 |                        3 |
      // | Quantized Int8  |                  4 |                        4 |
      // +-----------------+--------------------+--------------------------+

      // FullyConnected with sparse weight is supported at version 8.
      if (op_sig.options.fully_connected.sparse_weight) {
        return 8;
      }

      // Int16 fully fixed point kernel is at version 7.
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.input_types.at(1) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        return 7;
      }

      // 2 op_sig.inputs (no bias) use case is supported starting from
      // version 6.
      if (op_sig.input_types.size() == 2) {
        return 6;
      }
      // `keep_num_dims` is supported at version 5.
      if (op_sig.options.fully_connected.keep_num_dims) {
        return 5;
      }
      // Int8 fully fixed point kernel is at version 4.
      if (op_sig.input_types.at(0) == TensorType_INT8 &&
          op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_INT8) {
        return 4;
      }
      // If the op is a signed int8 hybrid operation, we need to return
      // version 3.
      if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
          op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_FLOAT32) {
        if (op_sig.options.fully_connected.asymmetric_quantize_inputs) {
          // This is to use the updated quantization scheme.
          return 9;
        }
        return 3;
      }
      // For float and uint8 fixed point kernels, if the weight is
      // Shuffled4x16Int8, is is version 2.
      if (op_sig.options.fully_connected.weights_format ==
          FullyConnectedOptionsWeightsFormat_SHUFFLED4x16INT8) {
        return 2;
      }
      // Otherwise (weight is default), the version is 1.
      return 1;

    case BuiltinOperator_GATHER:
      if (op_sig.input_types.at(0) == TensorType_INT16) {
        return 4;
      }
      // If the op takes bool input, it is version 3.
      if (op_sig.input_types.at(0) == TensorType_BOOL) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_SVDF:
      // Fully integer SVDF has int8 as input and is of version 3.
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 3;
      }
      // If the op is a signed int8 hybrid operation, we need to return
      // version 2.
      if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
          op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_FLOAT32) {
        // This is to use the updated quantization scheme
        if (op_sig.options.input_quantization.asymmetric_quantize_inputs) {
          return 4;
        }
        return 2;
      }
      return 1;

    case BuiltinOperator_MUL:
      // Version 4 supports int16 inputs
      if (op_sig.input_types.at(0) == TensorType_INT16) {
        return 4;
      }
      // Version 3 supports have a rescale value greater than or equal to 1.
      if (op_sig.options.mul.input1_scale != 0 &&
          op_sig.options.mul.input2_scale != 0 &&
          op_sig.options.mul.output_scale != 0 &&
          (op_sig.options.mul.input1_scale * op_sig.options.mul.input2_scale /
           op_sig.options.mul.output_scale) >= 1.0) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_MAX_POOL_2D:
    case BuiltinOperator_AVERAGE_POOL_2D:
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        return 3;
      }

      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_TRANSPOSE:
      if (op_sig.input_types.at(0) == TensorType_INT16) {
        return 5;
      }
      if (op_sig.options.single_input_op.num_dims > 4) {
        return 4;
      }
      // If the op takes bool input, it is version 3.
      if (op_sig.input_types.at(0) == TensorType_BOOL) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_TRANSPOSE_CONV: {
      if (op_sig.input_types.size() == 4 &&
          op_sig.input_types.at(3) != kTensorTypeNone) {
        return 3;
      }
      // If the op takes int8 input, it is version 2.
      if (op_sig.input_types.at(1) == TensorType_INT8) {
        return 2;
      }
      return 1;
    }

    case BuiltinOperator_LSTM:
      // If the input tensor is float and a weight is int8, this is a version
      // 3 hybrid operation.
      if (op_sig.options.lstm.kernel_type == LSTMKernelType_FULL &&
          op_sig.input_types.at(0) == TensorType_FLOAT32 &&
          op_sig.input_types.at(2) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_FLOAT32) {
        if (op_sig.options.lstm.asymmetric_quantize_inputs) {
          return 4;
        }
        return 3;
      }
      // KERNEL_BASIC was added in version 2.
      if (op_sig.options.lstm.kernel_type == LSTMKernelType_BASIC) {
        return 2;
      }
      return 1;

    case BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_LSTM:
      // If the input tensor is float and a weight is int8, this is a version
      // 2 hybrid operation.
      if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
          op_sig.input_types.at(2) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_FLOAT32) {
        if (op_sig.options.lstm.asymmetric_quantize_inputs) {
          return 3;
        }
        return 2;
      }
      return 1;

    case BuiltinOperator_SPLIT:
      // If the op take in16 input, it is version 4.
      if (op_sig.input_types.at(1) == TensorType_INT16) {
        return 4;
      }
      // If the op take int8 input, it is version 2, for int32 it's version 3.
      // The input tensor is at index 1 not 0, 0 is the axis.
      if (op_sig.input_types.at(1) == TensorType_INT32) {
        return 3;
      }
      if (op_sig.input_types.at(1) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_SPARSE_TO_DENSE:
      // Version 3 supports Int8 and Uint8 type.
      if (op_sig.input_types.at(2) == TensorType_INT8 ||
          op_sig.input_types.at(2) == TensorType_UINT8) {
        return 3;
      }
      // Version 2 supports Int64 value type.
      if (op_sig.input_types.at(2) == TensorType_INT64) {
        return 2;
      }
      return 1;

    case BuiltinOperator_SLICE:
      if (op_sig.input_types.at(0) == TensorType_INT16) {
        return 4;
      }
      // Version 3 supports string input types.
      if (op_sig.input_types.at(0) == TensorType_STRING) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_UNPACK:
      // If the op take int8/uint8 input, it is version 2.
      if (op_sig.input_types.at(0) == TensorType_INT8 ||
          op_sig.input_types.at(0) == TensorType_UINT8) {
        return 2;
      }
      // If the op take bool input, it is version 3.
      if (op_sig.input_types.at(0) == TensorType_BOOL) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        return 4;
      }
      return 1;

    case BuiltinOperator_DEQUANTIZE:
      // Version 3 supports signed int16 input types.
      if (op_sig.input_types.at(0) == TensorType_INT16 ||
          op_sig.input_types.at(0) == TensorType_FLOAT16) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_FLOOR_DIV:
      if (op_sig.input_types.at(0) == TensorType_FLOAT32) {
        return 2;
      }
      return 1;

    case BuiltinOperator_L2_NORMALIZATION:
      if (op_sig.output_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_ABS:
    case BuiltinOperator_RELU:
      if (op_sig.input_types.at(0) == TensorType_INT8 ||
          op_sig.input_types.at(0) == TensorType_UINT8) {
        return 2;
      }
      return 1;
    case BuiltinOperator_STRIDED_SLICE:
      if (op_sig.options.single_input_op.num_dims > 4) {
        return 4;
      }
      // If the op takes bool input, it is version 3.
      if (op_sig.input_types.at(0) == TensorType_BOOL) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;
    case BuiltinOperator_REVERSE_V2:
      if (op_sig.input_types.at(0) == TensorType_BOOL) {
        return 2;
      }
      return 1;
    case BuiltinOperator_RESIZE_BILINEAR:
      if (op_sig.options.resize.half_pixel_centers) {
        return 3;
      } else if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;
    case BuiltinOperator_RESIZE_NEAREST_NEIGHBOR:
      if (op_sig.input_types.at(0) == TensorType_INT16) {
        return 4;
      } else if (op_sig.options.resize.half_pixel_centers ||
                 op_sig.options.resize.align_corners) {
        return 3;
      } else if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_MAXIMUM:
    case BuiltinOperator_MINIMUM:
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        return 4;
      }
      if (op_sig.options.broadcast.need_broadcast &&
          op_sig.options.broadcast.num_dims > 4) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_PACK:
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }

      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        return 3;
      }
      return 1;

    case BuiltinOperator_TILE:
      if (op_sig.input_types.at(0) == TensorType_STRING) {
        return 2;
      }

      return 1;

    case BuiltinOperator_SPACE_TO_BATCH_ND:
    case BuiltinOperator_BATCH_TO_SPACE_ND:
      if (op_sig.options.single_input_op.num_dims != 4) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_ADD:
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        if (!op_sig.options.addsub.pot_scale_int16) {
          return 3;
        }
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_SUB:
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        if (!op_sig.options.addsub.pot_scale_int16) {
          return 5;
        }
      }
      if (!op_sig.input_types.empty() &&
          op_sig.input_types.at(0) == TensorType_INT64) {
        return 4;
      }
      if (op_sig.options.addsub.need_broadcast &&
          op_sig.options.addsub.num_dims > 4) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_GATHER_ND:
      if (!op_sig.input_types.empty() &&
          op_sig.input_types.at(0) == TensorType_STRING) {
        return 2;
      }
      return 1;

    case BuiltinOperator_DIV:
      if (op_sig.options.broadcast.need_broadcast &&
          op_sig.options.broadcast.num_dims > 4) {
        return 2;
      }
      return 1;
    case BuiltinOperator_TANH:
    case BuiltinOperator_LOGISTIC:
      if (op_sig.input_types.at(0) == TensorType_INT16 &&
          op_sig.output_types.at(0) == TensorType_INT16) {
        return 3;
      }

      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_FILL:
      if (op_sig.input_types.size() >= 2 &&
          (op_sig.input_types.at(1) == TensorType_BOOL ||
           op_sig.input_types.at(1) == TensorType_STRING)) {
        return 2;
      }
      return 1;

    case BuiltinOperator_EQUAL:
    case BuiltinOperator_NOT_EQUAL:
      if (!op_sig.input_types.empty()) {
        if (op_sig.input_types.at(0) == TensorType_STRING) {
          return 3;
        }
        if (op_sig.input_types.at(0) == TensorType_INT8) {
          return 2;
        }
      }
      return 1;

    case BuiltinOperator_LEAKY_RELU:
      if (op_sig.input_types.at(0) == TensorType_INT16) {
        return 2;
      }
      return 1;

    case BuiltinOperator_CONCATENATION:
    case BuiltinOperator_BATCH_MATMUL:
    case BuiltinOperator_SOFTMAX:
    case BuiltinOperator_MEAN:
    case BuiltinOperator_PAD:
    case BuiltinOperator_PADV2:
      // In case of int16 inputs, the version is 3.
      if (op_sig.input_types.at(0) == TensorType_INT16) {
        return 3;
      }
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;

    case BuiltinOperator_RNN:
    case BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_RNN:
    case BuiltinOperator_BIDIRECTIONAL_SEQUENCE_RNN:
    case BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM:
      if (op_sig.input_types.at(1) == TensorType_INT8 &&
          op_sig.output_types.at(0) == TensorType_FLOAT32) {
        if (op_sig.options.input_quantization.asymmetric_quantize_inputs) {
          return 3;
        } else {
          return 2;
        }
      }
      return 1;

    case BuiltinOperator_SPACE_TO_DEPTH:
    case BuiltinOperator_SPLIT_V:
    case BuiltinOperator_SUM:
    case BuiltinOperator_REDUCE_MAX:
    case BuiltinOperator_REDUCE_MIN:
    case BuiltinOperator_RELU6:
    case BuiltinOperator_LOG_SOFTMAX:
    case BuiltinOperator_TOPK_V2:
    case BuiltinOperator_ARG_MAX:
    case BuiltinOperator_ARG_MIN:
    case BuiltinOperator_GREATER:
    case BuiltinOperator_GREATER_EQUAL:
    case BuiltinOperator_LESS:
    case BuiltinOperator_LESS_EQUAL:
    case BuiltinOperator_SELECT:
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;
    case BuiltinOperator_MIRROR_PAD:
      if (op_sig.input_types.at(0) == TensorType_INT8) {
        return 2;
      }
      return 1;
    default:
      return 1;
  }
}

TensorType GetTensorType(int32_t idx, const SubGraph* subgraph) {
  if (idx == -1)
    // For optional input/output, return none type directly.
    return kTensorTypeNone;

  // Some tests have a graph with invalid tensor index.
  TFLITE_DCHECK_GE(idx, 0);
  if (subgraph->tensors() && idx < subgraph->tensors()->Length()) {
    return subgraph->tensors()->Get(idx)->type();
  }
  LOG(ERROR) << "Can't access tenor " << idx;
  return kTensorTypeNone;
}

// Generate OpSignature with the given OperatorCode, Operator and Tensors (from
// SubGraph). The OpSignature will be used by GetBuiltinOperatorVersion() and
// mostly input and output tensor types are enough to figure out op version.
// But some ops (DEPTHWISE_CONV_2D,  FULLY_CONNECTED, ...) require to pass their
// options to decide op version.
OpSignature GetOpSignature(const OperatorCode* op_code, const Operator* op,
                           const SubGraph* subgraph) {
  auto builtin_code = GetBuiltinCode(op_code);
  OpSignature op_sig = {builtin_code};

  switch (builtin_code) {
    case BuiltinOperator_DEPTHWISE_CONV_2D: {
      auto conv_option = op->builtin_options_as_DepthwiseConv2DOptions();
      if (conv_option) {
        op_sig.options.depthwise_conv_2d.dilation_w_factor =
            conv_option->dilation_w_factor();
        op_sig.options.depthwise_conv_2d.dilation_h_factor =
            conv_option->dilation_h_factor();
      }
      const Tensor* filter_tensor =
          subgraph->tensors()->Get(op->inputs()->Get(1));
      const QuantizationParameters* filter_quant =
          filter_tensor->quantization();
      int num_channels = filter_tensor->shape()->Get(3);
      if (filter_quant && filter_quant->scale() &&
          filter_quant->scale()->Length() &&
          filter_quant->scale()->Length() == num_channels) {
        op_sig.options.depthwise_conv_2d.is_per_channel_quantized = true;
      }
    } break;

    case BuiltinOperator_FAKE_QUANT: {
      auto fakequant_option = op->builtin_options_as_FakeQuantOptions();
      if (fakequant_option) {
        op_sig.options.fakequant.narrow_range =
            fakequant_option->narrow_range();
      }
    } break;

    case BuiltinOperator_FULLY_CONNECTED: {
      auto fully_connected_option =
          op->builtin_options_as_FullyConnectedOptions();
      if (fully_connected_option) {
        op_sig.options.fully_connected.keep_num_dims =
            fully_connected_option->keep_num_dims();
        op_sig.options.fully_connected.weights_format =
            fully_connected_option->weights_format();
        op_sig.options.fully_connected.asymmetric_quantize_inputs =
            fully_connected_option->asymmetric_quantize_inputs();
      }

      const Tensor* weight_tensor =
          subgraph->tensors()->Get(op->inputs()->Get(1));
      op_sig.options.fully_connected.sparse_weight =
          (weight_tensor->sparsity() != nullptr);
    } break;

    case BuiltinOperator_MUL: {
      if (op->inputs()->Length() < 2 || op->outputs()->Length() < 1) {
        break;
      }
      const Tensor* input1_tensor =
          subgraph->tensors()->Get(op->inputs()->Get(0));
      const Tensor* input2_tensor =
          subgraph->tensors()->Get(op->inputs()->Get(1));
      const Tensor* output_tensor =
          subgraph->tensors()->Get(op->outputs()->Get(0));
      const QuantizationParameters* input1_quant =
          input1_tensor->quantization();
      const QuantizationParameters* input2_qunt = input2_tensor->quantization();
      const QuantizationParameters* output_quant =
          output_tensor->quantization();
      if (input1_quant && input1_quant->scale() &&
          input1_quant->scale()->Length() && input2_qunt &&
          input2_qunt->scale() && input2_qunt->scale()->Length() &&
          output_quant && output_quant->scale() &&
          output_quant->scale()->Length()) {
        op_sig.options.mul.input1_scale = input1_quant->scale()->Get(0);
        op_sig.options.mul.input2_scale = input2_qunt->scale()->Get(0);
        op_sig.options.mul.output_scale = output_quant->scale()->Get(0);
      }
    } break;

    case BuiltinOperator_ADD: {
      auto add_option = op->builtin_options_as_AddOptions();
      op_sig.options.addsub.pot_scale_int16 = true;
      if (add_option) {
        op_sig.options.addsub.pot_scale_int16 = add_option->pot_scale_int16();
      }
    } break;

    case BuiltinOperator_SUB: {
      auto sub_option = op->builtin_options_as_SubOptions();
      op_sig.options.addsub.need_broadcast =
          !HaveSameShapes(subgraph, op, 0, 1);
      op_sig.options.addsub.num_dims =
          std::max(GetNumDims(subgraph, op, 0), GetNumDims(subgraph, op, 1));
      op_sig.options.addsub.pot_scale_int16 = true;
      if (sub_option) {
        op_sig.options.addsub.pot_scale_int16 = sub_option->pot_scale_int16();
      }
    } break;

    case BuiltinOperator_LSTM: {
      auto lstm_option = op->builtin_options_as_LSTMOptions();
      if (lstm_option) {
        op_sig.options.lstm.kernel_type = lstm_option->kernel_type();
      }
    } break;

    case BuiltinOperator_RESIZE_BILINEAR: {
      auto resize_bilinear_option =
          op->builtin_options_as_ResizeBilinearOptions();
      if (resize_bilinear_option) {
        op_sig.options.resize.half_pixel_centers =
            resize_bilinear_option->half_pixel_centers();
        op_sig.options.resize.align_corners =
            resize_bilinear_option->align_corners();
      }
    } break;
    case BuiltinOperator_RESIZE_NEAREST_NEIGHBOR: {
      auto resize_nn_option =
          op->builtin_options_as_ResizeNearestNeighborOptions();
      if (resize_nn_option) {
        op_sig.options.resize.half_pixel_centers =
            resize_nn_option->half_pixel_centers();
        op_sig.options.resize.align_corners = resize_nn_option->align_corners();
      }
    } break;
    case BuiltinOperator_CONV_2D: {
      const Tensor* filter_tensor =
          subgraph->tensors()->Get(op->inputs()->Get(1));
      const QuantizationParameters* filter_quant =
          filter_tensor->quantization();
      int num_channels = filter_tensor->shape()->Get(0);
      if (filter_quant && filter_quant->scale() &&
          filter_quant->scale()->Length() &&
          filter_quant->scale()->Length() == num_channels) {
        op_sig.options.conv_2d.is_per_channel_quantized = true;
      }
    } break;
    // TODO(b/150176627): Add tests for GetOpSignature.
    case BuiltinOperator_STRIDED_SLICE:
    case BuiltinOperator_SPACE_TO_BATCH_ND:
    case BuiltinOperator_BATCH_TO_SPACE_ND:
    case BuiltinOperator_TRANSPOSE: {
      op_sig.options.single_input_op.num_dims = GetNumDims(subgraph, op, 0);
    } break;

    case BuiltinOperator_DIV:
    case BuiltinOperator_MAXIMUM:
    case BuiltinOperator_MINIMUM: {
      op_sig.options.broadcast.need_broadcast =
          !HaveSameShapes(subgraph, op, 0, 1);
      op_sig.options.broadcast.num_dims =
          std::max(GetNumDims(subgraph, op, 0), GetNumDims(subgraph, op, 1));
    } break;

    default:
      break;
  }

  for (int32_t i = 0; i < op->inputs()->Length(); ++i) {
    TensorType tensor_type = GetTensorType(op->inputs()->Get(i), subgraph);
    op_sig.input_types.push_back(tensor_type);
  }
  for (int32_t i = 0; i < op->outputs()->Length(); ++i) {
    TensorType tensor_type = GetTensorType(op->outputs()->Get(i), subgraph);
    op_sig.output_types.push_back(tensor_type);
  }
  return op_sig;
}

void UpdateOpVersion(uint8_t* model_buffer_pointer) {
  auto model = GetMutableModel(model_buffer_pointer);
  auto subgraphs = model->subgraphs();

  for (int i = 0; i < subgraphs->Length(); ++i) {
    const SubGraph* subgraph = subgraphs->Get(i);
    for (int j = 0; j < subgraph->operators()->Length(); ++j) {
      const Operator* op = subgraph->operators()->Get(j);
      OperatorCode* op_code =
          model->mutable_operator_codes()->GetMutableObject(op->opcode_index());

      auto builtin_code = GetBuiltinCode(op_code);
      if (builtin_code != BuiltinOperator_CUSTOM) {
        OpSignature op_sig = GetOpSignature(op_code, op, subgraph);
        // Update builtin operator version.
        int32_t op_ver = GetBuiltinOperatorVersion(op_sig);
        if (!op_code->mutate_version(op_ver)) {
          LOG(ERROR) << "Can't set operator "
                     << EnumNameBuiltinOperator(builtin_code) << " to version "
                     << op_ver;
        }
      }
    }
  }
}

}  // namespace tflite
