Add reader::spirv:SuggestSanitizedMemberName

Also reader::spirv::GetMemberName

Bug: tint:3
Change-Id: I4cf2dce0703eb17a9d49452294ed0c28ea158a07
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17423
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/namer.cc b/src/reader/spirv/namer.cc
index d2353b4..4353291 100644
--- a/src/reader/spirv/namer.cc
+++ b/src/reader/spirv/namer.cc
@@ -49,6 +49,19 @@
   return result;
 }
 
+std::string Namer::GetMemberName(uint32_t struct_id,
+                                 uint32_t member_index) const {
+  std::string result;
+  auto where = struct_member_names_.find(struct_id);
+  if (where != struct_member_names_.end()) {
+    auto& member_names = where->second;
+    if (member_index < member_names.size()) {
+      result = member_names[member_index];
+    }
+  }
+  return result;
+}
+
 std::string Namer::FindUnusedDerivedName(const std::string& base_name) const {
   // Ensure uniqueness among names.
   std::string derived_name;
@@ -85,6 +98,21 @@
   return SaveName(id, FindUnusedDerivedName(Sanitize(suggested_name)));
 }
 
+bool Namer::SuggestSanitizedMemberName(uint32_t struct_id,
+                                       uint32_t member_index,
+                                       const std::string& suggested_name) {
+  // Creates an empty vector the first time we visit this struct.
+  auto& name_vector = struct_member_names_[struct_id];
+  // Resizing will set new entries to the empty string.
+  name_vector.resize(std::max(name_vector.size(), size_t(member_index + 1)));
+  auto& entry = name_vector[member_index];
+  if (entry.empty()) {
+    entry = Sanitize(suggested_name);
+    return true;
+  }
+  return false;
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/namer.h b/src/reader/spirv/namer.h
index 3c9d455..d288216 100644
--- a/src/reader/spirv/namer.h
+++ b/src/reader/spirv/namer.h
@@ -18,6 +18,7 @@
 #include <cstdint>
 #include <string>
 #include <unordered_map>
+#include <vector>
 
 #include "src/reader/spirv/fail_stream.h"
 
@@ -59,10 +60,19 @@
 
   /// @param id the SPIR-V ID
   /// @returns the name for the ID. It must have been registered.
-  const std::string& GetName(uint32_t id) {
+  const std::string& GetName(uint32_t id) const {
     return id_to_name_.find(id)->second;
   }
 
+  /// Gets the registered name for a struct member. If no name has
+  /// been registered for this member, then returns the empty string.
+  /// member index is in bounds.
+  /// @param id the SPIR-V ID of the struct
+  /// @param member_index the index of the member, counting from 0
+  /// @returns the registered name for the ID, or an empty string if
+  /// nothing has been registered.
+  std::string GetMemberName(uint32_t id, uint32_t member_index) const;
+
   /// Returns an unregistered name based on a given base name.
   /// @param base_name the base name
   /// @returns a new name
@@ -76,12 +86,23 @@
   bool SaveName(uint32_t id, const std::string& name);
 
   /// Saves a sanitized name for the given ID, if that ID does not yet
-  /// have a registered name.
+  /// have a registered name, and if the sanitized name has not already
+  /// been registered to a different ID.
   /// @param id the SPIR-V ID
   /// @param suggested_name the suggested name
   /// @returns true if a name was newly registered for the ID
   bool SuggestSanitizedName(uint32_t id, const std::string& suggested_name);
 
+  /// Saves a sanitized name for a member of a struct, if that member
+  /// does not yet have a registered name.
+  /// @param id the SPIR-V ID for the struct
+  /// @param member_index the index of the member inside the struct
+  /// @param suggested_name the suggested name
+  /// @returns true if a name was newly registered
+  bool SuggestSanitizedMemberName(uint32_t id,
+                                  uint32_t member_index,
+                                  const std::string& suggested_name);
+
  private:
   FailStream fail_stream_;
 
@@ -89,6 +110,11 @@
   std::unordered_map<uint32_t, std::string> id_to_name_;
   // Maps a name to a SPIR-V ID, or 0 (the case for derived names).
   std::unordered_map<std::string, uint32_t> name_to_id_;
+
+  // Maps a struct id and member index to a suggested sanitized name.
+  // If entry k in the vector is an empty string, then a suggestion
+  // was recorded for a higher-numbered index, but not for index k.
+  std::unordered_map<uint32_t, std::vector<std::string>> struct_member_names_;
 };
 
 }  // namespace spirv
diff --git a/src/reader/spirv/namer_test.cc b/src/reader/spirv/namer_test.cc
index e744a9d..764863d 100644
--- a/src/reader/spirv/namer_test.cc
+++ b/src/reader/spirv/namer_test.cc
@@ -175,6 +175,47 @@
   EXPECT_THAT(namer.GetName(9), Eq("rice_1"));
 }
 
+TEST_F(SpvNamerTest, GetMemberName_EmptyStringForUnvisitedStruct) {
+  Namer namer(fail_stream_);
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq(""));
+}
+
+TEST_F(SpvNamerTest, GetMemberName_EmptyStringForUnvisitedMember) {
+  Namer namer(fail_stream_);
+  namer.SuggestSanitizedMemberName(1, 2, "mother");
+  EXPECT_THAT(namer.GetMemberName(1, 0), Eq(""));
+}
+
+TEST_F(SpvNamerTest, SuggestSanitizedMemberName_TakeSuggestionWhenNoConflict) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mother"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mother"));
+}
+
+TEST_F(SpvNamerTest, SuggestSanitizedMemberName_TakeSanitizedSuggestion) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "m:t%er"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("m_t_er"));
+}
+
+TEST_F(
+    SpvNamerTest,
+    SuggestSanitizedMemberName_TakeSuggestionWhenNoConflictAfterSuggestionForLowerMember) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 7, "mother"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq(""));
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mary"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mary"));
+}
+
+TEST_F(SpvNamerTest,
+       SuggestSanitizedMemberName_RejectSuggestionIfConflictOnMember) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(namer.SuggestSanitizedMemberName(1, 2, "mother"));
+  EXPECT_FALSE(namer.SuggestSanitizedMemberName(1, 2, "mary"));
+  EXPECT_THAT(namer.GetMemberName(1, 2), Eq("mother"));
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader