[spirv-reader] Unordered float compares

Unordered float compares are not supported directly by WGSL.
Translate them as negated ordered compares.

Bug: tint:3
Change-Id: I4fea7c924054cffc9a39a8be3b3d9f088d302114
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/21540
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 9889e18..7140a0a 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -232,6 +232,31 @@
   return ast::BinaryOp::kNone;
 }
 
+// If the given SPIR-V opcode is a floating point unordered comparison,
+// then returns the binary float comparison for which it is the negation.
+// Othewrise returns BinaryOp::kNone.
+// @param opcode SPIR-V opcode
+// @returns operation corresponding to negated version of the SPIR-V opcode
+ast::BinaryOp NegatedFloatCompare(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpFUnordEqual:
+      return ast::BinaryOp::kNotEqual;
+    case SpvOpFUnordNotEqual:
+      return ast::BinaryOp::kEqual;
+    case SpvOpFUnordLessThan:
+      return ast::BinaryOp::kGreaterThanEqual;
+    case SpvOpFUnordLessThanEqual:
+      return ast::BinaryOp::kGreaterThan;
+    case SpvOpFUnordGreaterThan:
+      return ast::BinaryOp::kLessThanEqual;
+    case SpvOpFUnordGreaterThanEqual:
+      return ast::BinaryOp::kLessThan;
+    default:
+      break;
+  }
+  return ast::BinaryOp::kNone;
+}
+
 // @returns the merge block ID for the given basic block, or 0 if there is none.
 uint32_t MergeFor(const spvtools::opt::BasicBlock& bb) {
   // Get the OpSelectionMerge or OpLoopMerge instruction, if any.
@@ -1502,6 +1527,17 @@
             std::make_unique<ast::AsExpression>(target_ty, operand(0).expr)};
   }
 
+  auto negated_op = NegatedFloatCompare(inst.opcode());
+  if (negated_op != ast::BinaryOp::kNone) {
+    auto arg0 = operand(0);
+    auto arg1 = operand(1);
+    auto binary_expr = std::make_unique<ast::BinaryExpression>(
+        negated_op, std::move(arg0.expr), std::move(arg1.expr));
+    auto negated_expr = std::make_unique<ast::UnaryOpExpression>(
+        ast::UnaryOp::kNot, std::move(binary_expr));
+    return {ast_type, std::move(negated_expr)};
+  }
+
   // builtin readonly function
   // glsl.std.450 readonly function
 
diff --git a/src/reader/spirv/function_logical_test.cc b/src/reader/spirv/function_logical_test.cc
index 040a599..4a0f1df 100644
--- a/src/reader/spirv/function_logical_test.cc
+++ b/src/reader/spirv/function_logical_test.cc
@@ -682,21 +682,434 @@
                    "__vec_2__bool", AstFor("v2int_30_40"), "less_than_equal",
                    AstFor("cast_v2uint_20_10")}));
 
+using SpvFUnordTest = SpvParserTestBase<::testing::Test>;
+
+TEST_F(SpvFUnordTest, FUnordEqual_Scalar) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          ScalarConstructor{50.000000}
+          not_equal
+          ScalarConstructor{60.000000}
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordEqual_Vector) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordEqual %bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{50.000000}
+            ScalarConstructor{60.000000}
+          }
+          not_equal
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{60.000000}
+            ScalarConstructor{50.000000}
+          }
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordNotEqual_Scalar) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordNotEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          ScalarConstructor{50.000000}
+          equal
+          ScalarConstructor{60.000000}
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordNotEqual_Vector) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordNotEqual %bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{50.000000}
+            ScalarConstructor{60.000000}
+          }
+          equal
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{60.000000}
+            ScalarConstructor{50.000000}
+          }
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThan_Scalar) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThan %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          ScalarConstructor{50.000000}
+          greater_than_equal
+          ScalarConstructor{60.000000}
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThan_Vector) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThan %bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{50.000000}
+            ScalarConstructor{60.000000}
+          }
+          greater_than_equal
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{60.000000}
+            ScalarConstructor{50.000000}
+          }
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThanEqual_Scalar) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThanEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          ScalarConstructor{50.000000}
+          greater_than
+          ScalarConstructor{60.000000}
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordLessThanEqual_Vector) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordLessThanEqual %bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{50.000000}
+            ScalarConstructor{60.000000}
+          }
+          greater_than
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{60.000000}
+            ScalarConstructor{50.000000}
+          }
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThan_Scalar) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThan %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          ScalarConstructor{50.000000}
+          less_than_equal
+          ScalarConstructor{60.000000}
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThan_Vector) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThan %bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{50.000000}
+            ScalarConstructor{60.000000}
+          }
+          less_than_equal
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{60.000000}
+            ScalarConstructor{50.000000}
+          }
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Scalar) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThanEqual %bool %float_50 %float_60
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          ScalarConstructor{50.000000}
+          less_than
+          ScalarConstructor{60.000000}
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
+TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Vector) {
+  const auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpFUnordGreaterThanEqual %bool %v2float_50_60 %v2float_60_50
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"(
+  Variable{
+    x_1
+    none
+    __bool
+    {
+      UnaryOp{
+        not
+        Binary{
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{50.000000}
+            ScalarConstructor{60.000000}
+          }
+          less_than
+          TypeConstructor{
+            __vec_2__f32
+            ScalarConstructor{60.000000}
+            ScalarConstructor{50.000000}
+          }
+        }
+      }
+    }
+  })"))
+      << ToString(fe.ast_body());
+}
+
 // TODO(dneto): OpAny  - likely builtin function TBD
 // TODO(dneto): OpAll  - likely builtin function TBD
 // TODO(dneto): OpIsNan - likely builtin function TBD
 // TODO(dneto): OpIsInf - likely builtin function TBD
 // TODO(dneto): Kernel-guarded instructions.
 // TODO(dneto): OpSelect - likely builtin function TBD
-//
-// Unordered float inequalities: blocked pending the resolution of
-// https://github.com/gpuweb/gpuweb/issues/706
-// TODO(dneto): OpFUnordEqual
-// TODO(dneto): OpFUnordNotEqual
-// TODO(dneto): OpFUnordLessThan
-// TODO(dneto): OpFUnordGreaterThan
-// TODO(dneto): OpFUnordLessThanEqual
-// TODO(dneto): OpFUnordGreaterThanEqual
 
 }  // namespace
 }  // namespace spirv