diff --git a/source/opt/set_spec_constant_default_value_pass.cpp b/source/opt/set_spec_constant_default_value_pass.cpp index 2c62c240a..9ea304d53 100644 --- a/source/opt/set_spec_constant_default_value_pass.cpp +++ b/source/opt/set_spec_constant_default_value_pass.cpp @@ -15,6 +15,8 @@ #include "set_spec_constant_default_value_pass.h" #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include "util/parse_number.h" #include "def_use_manager.h" +#include "make_unique.h" #include "type_manager.h" #include "types.h" @@ -32,6 +35,7 @@ namespace opt { namespace { using spvutils::NumberType; using spvutils::EncodeNumberStatus; +using spvutils::ParseNumber; using spvutils::ParseAndEncodeNumber; // Given a numeric value in a null-terminated c string and the expected type of @@ -248,5 +252,58 @@ bool SetSpecConstantDefaultValuePass::Process(ir::Module* module) { return modified; } +// Returns true if the given char is ':', '\0' or considered as blank space +// (i.e.: '\n', '\r', '\v', '\t', '\f' and ' '). +bool IsSeparator(char ch) { + return std::strchr(":\0", ch) || std::isspace(ch) != 0; +} + +std::unique_ptr +SetSpecConstantDefaultValuePass::ParseDefaultValuesString(const char* str) { + if (!str) return nullptr; + + auto spec_id_to_value = MakeUnique(); + + // The parsing loop, break when points to the end. + while (*str) { + // Find the spec id. + while (std::isspace(*str)) str++; // skip leading spaces. + const char* entry_begin = str; + while (!IsSeparator(*str)) str++; + const char* entry_end = str; + std::string spec_id_str(entry_begin, entry_end - entry_begin); + uint32_t spec_id = 0; + if (!ParseNumber(spec_id_str.c_str(), &spec_id)) { + // The spec id is not a valid uint32 number. + return nullptr; + } + auto iter = spec_id_to_value->find(spec_id); + if (iter != spec_id_to_value->end()) { + // Same spec id has been defined before + return nullptr; + } + // Find the ':', spaces between the spec id and the ':' are not allowed. + if (*str++ != ':') { + // ':' not found + return nullptr; + } + // Find the value string + const char* val_begin = str; + while (!IsSeparator(*str)) str++; + const char* val_end = str; + if (val_end == val_begin) { + // Value string is empty. + return nullptr; + } + // Update the mapping with spec id and value string. + (*spec_id_to_value)[spec_id] = std::string(val_begin, val_end - val_begin); + + // Skip trailing spaces. + while (std::isspace(*str)) str++; + } + + return spec_id_to_value; +} + } // namespace opt } // namespace spvtools diff --git a/source/opt/set_spec_constant_default_value_pass.h b/source/opt/set_spec_constant_default_value_pass.h index 9ac545f15..0958f1942 100644 --- a/source/opt/set_spec_constant_default_value_pass.h +++ b/source/opt/set_spec_constant_default_value_pass.h @@ -43,6 +43,42 @@ class SetSpecConstantDefaultValuePass : public Pass { const char* name() const override { return "set-spec-const-default-value"; } bool Process(ir::Module*) override; + // Parses the given null-terminated C string to get a mapping from Spec Id to + // default value strings. Returns a unique pointer of the mapping from spec + // ids to spec constant default value strings built from the given |str| on + // success. Returns a nullptr if the given string is not valid for building + // the mapping. + // A valid string for building the mapping should follow the rule below: + // + // ": : ..." + // Example: + // "200:0x11 201:3.14 202:1.4728" + // + // Entries are separated with blank spaces (i.e.:' ', '\n', '\r', '\t', + // '\f', '\v'). Each entry corresponds to a Spec Id and default value pair. + // Multiple spaces between, before or after entries are allowed. However, + // spaces are not allowed within spec id or the default value string because + // spaces are always considered as delimiter to separate entries. + // + // In each entry, the spec id and value string is separated by ':'. Missing + // ':' in any entry is invalid. And it is invalid to have blank spaces in + // between the spec id and ':' or the default value and ':'. + // + // : specifies the spec id value. + // The text must represent a valid uint32_t number. + // Hex format with '0x' prefix is allowed. + // Empty is not allowed. + // One spec id value can only be defined once, multiple default values + // defined for the same spec id is not allowed. Spec ids with same value + // but different formats (e.g. 0x100 and 256) are considered the same. + // + // : the default value string. + // Spaces before and after default value text is allowed. + // Spaces within the text is not allowed. + // Empty is not allowed. + static std::unique_ptr ParseDefaultValuesString( + const char* str); + private: // The mapping from spec ids to their default values to be set. const SpecIdToValueStrMap spec_id_to_value_; diff --git a/test/opt/test_set_spec_const_default_value.cpp b/test/opt/test_set_spec_const_default_value.cpp index c0939016a..0d4a1d22a 100644 --- a/test/opt/test_set_spec_const_default_value.cpp +++ b/test/opt/test_set_spec_const_default_value.cpp @@ -14,12 +14,121 @@ #include "pass_fixture.h" +#include + namespace { using namespace spvtools; +using testing::Eq; + using SpecIdToValueStrMap = opt::SetSpecConstantDefaultValuePass::SpecIdToValueStrMap; +struct DefaultValuesStringParsingTestCase { + const char* default_values_str; + bool expect_success; + SpecIdToValueStrMap expected_map; +}; + +using DefaultValuesStringParsingTest = + ::testing::TestWithParam; + +TEST_P(DefaultValuesStringParsingTest, TestCase) { + const auto& tc = GetParam(); + auto actual_map = + opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString( + tc.default_values_str); + if (tc.expect_success) { + EXPECT_NE(nullptr, actual_map); + if (actual_map) EXPECT_THAT(*actual_map, Eq(tc.expected_map)); + } else { + EXPECT_EQ(nullptr, actual_map); + } +} + +INSTANTIATE_TEST_CASE_P( + ValidString, DefaultValuesStringParsingTest, + ::testing::ValuesIn(std::vector{ + // 0. empty map + {"", true, SpecIdToValueStrMap{}}, + // 1. one pair + {"100:1024", true, SpecIdToValueStrMap{{100, "1024"}}}, + // 2. two pairs + {"100:1024 200:2048", true, + SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}}, + // 3. spaces between entries + {"100:1024 \n \r \t \v \f 200:2048", true, + SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}}, + // 4. \t, \n, \r and spaces before spec id + {" \n \r\t \t \v \f 100:1024", true, + SpecIdToValueStrMap{{100, "1024"}}}, + // 5. \t, \n, \r and spaces after value string + {"100:1024 \n \r\t \t \v \f ", true, + SpecIdToValueStrMap{{100, "1024"}}}, + // 6. maximum spec id + {"4294967295:0", true, SpecIdToValueStrMap{{4294967295, "0"}}}, + // 7. minimum spec id + {"0:100", true, SpecIdToValueStrMap{{0, "100"}}}, + // 8. random content without spaces are allowed + {"200:random_stuff", true, SpecIdToValueStrMap{{200, "random_stuff"}}}, + // 9. support hex format spec id (just because we use the + // ParseNumber() utility) + {"0x100:1024", true, SpecIdToValueStrMap{{256, "1024"}}}, + // 10. multiple entries + {"101:1 102:2 103:3 104:4 200:201 9999:1000 0x100:333", true, + SpecIdToValueStrMap{{101, "1"}, + {102, "2"}, + {103, "3"}, + {104, "4"}, + {200, "201"}, + {9999, "1000"}, + {256, "333"}}}, + // 11. default value in hex float format + {"100:0x0.3p10", true, SpecIdToValueStrMap{{100, "0x0.3p10"}}}, + // 12. default value in decimal float format + {"100:1.5e-13", true, SpecIdToValueStrMap{{100, "1.5e-13"}}}, + })); + +INSTANTIATE_TEST_CASE_P( + InvalidString, DefaultValuesStringParsingTest, + ::testing::ValuesIn(std::vector{ + // 0. missing default value + {"100:", false, SpecIdToValueStrMap{}}, + // 1. spec id is not an integer + {"100.0:200", false, SpecIdToValueStrMap{}}, + // 2. spec id is not a number + {"something_not_a_number:1", false, SpecIdToValueStrMap{}}, + // 3. only spec id number + {"100", false, SpecIdToValueStrMap{}}, + // 4. same spec id defined multiple times + {"100:20 100:21", false, SpecIdToValueStrMap{}}, + // 5. Multiple definition of an identical spec id in different forms + // is not allowed + {"0x100:100 256:200", false, SpecIdToValueStrMap{}}, + // 6. empty spec id + {":3", false, SpecIdToValueStrMap{}}, + // 7. only colon + {":", false, SpecIdToValueStrMap{}}, + // 8. spec id overflow + {"4294967296:200", false, SpecIdToValueStrMap{}}, + // 9. spec id less than 0 + {"-1:200", false, SpecIdToValueStrMap{}}, + // 10. nullptr + {nullptr, false, SpecIdToValueStrMap{}}, + // 11. only a number is invalid + {"1234", false, SpecIdToValueStrMap{}}, + // 12. invalid entry separator + {"12:34;23:14", false, SpecIdToValueStrMap{}}, + // 13. invalid spec id and default value separator + {"12@34", false, SpecIdToValueStrMap{}}, + // 14. spaces before colon + {"100 :1024", false, SpecIdToValueStrMap{}}, + // 15. spaces after colon + {"100: 1024", false, SpecIdToValueStrMap{}}, + // 16. spec id represented in hex float format is invalid + {"0x3p10:200", false, SpecIdToValueStrMap{}}, + })); + struct SetSpecConstantDefaultValueTestCase { const char* code; SpecIdToValueStrMap default_values;