Assembler support for simple mask expressions

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.
This commit is contained in:
David Neto 2015-09-16 18:32:54 -04:00
parent 388c40d9c6
commit 36b0c0f6b3
6 changed files with 185 additions and 11 deletions

View File

@ -24,25 +24,24 @@
// 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.

View 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

View 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

View 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

View 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

View 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];