Repository: microsoft/onnxruntime
File: onnxruntime/core/providers/webnn/builders/impl/gemm_op_builder.cc (C++)

Code before:
class GemmOpBuilder : public BaseOpBuilder {
  // Add operator related.
 private:
  Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
                               const logging::Logger& logger) const override ORT_MUST_USE_RESULT;

  // Operator support related.
 private:
  bool IsOpSupportedImpl(const GraphViewer&, const Node& node,
                         const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
  bool HasSupportedInputsImpl(const GraphViewer&, const Node& node,
                              const emscripten::val& wnn_limits, const logging::Logger& logger) const override;
  bool HasSupportedOutputsImpl(const Node& node, const emscripten::val& wnn_limits,
                               const logging::Logger& logger) const override;
};

// Helper function
common::Status ProcessZeroPointAndScale(
    const std::vector<int64_t>& input_shape,
    const size_t zero_point_index,
    emscripten::val& zero_point,
    emscripten::val& scale,
    ModelBuilder& model_builder,
    const Node& node,
    const int32_t zero_point_type,
    const logging::Logger& logger) {
  ORT_RETURN_IF_NOT((zero_point_index == 2 || zero_point_index == 3), "zero_point_index should be 2 or 3.");
  // The WebNN dequantizeLinear op requires the scale and zero_point tensors to have the
  // same rank as the input tensor. So we need to reshape the zero_point tensors
  // to match the input rank.

  // Initially set target zero point shape to [1, 1, ..., 1].
  std::vector<uint32_t> target_zero_point_shape(input_shape.size(), 1);
  if (TensorExists(node.InputDefs(), zero_point_index)) {
    const auto& zero_point_arg = node.InputDefs()[zero_point_index];
    const auto zero_point_name = zero_point_arg->Name();
    zero_point = model_builder.GetOperand(zero_point_name);
    std::vector<int64_t> zero_point_shape;
    ORT_RETURN_IF_NOT(GetShape(*zero_point_arg, zero_point_shape, logger),
                      "Cannot get shape of zero_point: " + zero_point_name);

    // Assume zero_point shape has the same shape as input or is 1-D.
    // Adjust zero_point shape only if it's 1-D to match the input shape.
    const auto target_zero_point_rank = target_zero_point_shape.size();
    const auto zero_point_rank = zero_point_shape.size();
    if (zero_point_rank == 1 && zero_point_shape[0] != 1) {
      // For a_zero_point, its shape may be:
      // - [K] when the A input shape is [1, K], it should be reshaped to [1, K]
      // - [M] when the A input shape is [M, K], it should be reshaped to [M, 1]
      // For b_zero_point, its shape may be:
      // - [K] when the B input shape is [K, 1], it should be reshaped to [K, 1]

Review comment (suggestion):
```suggestion
    ModelBuilder& model_builder,
    const Node& node,
    int32_t zero_point_type,
    const logging::Logger& logger,
    emscripten::val& zero_point,
    emscripten::val& scale) {
```
Output parameters really should go last.

Code after:
class GemmOpBuilder : public BaseOpBuilder {
  // Add operator related.
 private:
  Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
                               const logging::Logger& logger) const override ORT_MUST_USE_RESULT;

  // Operator support related.
 private:
  bool IsOpSupportedImpl(const GraphViewer&, const Node& node,
                         const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
  bool HasSupportedInputsImpl(const GraphViewer&, const Node& node,
                              const emscripten::val& wnn_limits, const logging::Logger& logger) const override;
  bool HasSupportedOutputsImpl(const Node& node, const emscripten::val& wnn_limits,
                               const logging::Logger& logger) const override;
};

// Helper function
common::Status ProcessZeroPointAndScale(
    gsl::span<const int64_t> input_shape,
    const size_t zero_point_index,
    ModelBuilder& model_builder,
    const Node& node,
    const int32_t zero_point_type,
    const logging::Logger& logger,
    emscripten::val& zero_point,
    emscripten::val& scale) {
  ORT_RETURN_IF_NOT((zero_point_index == 2 || zero_point_index == 3), "zero_point_index should be 2 or 3.");
  // The WebNN dequantizeLinear op requires the scale and zero_point tensors to have the
  // same rank as the input tensor. So we need to reshape the zero_point tensors
  // to match the input rank.

  // Initially set target zero point shape to [1, 1, ..., 1].
  std::vector<uint32_t> target_zero_point_shape(input_shape.size(), 1);
  if (TensorExists(node.InputDefs(), zero_point_index)) {
    const auto& zero_point_arg = node.InputDefs()[zero_point_index];
    const auto zero_point_name = zero_point_arg->Name();
    zero_point = model_builder.GetOperand(zero_point_name);
    std::vector<int64_t> zero_point_shape;
    ORT_RETURN_IF_NOT(GetShape(*zero_point_arg, zero_point_shape, logger),
                      "Cannot get shape of zero_point: " + zero_point_name);

    // Assume zero_point shape has the same shape as input or is 1-D.
    // Adjust zero_point shape only if it's 1-D to match the input shape.
    const auto target_zero_point_rank = target_zero_point_shape.size();
    const auto zero_point_rank = zero_point_shape.size();
    if (zero_point_rank == 1 && zero_point_shape[0] != 1) {
      // For a_zero_point, its shape may be:
      // - [K] when the A input shape is [1, K], it should be reshaped to [1, K]
      // - [M] when the A input shape is [M, K], it should be reshaped to [M, 1]
      // For b_zero_point, its shape may be:
      // - [K] when the B input shape is [K, 1], it should be reshaped to [K, 1]