Browse Source

[spirv] Translate boolean math operators: &&, ||, ?: (#499)

Lei Zhang 8 years ago
parent
commit
f38109c154

+ 17 - 0
docs/SPIR-V.rst

@@ -320,6 +320,23 @@ Comparison operators
 
 
 Note that for comparison of (vectors of) floats, SPIR-V has two sets of instructions: ``OpFOrd*``, ``OpFUnord*``. We translate into ``OpFOrd*`` ones.
 Note that for comparison of (vectors of) floats, SPIR-V has two sets of instructions: ``OpFOrd*``, ``OpFUnord*``. We translate into ``OpFOrd*`` ones.
 
 
+Boolean math operators
+++++++++++++++++++++++
+
+`Boolean match operators <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509631(v=vs.85).aspx#Boolean_Math_Operators>`_ (``&&``, ``||``, ``?:``) are translated into their corresponding SPIR-V opcodes according to the following table.
+
++--------+----------------------+
+|        | (Vector of) Booleans |
++--------+----------------------+
+| ``&&`` |  ``OpLogicalAnd``    |
++--------+----------------------+
+| ``||`` |  ``OpLogicalOr``     |
++--------+----------------------+
+| ``?:`` |  ``OpSelect``        |
++--------+----------------------+
+
+Please note that "unlike short-circuit evaluation of ``&&``, ``||``, and ``?:`` in C, HLSL expressions never short-circuit an evaluation because they are vector operations. All sides of the expression are always evaluated."
+
 Unary operators
 Unary operators
 +++++++++++++++
 +++++++++++++++
 
 

+ 5 - 0
tools/clang/include/clang/SPIRV/ModuleBuilder.h

@@ -133,6 +133,11 @@ public:
   uint32_t createBinaryOp(spv::Op op, uint32_t resultType, uint32_t lhs,
   uint32_t createBinaryOp(spv::Op op, uint32_t resultType, uint32_t lhs,
                           uint32_t rhs);
                           uint32_t rhs);
 
 
+  /// \brief Creates a select operation with the given values for true and false
+  /// cases and returns the <result-id> for the result.
+  uint32_t createSelect(uint32_t resultType, uint32_t condition,
+                        uint32_t trueValue, uint32_t falseValue);
+
   // \brief Creates an unconditional branch to the given target label.
   // \brief Creates an unconditional branch to the given target label.
   void createBranch(uint32_t targetLabel);
   void createBranch(uint32_t targetLabel);
 
 

+ 24 - 1
tools/clang/lib/SPIRV/EmitSPIRVAction.cpp

@@ -637,6 +637,10 @@ public:
       return doCallExpr(funcCall);
       return doCallExpr(funcCall);
     }
     }
 
 
+    if (const auto *condExpr = dyn_cast<ConditionalOperator>(expr)) {
+      return doConditionalOperator(condExpr);
+    }
+
     emitError("Expr '%0' is not supported yet.") << expr->getStmtClassName();
     emitError("Expr '%0' is not supported yet.") << expr->getStmtClassName();
     // TODO: handle other expressions
     // TODO: handle other expressions
     return 0;
     return 0;
@@ -683,7 +687,9 @@ public:
     case BO_Or:
     case BO_Or:
     case BO_Xor:
     case BO_Xor:
     case BO_Shl:
     case BO_Shl:
-    case BO_Shr: {
+    case BO_Shr:
+    case BO_LAnd:
+    case BO_LOr: {
       const spv::Op spvOp = translateOp(opcode, elemType);
       const spv::Op spvOp = translateOp(opcode, elemType);
       return theBuilder.createBinaryOp(spvOp, typeId, lhs, rhs);
       return theBuilder.createBinaryOp(spvOp, typeId, lhs, rhs);
     }
     }
@@ -1010,6 +1016,17 @@ public:
     return 0;
     return 0;
   }
   }
 
 
+  uint32_t doConditionalOperator(const ConditionalOperator *expr) {
+    // According to HLSL doc, all sides of the ?: expression are always
+    // evaluated.
+    const uint32_t type = typeTranslator.translateType(expr->getType());
+    const uint32_t condition = doExpr(expr->getCond());
+    const uint32_t trueBranch = doExpr(expr->getTrueExpr());
+    const uint32_t falseBranch = doExpr(expr->getFalseExpr());
+
+    return theBuilder.createSelect(type, condition, trueBranch, falseBranch);
+  }
+
   /// Translates a floatN * float multiplication into SPIR-V instructions and
   /// Translates a floatN * float multiplication into SPIR-V instructions and
   /// returns the <result-id>. Returns 0 if the given binary operation is not
   /// returns the <result-id>. Returns 0 if the given binary operation is not
   /// floatN * float.
   /// floatN * float.
@@ -1162,6 +1179,12 @@ case BO_##kind : {                                                             \
       BIN_OP_CASE_SINT_UINT(ShlAssign, ShiftLeftLogical, ShiftLeftLogical);
       BIN_OP_CASE_SINT_UINT(ShlAssign, ShiftLeftLogical, ShiftLeftLogical);
       BIN_OP_CASE_SINT_UINT(Shr, ShiftRightArithmetic, ShiftRightLogical);
       BIN_OP_CASE_SINT_UINT(Shr, ShiftRightArithmetic, ShiftRightLogical);
       BIN_OP_CASE_SINT_UINT(ShrAssign, ShiftRightArithmetic, ShiftRightLogical);
       BIN_OP_CASE_SINT_UINT(ShrAssign, ShiftRightArithmetic, ShiftRightLogical);
+    // According to HLSL doc, all sides of the && and || expression are always
+    // evaluated.
+    case BO_LAnd:
+      return spv::Op::OpLogicalAnd;
+    case BO_LOr:
+      return spv::Op::OpLogicalOr;
     default:
     default:
       break;
       break;
     }
     }

+ 12 - 3
tools/clang/lib/SPIRV/ModuleBuilder.cpp

@@ -181,6 +181,15 @@ uint32_t ModuleBuilder::createAccessChain(uint32_t resultType, uint32_t base,
   return id;
   return id;
 }
 }
 
 
+uint32_t ModuleBuilder::createUnaryOp(spv::Op op, uint32_t resultType,
+                                      uint32_t operand) {
+  assert(insertPoint && "null insert point");
+  const uint32_t id = theContext.takeNextId();
+  instBuilder.unaryOp(op, resultType, id, operand).x();
+  insertPoint->appendInstruction(std::move(constructSite));
+  return id;
+}
+
 uint32_t ModuleBuilder::createBinaryOp(spv::Op op, uint32_t resultType,
 uint32_t ModuleBuilder::createBinaryOp(spv::Op op, uint32_t resultType,
                                        uint32_t lhs, uint32_t rhs) {
                                        uint32_t lhs, uint32_t rhs) {
   assert(insertPoint && "null insert point");
   assert(insertPoint && "null insert point");
@@ -190,11 +199,11 @@ uint32_t ModuleBuilder::createBinaryOp(spv::Op op, uint32_t resultType,
   return id;
   return id;
 }
 }
 
 
-uint32_t ModuleBuilder::createUnaryOp(spv::Op op, uint32_t resultType,
-                                      uint32_t operand) {
+uint32_t ModuleBuilder::createSelect(uint32_t resultType, uint32_t condition,
+                                     uint32_t trueValue, uint32_t falseValue) {
   assert(insertPoint && "null insert point");
   assert(insertPoint && "null insert point");
   const uint32_t id = theContext.takeNextId();
   const uint32_t id = theContext.takeNextId();
-  instBuilder.unaryOp(op, resultType, id, operand).x();
+  instBuilder.opSelect(resultType, id, condition, trueValue, falseValue).x();
   insertPoint->appendInstruction(std::move(constructSite));
   insertPoint->appendInstruction(std::move(constructSite));
   return id;
   return id;
 }
 }

+ 27 - 0
tools/clang/test/CodeGenSPIRV/binary-op.logical-and.hlsl

@@ -0,0 +1,27 @@
+// Run: %dxc -T ps_6_0 -E main
+
+void main() {
+// CHECK-LABEL: %bb_entry = OpLabel
+
+    bool a, b, c;
+    // Plain assign (scalar)
+// CHECK:      [[a0:%\d+]] = OpLoad %bool %a
+// CHECK-NEXT: [[b0:%\d+]] = OpLoad %bool %b
+// CHECK-NEXT: [[and0:%\d+]] = OpLogicalAnd %bool [[a0]] [[b0]]
+// CHECK-NEXT: OpStore %c [[and0]]
+    c = a && b;
+
+    bool1 i, j, k;
+    bool3 o, p, q;
+    // Plain assign (vector)
+// CHECK-NEXT: [[i0:%\d+]] = OpLoad %bool %i
+// CHECK-NEXT: [[j0:%\d+]] = OpLoad %bool %j
+// CHECK-NEXT: [[and1:%\d+]] = OpLogicalAnd %bool [[i0]] [[j0]]
+// CHECK-NEXT: OpStore %k [[and1]]
+// CHECK-NEXT: [[o0:%\d+]] = OpLoad %v3bool %o
+// CHECK-NEXT: [[p0:%\d+]] = OpLoad %v3bool %p
+// CHECK-NEXT: [[and2:%\d+]] = OpLogicalAnd %v3bool [[o0]] [[p0]]
+// CHECK-NEXT: OpStore %q [[and2]]
+    k = i && j;
+    q = o && p;
+}

+ 27 - 0
tools/clang/test/CodeGenSPIRV/binary-op.logical-or.hlsl

@@ -0,0 +1,27 @@
+// Run: %dxc -T ps_6_0 -E main
+
+void main() {
+// CHECK-LABEL: %bb_entry = OpLabel
+
+    bool a, b, c;
+    // Plain assign (scalar)
+// CHECK:      [[a0:%\d+]] = OpLoad %bool %a
+// CHECK-NEXT: [[b0:%\d+]] = OpLoad %bool %b
+// CHECK-NEXT: [[or0:%\d+]] = OpLogicalOr %bool [[a0]] [[b0]]
+// CHECK-NEXT: OpStore %c [[or0]]
+    c = a || b;
+
+    bool1 i, j, k;
+    bool3 o, p, q;
+    // Plain assign (vector)
+// CHECK-NEXT: [[i0:%\d+]] = OpLoad %bool %i
+// CHECK-NEXT: [[j0:%\d+]] = OpLoad %bool %j
+// CHECK-NEXT: [[or1:%\d+]] = OpLogicalOr %bool [[i0]] [[j0]]
+// CHECK-NEXT: OpStore %k [[or1]]
+// CHECK-NEXT: [[o0:%\d+]] = OpLoad %v3bool %o
+// CHECK-NEXT: [[p0:%\d+]] = OpLoad %v3bool %p
+// CHECK-NEXT: [[or2:%\d+]] = OpLogicalOr %v3bool [[o0]] [[p0]]
+// CHECK-NEXT: OpStore %q [[or2]]
+    k = i || j;
+    q = o || p;
+}

+ 33 - 0
tools/clang/test/CodeGenSPIRV/cf.cond-op.hlsl

@@ -0,0 +1,33 @@
+// Run: %dxc -T ps_6_0 -E main
+
+// TODO: write to global variable
+bool fn() { return true; }
+bool fn1() { return false; }
+bool fn2() { return true; }
+
+void main() {
+// CHECK-LABEL: %bb_entry = OpLabel
+
+    // Use in control flow
+
+    bool a, b, c;
+    int val = 0;
+// CHECK:      [[a0:%\d+]] = OpLoad %bool %a
+// CHECK-NEXT: [[b0:%\d+]] = OpLoad %bool %b
+// CHECK-NEXT: [[c0:%\d+]] = OpLoad %bool %c
+// CHECK-NEXT: [[s0:%\d+]] = OpSelect %bool [[a0]] [[b0]] [[c0]]
+// CHECK-NEXT: OpSelectionMerge %if_merge None
+// CHECK-NEXT: OpBranchConditional [[s0]] %if_true %if_merge
+    if (a ? b : c) val++;
+
+    // Operand with side effects
+
+// CHECK-LABEL: %if_merge = OpLabel
+// CHECK-NEXT: [[fn:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[fn1:%\d+]] = OpFunctionCall %bool %fn1
+// CHECK-NEXT: [[fn2:%\d+]] = OpFunctionCall %bool %fn2
+// CHECK-NEXT: [[s1:%\d+]] = OpSelect %bool [[fn]] [[fn1]] [[fn2]]
+// CHECK-NEXT: OpSelectionMerge %if_merge_0 None
+// CHECK-NEXT: OpBranchConditional [[s1]] %if_true_0 %if_merge_0
+    if (fn() ? fn1() : fn2()) val++;
+}

+ 44 - 0
tools/clang/test/CodeGenSPIRV/cf.logical-and.hlsl

@@ -0,0 +1,44 @@
+// Run: %dxc -T ps_6_0 -E main
+
+// TODO: write to global variable
+bool fn() { return true; }
+
+void main() {
+// CHECK-LABEL: %bb_entry = OpLabel
+
+    // Use in control flow
+
+    bool a, b;
+    int val = 0;
+// CHECK:      [[a0:%\d+]] = OpLoad %bool %a
+// CHECK-NEXT: [[b0:%\d+]] = OpLoad %bool %b
+// CHECK-NEXT: [[and0:%\d+]] = OpLogicalAnd %bool [[a0]] [[b0]]
+// CHECK-NEXT: OpSelectionMerge %if_merge None
+// CHECK-NEXT: OpBranchConditional [[and0]] %if_true %if_merge
+    if (a && b) val++;
+
+    // Operand with side effects
+
+// CHECK-LABEL: %if_merge = OpLabel
+// CHECK-NEXT: [[fn0:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[fn1:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[and1:%\d+]] = OpLogicalAnd %bool [[fn0]] [[fn1]]
+// CHECK-NEXT: OpSelectionMerge %if_merge_0 None
+// CHECK-NEXT: OpBranchConditional [[and1]] %if_true_0 %if_merge_0
+    if (fn() && fn()) val++;
+// CHECK-LABEL: %if_merge_0 = OpLabel
+// CHECK-NEXT: [[a1:%\d+]] = OpLoad %bool %a
+// CHECK-NEXT: [[fn2:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[and2:%\d+]] = OpLogicalAnd %bool [[a1]] [[fn2]]
+// CHECK-NEXT: OpSelectionMerge %if_merge_1 None
+// CHECK-NEXT: OpBranchConditional [[and2]] %if_true_1 %if_merge_1
+    if (a && fn()) val++;
+// CHECK-LABEL: %if_merge_1 = OpLabel
+// CHECK-NEXT: [[fn3:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[b1:%\d+]] = OpLoad %bool %b
+// CHECK-NEXT: [[and3:%\d+]] = OpLogicalAnd %bool [[fn3]] [[b1]]
+// CHECK-NEXT: OpSelectionMerge %if_merge_2 None
+// CHECK-NEXT: OpBranchConditional [[and3]] %if_true_2 %if_merge_2
+    if (fn() && b) val++;
+}
+// CHECK: OpFunctionEnd

+ 44 - 0
tools/clang/test/CodeGenSPIRV/cf.logical-or.hlsl

@@ -0,0 +1,44 @@
+// Run: %dxc -T ps_6_0 -E main
+
+// TODO: write to global variable
+bool fn() { return true; }
+
+void main() {
+// CHECK-LABEL: %bb_entry = OpLabel
+
+    // Use in control flow
+
+    bool a, b;
+    int val = 0;
+// CHECK:      [[a0:%\d+]] = OpLoad %bool %a
+// CHECK-NEXT: [[b0:%\d+]] = OpLoad %bool %b
+// CHECK-NEXT: [[or0:%\d+]] = OpLogicalOr %bool [[a0]] [[b0]]
+// CHECK-NEXT: OpSelectionMerge %if_merge None
+// CHECK-NEXT: OpBranchConditional [[or0]] %if_true %if_merge
+    if (a || b) val++;
+
+    // Operand with side effects
+
+// CHECK-LABEL: %if_merge = OpLabel
+// CHECK-NEXT: [[fn0:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[fn1:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[or1:%\d+]] = OpLogicalOr %bool [[fn0]] [[fn1]]
+// CHECK-NEXT: OpSelectionMerge %if_merge_0 None
+// CHECK-NEXT: OpBranchConditional [[or1]] %if_true_0 %if_merge_0
+    if (fn() || fn()) val++;
+// CHECK-LABEL: %if_merge_0 = OpLabel
+// CHECK-NEXT: [[a1:%\d+]] = OpLoad %bool %a
+// CHECK-NEXT: [[fn2:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[or2:%\d+]] = OpLogicalOr %bool [[a1]] [[fn2]]
+// CHECK-NEXT: OpSelectionMerge %if_merge_1 None
+// CHECK-NEXT: OpBranchConditional [[or2]] %if_true_1 %if_merge_1
+    if (a || fn()) val++;
+// CHECK-LABEL: %if_merge_1 = OpLabel
+// CHECK-NEXT: [[fn3:%\d+]] = OpFunctionCall %bool %fn
+// CHECK-NEXT: [[b1:%\d+]] = OpLoad %bool %b
+// CHECK-NEXT: [[or3:%\d+]] = OpLogicalOr %bool [[fn3]] [[b1]]
+// CHECK-NEXT: OpSelectionMerge %if_merge_2 None
+// CHECK-NEXT: OpBranchConditional [[or3]] %if_true_2 %if_merge_2
+    if (fn() || b) val++;
+}
+// CHECK: OpFunctionEnd

+ 34 - 0
tools/clang/test/CodeGenSPIRV/ternary-op.cond-op.hlsl

@@ -0,0 +1,34 @@
+// Run: %dxc -T ps_6_0 -E main
+
+void main() {
+// CHECK-LABEL: %bb_entry = OpLabel
+
+    bool b0;
+    int m, n, o;
+    // Plain assign (scalar)
+// CHECK:      [[b0:%\d+]] = OpLoad %bool %b0
+// CHECK-NEXT: [[m0:%\d+]] = OpLoad %int %m
+// CHECK-NEXT: [[n0:%\d+]] = OpLoad %int %n
+// CHECK-NEXT: [[s0:%\d+]] = OpSelect %int [[b0]] [[m0]] [[n0]]
+// CHECK-NEXT: OpStore %o [[s0]]
+    o = b0 ? m : n;
+
+
+    bool1 b1;
+    bool3 b3;
+    uint1 p, q, r;
+    float3 x, y, z;
+    // Plain assign (vector)
+// CHECK-NEXT: [[b1:%\d+]] = OpLoad %bool %b1
+// CHECK-NEXT: [[p0:%\d+]] = OpLoad %uint %p
+// CHECK-NEXT: [[q0:%\d+]] = OpLoad %uint %q
+// CHECK-NEXT: [[s1:%\d+]] = OpSelect %uint [[b1]] [[p0]] [[q0]]
+// CHECK-NEXT: OpStore %r [[s1]]
+    r = b1 ? p : q;
+// CHECK-NEXT: [[b3:%\d+]] = OpLoad %v3bool %b3
+// CHECK-NEXT: [[x0:%\d+]] = OpLoad %v3float %x
+// CHECK-NEXT: [[y0:%\d+]] = OpLoad %v3float %y
+// CHECK-NEXT: [[s2:%\d+]] = OpSelect %v3float [[b3]] [[x0]] [[y0]]
+// CHECK-NEXT: OpStore %z [[s2]]
+    z = b3 ? x : y;
+}

+ 16 - 0
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -124,6 +124,19 @@ TEST_F(FileTest, BinaryOpMixedComparison) {
   runFileTest("binary-op.comparison.mixed.hlsl");
   runFileTest("binary-op.comparison.mixed.hlsl");
 }
 }
 
 
+// For logical binary operators
+TEST_F(FileTest, BinaryOpLogicalAnd) {
+  runFileTest("binary-op.logical-and.hlsl");
+}
+TEST_F(FileTest, BinaryOpLogicalOr) {
+  runFileTest("binary-op.logical-or.hlsl");
+}
+
+// For ternary operators
+TEST_F(FileTest, TernaryOpConditionalOp) {
+  runFileTest("ternary-op.cond-op.hlsl");
+}
+
 // For if statements
 // For if statements
 TEST_F(FileTest, IfStmtPlainAssign) { runFileTest("if-stmt.plain.hlsl"); }
 TEST_F(FileTest, IfStmtPlainAssign) { runFileTest("if-stmt.plain.hlsl"); }
 TEST_F(FileTest, IfStmtNestedIfStmt) { runFileTest("if-stmt.nested.hlsl"); }
 TEST_F(FileTest, IfStmtNestedIfStmt) { runFileTest("if-stmt.nested.hlsl"); }
@@ -134,6 +147,9 @@ TEST_F(FileTest, ForStmtNestedForStmt) { runFileTest("for-stmt.nested.hlsl"); }
 
 
 // For control flows
 // For control flows
 TEST_F(FileTest, ControlFlowNestedIfForStmt) { runFileTest("cf.if.for.hlsl"); }
 TEST_F(FileTest, ControlFlowNestedIfForStmt) { runFileTest("cf.if.for.hlsl"); }
+TEST_F(FileTest, ControlFlowLogicalAnd) { runFileTest("cf.logical-and.hlsl"); }
+TEST_F(FileTest, ControlFlowLogicalOr) { runFileTest("cf.logical-or.hlsl"); }
+TEST_F(FileTest, ControlFlowConditionalOp) { runFileTest("cf.cond-op.hlsl"); }
 
 
 // For function calls
 // For function calls
 TEST_F(FileTest, FunctionCall) { runFileTest("fn.call.hlsl"); }
 TEST_F(FileTest, FunctionCall) { runFileTest("fn.call.hlsl"); }