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

Code before:
      a_scale = model_builder.CreateOrGetConstant<float>(ONNX_NAMESPACE::TensorProto_DataType_FLOAT,
                                                         1.0f,
                                                         target_a_zero_point_shape);
    } else {
      // If a_zero_point is not provided, create default zero_point and scale inputs with the same rank as input A.
      a_zero_point = model_builder.CreateOrGetConstant<uint8_t>(a_type, 0, target_a_zero_point_shape);
      a_scale = model_builder.CreateOrGetConstant<float>(
          ONNX_NAMESPACE::TensorProto_DataType_FLOAT, 1.0f, target_a_zero_point_shape);
    }

    if (TensorExists(input_defs, 3)) {  // b_zero_point
      b_zero_point = model_builder.GetOperand(node.InputDefs()[3]->Name());
      std::vector<int64_t> b_zero_point_shape;
      ORT_RETURN_IF_NOT(GetShape(*input_defs[3], b_zero_point_shape, logger), "Cannot get shape of b_zero_point");

      // Adjust b_zero_point shape based on its rank and the rank of input B.
      if (b_zero_point_shape.size() == 1) {
        const uint32_t safe_b_zero_point = SafeInt<uint32_t>(b_zero_point_shape[0]);
        if (b_rank == 1) {
          // Input B is 1-D, reshaped to [K, 1]. Append 1 to b_zero_point shape.
          target_b_zero_point_shape[0] = safe_b_zero_point;
        } else {
          // Input B is at least 2-D. Prepend 1s to b_zero_point shape.
          target_b_zero_point_shape.back() = safe_b_zero_point;
        }
      }

      if (b_zero_point_shape.size() != target_b_zero_point_shape.size()) {
        // Need to reshape b_zero_point to the target shape.
        common_options.set("label", node.Name() + "_reshape_b_zero_point");
        b_zero_point = model_builder.GetBuilder().call<emscripten::val>(
            "reshape", b_zero_point, emscripten::val::array(target_b_zero_point_shape), common_options);
      } else {
        // b_zero_point will have the same rank as input B, no need to reshape it.
        // Set target_b_zero_point_shape to the actual shape of b_zero_point.
        target_b_zero_point_shape = GetNarrowedIntFromInt64<uint32_t>(b_zero_point_shape);
      }

      b_scale = model_builder.CreateOrGetConstant<float>(ONNX_NAMESPACE::TensorProto_DataType_FLOAT,
                                                         1.0f,
                                                         target_b_zero_point_shape);
    } else {
      b_zero_point = model_builder.CreateOrGetConstant<uint8_t>(a_type, 0, target_b_zero_point_shape);
      b_scale = model_builder.CreateOrGetConstant<float>(
          ONNX_NAMESPACE::TensorProto_DataType_FLOAT, 1.0f, target_b_zero_point_shape);
    }

    // Dequantize A to Float32
    common_options.set("label", node.Name() + "_dequantized_a");
    emscripten::val dequantized_a = model_builder.GetBuilder().call<emscripten::val>("dequantizeLinear",
                                                                                     a,

Review comment (security):
I've seen a few variants of this repeated above. Can we add a helper function that just expands the shape, kinda like the ORT DML EP's helper function that takes an input shape, new rank, and alignment (left or right). e.g.:

```
target_b_zero_point_shape = ExpandShape(b_zero_point_shape, target_rank, AxisAlignment::Right, /*offset*/ 0);
```

I'm not sure if it is possible to cleanly do this for all the cases above, but feels like it should be so for at least a few. These DML EP helpers aren't exactly what's needed, but they might be inspiring:

https://github.com/microsoft/onnxruntime/blob/b49e3b17f583a2f1a68a56c06b2365dc698e13f1/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.h#L101

https://github.com/microsoft/onnxruntime/blob/b49e3b17f583a2f1a68a56c06b2365dc698e13f1/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.cpp#L60

https://github.com/microsoft/onnxruntime/blob/b49e3b17f583a2f1a68a56c06b2365dc698e13f1/onnxruntime/core/providers/dml/OperatorAuthorHelper/Common.h#L51

Code after:
  // MatMul and MatMulInteger in ONNX allow 1-D inputs while matmul in WebNN only supports at least 2-D inputs.
  // We can support 1-D inputs by reshaping them to 2-D. We don't care Gemm here because it only provides 2-D inputs.
  const auto a_rank = a_shape.size();
  const auto b_rank = b_shape.size();
  const bool is_input_1d = (a_rank == 1 || b_rank == 1);

  // If the input A is 1-D, it is promoted to a matrix by prepending a 1 to its dimensions.
  if (a_rank == 1) {
    a_shape.insert(a_shape.begin(), 1);
    emscripten::val a_shape_arr = emscripten::val::array(GetNarrowedIntFromInt64<uint32_t>(a_shape));
    common_options.set("label", node.Name() + "_reshape_a");
    a = model_builder.GetBuilder().call<emscripten::val>("reshape", a, a_shape_arr, common_options);
  }
  // If the input B is 1-D, it is promoted to a matrix by appending a 1 to its dimensions.
  if (b_rank == 1) {
    b_shape.push_back(1);
    emscripten::val b_shape_arr = emscripten::val::array(GetNarrowedIntFromInt64<uint32_t>(b_shape));
    common_options.set("label", node.Name() + "_reshape_b");
    b = model_builder.GetBuilder().call<emscripten::val>("reshape", b, b_shape_arr, common_options);
  }

  if (op_type == "MatMul") {
    common_options.set("label", node.Name());
    output = model_builder.GetBuilder().call<emscripten::val>("matmul", a, b, common_options);
  } else if (op_type == "MatMulInteger") {
    // WebNN doesn't provide a dedicated op for MatMulInteger, it can be simply decomposed by
    // DequantizeLinear A, B -> MatMul -> Cast (to int32)
    int32_t a_type;
    ORT_RETURN_IF_NOT(GetType(*input_defs[0], a_type, logger), "Cannot get data type of input A");

    emscripten::val a_zero_point, b_zero_point, a_scale, b_scale;

    ORT_RETURN_IF_ERROR(
        ProcessZeroPointAndScale(a_shape, 2, model_builder, node, a_type, logger, a_zero_point, a_scale));
    ORT_RETURN_IF_ERROR(
        ProcessZeroPointAndScale(b_shape, 3, model_builder, node, a_type, logger, b_zero_point, b_scale));

    // Dequantize A to Float32
    common_options.set("label", node.Name() + "_dequantized_a");
    emscripten::val dequantized_a = model_builder.GetBuilder().call<emscripten::val>("dequantizeLinear",
                                                                                     a,
                                                                                     a_scale,
                                                                                     a_zero_point,
                                                                                     common_options);
    // Dequantize B to Float32
    common_options.set("label", node.Name() + "_dequantized_b");
    emscripten::val dequantized_b = model_builder.GetBuilder().call<emscripten::val>("dequantizeLinear",
                                                                                     b,
                                                                                     b_scale,
                                                                                     b_zero_point,
                                                                                     common_options);