// 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;
} // 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,
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.
/// @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
using spvtest::MakeInstruction;
using spvtest::MakeVector;
using ::testing::Eq;
+using test_fixture::TextToBinaryTest;
// Test OpDecorate
#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
using spvtest::MakeInstruction;
using ::testing::Eq;
+using test_fixture::TextToBinaryTest;
// An example case for an enumerated value.
template <typename E>
#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
#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
using spvtest::MakeInstruction;
using ::testing::Eq;
+using test_fixture::TextToBinaryTest;;
// An example case for an enumerated value.
template <typename E>
#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
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];