Build "spec id->default val str" mapping from string
Add function `ParseDefaultValuesString()` to build the spec id->default
value string mapping required by `SetSpecConstantDefaultValuePass`.
diff --git a/source/opt/set_spec_constant_default_value_pass.cpp b/source/opt/set_spec_constant_default_value_pass.cpp
index 2c62c24..9ea304d 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 <cstring>
+#include <cctype>
+#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
@@ -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 {
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 @@
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::SpecIdToValueStrMap>
+SetSpecConstantDefaultValuePass::ParseDefaultValuesString(const char* str) {
+ if (!str) return nullptr;
+
+ auto spec_id_to_value = MakeUnique<SpecIdToValueStrMap>();
+
+ // 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 9ac545f..0958f19 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 @@
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:
+ //
+ // "<spec id A>:<default value for A> <spec id B>:<default value for B> ..."
+ // 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 ':'.
+ //
+ // <spec id>: specifies the spec id value.
+ // The text must represent a valid uint32_t number.
+ // Hex format with '0x' prefix is allowed.
+ // Empty <spec id> 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.
+ //
+ // <default value>: the default value string.
+ // Spaces before and after default value text is allowed.
+ // Spaces within the text is not allowed.
+ // Empty <default value> is not allowed.
+ static std::unique_ptr<SpecIdToValueStrMap> 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 c093901..0d4a1d2 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 <gmock/gmock.h>
+
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<DefaultValuesStringParsingTestCase>;
+
+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<DefaultValuesStringParsingTestCase>{
+ // 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<DefaultValuesStringParsingTestCase>{
+ // 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;