Assembler support for simple mask expressions
authorDavid Neto <dneto@google.com>
Wed, 16 Sep 2015 22:32:54 +0000 (18:32 -0400)
committerDavid Neto <dneto@google.com>
Mon, 26 Oct 2015 16:55:33 +0000 (12:55 -0400)
For example, support combining mask enums with "|",
such as "NotNaN|AllowRecip" for the fast math mode.

This is supported for mask values that don't modify the
expected operand pattern:
 - fast math mode
 - function control
 - loop control
 - selection control

TODO: disassembler support to print them as mask expressions.

source/text.cpp
source/text.h
test/TextToBinary.Annotation.cpp
test/TextToBinary.ControlFlow.cpp
test/TextToBinary.Function.cpp
test/TextToBinary.cpp

index 5776b1b..d44950a 100644 (file)
 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 // MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
 
+#include "text.h"
+
 #include <algorithm>
 #include <cassert>
 #include <cstdio>
 #include <cstdlib>
+#include <cstring>
 #include <string>
+#include <unordered_map>
+#include <vector>
 
-#include <libspirv/libspirv.h>
-
-#include "bitwisecast.h"
 #include "binary.h"
+#include "bitwisecast.h"
 #include "diagnostic.h"
 #include "ext_inst.h"
+#include <libspirv/libspirv.h>
 #include "opcode.h"
 #include "operand.h"
-#include "text.h"
-
-#include <string>
-#include <vector>
-#include <unordered_map>
 
 using spvutils::BitwiseCast;
 
@@ -375,6 +374,41 @@ bool isIdType(spv_operand_type_t type) {
 
 }  // anonymous namespace
 
+spv_result_t spvTextParseMaskOperand(const spv_operand_table operandTable,
+                                     const spv_operand_type_t type,
+                                     const char *textValue, uint32_t *pValue) {
+  if (textValue == nullptr) return SPV_ERROR_INVALID_TEXT;
+  size_t text_length = strlen(textValue);
+  if (text_length == 0) return SPV_ERROR_INVALID_TEXT;
+  const char *text_end = textValue + text_length;
+
+  // We only support mask expressions in ASCII, so the separator value is a
+  // char.
+  const char separator = '|';
+
+  // Accumulate the result by interpreting one word at a time, scanning
+  // from left to right.
+  uint32_t value = 0;
+  const char *begin = textValue;  // The left end of the current word.
+  const char *end = nullptr;  // One character past the end of the current word.
+  do {
+    end = std::find(begin, text_end, separator);
+
+    spv_operand_desc entry = nullptr;
+    if (spvOperandTableNameLookup(operandTable, type, begin, end - begin,
+                                  &entry)) {
+      return SPV_ERROR_INVALID_TEXT;
+    }
+    value |= entry->value;
+
+    // Advance to the next word by skipping over the separator.
+    begin = end + 1;
+  } while (end != text_end);
+
+  *pValue = value;
+  return SPV_SUCCESS;
+}
+
 spv_result_t spvTextEncodeOperand(
     const spv_operand_type_t type, const char *textValue,
     const spv_operand_table operandTable, const spv_ext_inst_table extInstTable,
@@ -535,6 +569,20 @@ spv_result_t spvTextEncodeOperand(
     case SPV_OPERAND_TYPE_OPTIONAL_IMAGE:
       assert(0 && " Handle optional optional image operands");
       break;
+    case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
+    case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
+    case SPV_OPERAND_TYPE_LOOP_CONTROL:
+    case SPV_OPERAND_TYPE_SELECTION_CONTROL: {
+      uint32_t value;
+      if (spvTextParseMaskOperand(operandTable, type, textValue, &value)) {
+        DIAGNOSTIC << "Invalid " << spvOperandTypeStr(type) << " '" << textValue
+                   << "'.";
+        return SPV_ERROR_INVALID_TEXT;
+      }
+      if (auto error = spvBinaryEncodeU32(value, pInst, position, pDiagnostic))
+        return error;
+      // TODO(dneto): So far, masks don't modify the expected operand pattern.
+    } break;
     default: {
       // NOTE: All non literal operands are handled here using the operand
       // table.
index f7b9240..320e785 100644 (file)
@@ -178,6 +178,24 @@ uint32_t spvNamedIdAssignOrGet(spv_named_id_table table, const char *textValue,
 /// @return zero on failure, non-zero otherwise
 int32_t spvTextIsNamedId(const char *textValue);
 
+/// @brief Parses a mask expression string for the given operand type.
+///
+/// A mask expression is a sequence of one or more terms separated by '|',
+/// where each term a named enum value for the given type.  No whitespace
+/// is permitted.
+///
+/// On success, the value is written to pValue.
+///
+/// @param[in] operandTable operand lookup table
+/// @param[in] type of the operand
+/// @param[in] textValue word of text to be parsed
+/// @param[out] pValue where the resulting value is written
+///
+/// @return result code
+spv_result_t spvTextParseMaskOperand(const spv_operand_table operandTable,
+                                     const spv_operand_type_t type,
+                                     const char *textValue, uint32_t *pValue);
+
 /// @brief Translate an Opcode operand to binary form
 ///
 /// @param[in] type of the operand
index 8087379..554dcb8 100644 (file)
@@ -39,6 +39,7 @@ namespace {
 using spvtest::MakeInstruction;
 using spvtest::MakeVector;
 using ::testing::Eq;
+using test_fixture::TextToBinaryTest;
 
 // Test OpDecorate
 
@@ -236,6 +237,19 @@ INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest,
 #undef CASE
 // clang-format on
 
+TEST_F(TextToBinaryTest, CombinedFPFastMathMask) {
+  // Sample a single combination.  This ensures we've integrated
+  // the instruction parsing logic with spvTextParseMask.
+  const std::string input = "OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ";
+  const uint32_t expected_enum = spv::DecorationFPFastMathMode;
+  const uint32_t expected_mask = spv::FPFastMathModeNotNaNMask |
+                                 spv::FPFastMathModeNotInfMask |
+                                 spv::FPFastMathModeNSZMask;
+  EXPECT_THAT(
+      CompiledInstructions(input),
+      Eq(MakeInstruction(spv::OpDecorate, {1, expected_enum, expected_mask})));
+}
+
 // Test OpDecorate Linkage
 
 // A single test case for a linkage
index ca0243e..4ff33b7 100644 (file)
@@ -36,6 +36,7 @@ namespace {
 
 using spvtest::MakeInstruction;
 using ::testing::Eq;
+using test_fixture::TextToBinaryTest;
 
 // An example case for an enumerated value.
 template <typename E>
@@ -67,7 +68,13 @@ INSTANTIATE_TEST_CASE_P(TextToBinarySelectionMerge, OpSelectionMergeTest,
 #undef CASE
 // clang-format on
 
-// TODO(dneto): Combination of selection control masks.
+TEST_F(OpSelectionMergeTest, CombinedSelectionControlMask) {
+  const std::string input = "OpSelectionMerge %1 Flatten|DontFlatten";
+  const uint32_t expected_mask =
+      spv::SelectionControlFlattenMask | spv::SelectionControlDontFlattenMask;
+  EXPECT_THAT(CompiledInstructions(input),
+              Eq(MakeInstruction(spv::OpSelectionMerge, {1, expected_mask})));
+}
 
 // Test OpLoopMerge
 
@@ -91,7 +98,13 @@ INSTANTIATE_TEST_CASE_P(TextToBinaryLoopMerge, OpLoopMergeTest,
 #undef CASE
 // clang-format on
 
-// TODO(dneto): Combination of loop control masks.
+TEST_F(OpLoopMergeTest, CombinedLoopControlMask) {
+  const std::string input = "OpLoopMerge %1 Unroll|DontUnroll";
+  const uint32_t expected_mask =
+      spv::LoopControlUnrollMask | spv::LoopControlDontUnrollMask;
+  EXPECT_THAT(CompiledInstructions(input),
+              Eq(MakeInstruction(spv::OpLoopMerge, {1, expected_mask})));
+}
 
 // TODO(dneto): OpPhi
 // TODO(dneto): OpLoopMerge
index 7deb2fb..cebbf19 100644 (file)
@@ -36,6 +36,7 @@ namespace {
 
 using spvtest::MakeInstruction;
 using ::testing::Eq;
+using test_fixture::TextToBinaryTest;;
 
 // An example case for an enumerated value.
 template <typename E>
@@ -70,7 +71,17 @@ INSTANTIATE_TEST_CASE_P(TextToBinaryFunctionTest, OpFunctionControlTest,
 #undef CASE
 // clang-format on
 
-// TODO(dneto): Combination of function control masks.
+TEST_F(TextToBinaryTest, CombinedFunctionControlMask) {
+  // Sample a single combination.  This ensures we've integrated
+  // the instruction parsing logic with spvTextParseMask.
+  const std::string input =
+      "%result_id = OpFunction %result_type Inline|Pure|Const %function_type";
+  const uint32_t expected_mask = spv::FunctionControlInlineMask |
+                                 spv::FunctionControlPureMask |
+                                 spv::FunctionControlConstMask;
+  EXPECT_THAT(CompiledInstructions(input),
+              Eq(MakeInstruction(spv::OpFunction, {1, 2, expected_mask, 3})));
+}
 
 // TODO(dneto): OpFunctionParameter
 // TODO(dneto): OpFunctionEnd
index 48ed58e..cbad557 100644 (file)
@@ -51,6 +51,76 @@ TEST(GetWord, Simple) {
   EXPECT_EQ("abc", spvGetWord("abc\n"));
 }
 
+// An mask parsing test case.
+struct MaskCase {
+  const spv_operand_type_t which_enum;
+  const uint32_t expected_value;
+  const char* expression;
+};
+
+using GoodMaskParseTest = ::testing::TestWithParam<MaskCase>;
+
+TEST_P(GoodMaskParseTest, GoodMaskExpressions) {
+  spv_operand_table operandTable;
+  ASSERT_EQ(SPV_SUCCESS, spvOperandTableGet(&operandTable));
+
+  uint32_t value;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvTextParseMaskOperand(operandTable, GetParam().which_enum,
+                                    GetParam().expression, &value));
+  EXPECT_EQ(GetParam().expected_value, value);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    ParseMask, GoodMaskParseTest,
+    ::testing::ValuesIn(std::vector<MaskCase>{
+        {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 0, "None"},
+        {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 1, "NotNaN"},
+        {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 2, "NotInf"},
+        {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotNaN|NotInf"},
+        // Mask experssions are symmetric.
+        {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotInf|NotNaN"},
+        // Repeating a value has no effect.
+        {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotInf|NotNaN|NotInf"},
+        // Using 3 operands still works.
+        {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 0x13, "NotInf|NotNaN|Fast"},
+        {SPV_OPERAND_TYPE_SELECTION_CONTROL, 0, "None"},
+        {SPV_OPERAND_TYPE_SELECTION_CONTROL, 1, "Flatten"},
+        {SPV_OPERAND_TYPE_SELECTION_CONTROL, 2, "DontFlatten"},
+        // Weirdly, you can specify to flatten and don't flatten a selection.
+        {SPV_OPERAND_TYPE_SELECTION_CONTROL, 3, "Flatten|DontFlatten"},
+        {SPV_OPERAND_TYPE_LOOP_CONTROL, 0, "None"},
+        {SPV_OPERAND_TYPE_LOOP_CONTROL, 1, "Unroll"},
+        {SPV_OPERAND_TYPE_LOOP_CONTROL, 2, "DontUnroll"},
+        // Weirdly, you can specify to unroll and don't unroll a loop.
+        {SPV_OPERAND_TYPE_LOOP_CONTROL, 3, "Unroll|DontUnroll"},
+        {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 0, "None"},
+        {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 1, "Inline"},
+        {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 2, "DontInline"},
+        {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 4, "Pure"},
+        {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 8, "Const"},
+        {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 0xd, "Inline|Const|Pure"},
+    }));
+
+using BadFPFastMathMaskParseTest = ::testing::TestWithParam<const char*>;
+
+TEST_P(BadFPFastMathMaskParseTest, BadMaskExpressions) {
+  spv_operand_table operandTable;
+  ASSERT_EQ(SPV_SUCCESS, spvOperandTableGet(&operandTable));
+
+  uint32_t value;
+  EXPECT_NE(SPV_SUCCESS, spvTextParseMaskOperand(operandTable,
+                                             SPV_OPERAND_TYPE_FP_FAST_MATH_MODE,
+                                             GetParam(), &value));
+}
+
+INSTANTIATE_TEST_CASE_P(ParseMask, BadFPFastMathMaskParseTest,
+                        ::testing::ValuesIn(std::vector<const char*>{
+                            nullptr, "", "NotValidEnum", "|", "NotInf|",
+                            "|NotInf", "NotInf||NotNaN",
+                            "Unroll"  // A good word, but for the wrong enum
+                        }));
+
 // TODO(dneto): Aliasing like this relies on undefined behaviour. Fix this.
 union char_word_t {
   char cs[4];