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

Code before:
                      gsl::span<const int32_t> data_types,
                      const logging::Logger& logger) {
  for (size_t i = 1; i < data_types.size(); i++) {
    if (data_types[0] != data_types[i]) {
      LOGS(logger, VERBOSE) << "[" << op_type
                            << "] data types should be the same, but ["
                            << data_types[0] << "] does not match "
                            << data_types[i] << "].";
      return false;
    }
  }
  return true;
}

bool IsSupportedDataType(const int32_t onnx_data_type, const emscripten::val& webnn_supported_data_types) {
  auto it = onnx_to_webnn_data_type_map.find(static_cast<ONNX_NAMESPACE::TensorProto_DataType>(onnx_data_type));
  if (it == onnx_to_webnn_data_type_map.end())
    return false;

  const std::string_view webnn_data_type = it->second;

  // Check if WebNN supports the data type.
  bool is_supported = webnn_supported_data_types.call<emscripten::val>("includes",
                                                                       emscripten::val(std::string(webnn_data_type)))
                          .as<bool>();

  if (webnn_data_type == "int64" &&
      !is_supported &&
      webnn_supported_data_types.call<emscripten::val>("includes", emscripten::val("int32")).as<bool>()) {
    // Current context doesn't support int64, but int32 is supported.
    // We can use int32 as a workaround.
    is_supported = true;
  }

  return is_supported;
}

// Check if the input or output data type of ONNX node is supported by the WebNN operator.
bool IsDataTypeSupportedByOp(const std::string_view onnx_op_type,
                             const int32_t onnx_data_type,
                             const emscripten::val& wnn_limits,
                             const std::string_view webnn_input_output_name,
                             const std::string_view onnx_input_output_name,
                             const logging::Logger& logger) {
  const std::string_view webnn_op_type = GetWebNNOpType(onnx_op_type);

  return !webnn_op_type.empty() &&
         IsDataTypeSupportedByWebNNOp(onnx_op_type, webnn_op_type, onnx_data_type, wnn_limits,
                                      webnn_input_output_name, onnx_input_output_name, logger);
}


Review comment (suggestion):
🤔 Reviewing @BruceDai's test CR recently, it made we wonder if it would be more useful for callers to have `constant` and `input` placed at the same level in the hierarchy as all the other operators. The `constant` operator is essentially a generator operator (kinda like the `fillSequence` that never made it into WebNN) which takes 0 inputs and yields 1 output. e.g.

```diff
dictionary MLOpSupportLimits
{
    MLInputOperandLayout preferredInputLayout;
    [EnforceRange] unsigned long long maxTensorByteLength;
-   MLTensorLimits input;
-   MLTensorLimits constant;
    MLTensorLimits output;
};

+ dictionary MLSingleOutputSupportLimits
+ {
+     MLTensorLimits output;
+ };

partial dictionary MLOpSupportLimits
{
+   MLSingleOutputSupportLimits input;
+   MLSingleOutputSupportLimits constant;
};
```

The oddity though is the `output` field, which doesn't fit nicely into this pattern, because there is no 'output' operator (and if there was, then it would have a single input, but the existing `MLSingleInputSupportLimits` actually has an input *and* an output, which would make it awkward). 🤷‍♂️

Anyway, thinking aloud - resolve me after reading.

Code after:
                      gsl::span<const int32_t> data_types,
                      const logging::Logger& logger) {
  for (size_t i = 1; i < data_types.size(); i++) {
    if (data_types[0] != data_types[i]) {
      LOGS(logger, VERBOSE) << "[" << op_type
                            << "] data types should be the same, but ["
                            << data_types[0] << "] does not match "
                            << data_types[i] << "].";
      return false;
    }
  }
  return true;
}

bool IsSupportedDataType(const int32_t onnx_data_type,
                         const emscripten::val& wnn_limits,
                         const std::string_view webnn_op_type,
                         const std::string_view webnn_input_output_name) {
  auto it = onnx_to_webnn_data_type_map.find(static_cast<ONNX_NAMESPACE::TensorProto_DataType>(onnx_data_type));
  if (it == onnx_to_webnn_data_type_map.end())
    return false;

  const std::string_view webnn_data_type = it->second;

  // MLOpSupportLimits has different structure. Certain WebNN ops have input and output name,
  // special cases like 'constant', 'input' and 'output' have no input or output name.
  emscripten::val webnn_supported_data_types =
      webnn_input_output_name.empty()
          ? wnn_limits[std::string(webnn_op_type)]["dataTypes"]
          : wnn_limits[std::string(webnn_op_type)][std::string(webnn_input_output_name)]["dataTypes"];

  if (webnn_supported_data_types.isUndefined()) {
    return false;
  }

  // Check if WebNN supports the data type.
  bool is_supported = webnn_supported_data_types.call<emscripten::val>("includes",
                                                                       emscripten::val(std::string(webnn_data_type)))
                          .as<bool>();

  if (webnn_data_type == "int64" &&
      !is_supported &&
      webnn_supported_data_types.call<emscripten::val>("includes", emscripten::val("int32")).as<bool>() &&
      !wnn_limits["constant"]["dataTypes"].call<emscripten::val>("includes", emscripten::val("int64")).as<bool>()) {
    // Current context doesn't support int64, but int32 is supported.
    // We can use int32 as a workaround.
    is_supported = true;
  }

  return is_supported;
}