ChainUtil autogenerated validation for unpacking.

- The new generated functions only unpack the chains while verifying
  only expected extensions are on the chain.
- Further validations can be implemented on top of the tuples with
  templating if desired.

Bug: dawn:1955
Change-Id: Iec47072299dd4bb6d6cb093fdd7c211f8dbdb494
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/146361
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index 91c8ee7..1ebf88b 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -247,12 +247,14 @@
         if self.chained:
             assert self.chained == 'in' or self.chained == 'out'
             assert 'chain roots' in json_data
+            self.chain_roots = []
         if self.extensible:
             assert self.extensible == 'in' or self.extensible == 'out'
         # Chained structs inherit from wgpu::ChainedStruct, which has
         # nextInChain, so setting both extensible and chained would result in
         # two nextInChain members.
         assert not (self.extensible and self.chained)
+        self.extensions = []
 
     def update_metadata(self):
         Record.update_metadata(self)
@@ -384,6 +386,9 @@
 
 def link_structure(struct, types):
     struct.members = linked_record_members(struct.json_data['members'], types)
+    for root in struct.json_data.get('chain roots', []):
+        struct.chain_roots.append(types[root])
+        types[root].extensions.append(struct)
     struct.chain_roots = [types[root] for root in struct.json_data.get('chain roots', [])]
     assert all((root.category == 'structure' for root in struct.chain_roots))
 
diff --git a/generator/templates/dawn/native/ChainUtils.cpp b/generator/templates/dawn/native/ChainUtils.cpp
index 1600c41..1f2e6bf 100644
--- a/generator/templates/dawn/native/ChainUtils.cpp
+++ b/generator/templates/dawn/native/ChainUtils.cpp
@@ -18,7 +18,9 @@
 {% set native_dir = impl_dir + namespace_name.Dirs() %}
 #include "{{native_dir}}/ChainUtils_autogen.h"
 
+#include <tuple>
 #include <unordered_set>
+#include <utility>
 
 namespace {{native_namespace}} {
 
@@ -75,4 +77,56 @@
     return {};
 }
 
+//
+// Unpacked chain helpers.
+//
+{% for type in by_category["structure"] %}
+    {% if type.extensible == "in" %}
+        {% set unpackedChain = "Unpacked" + as_cppType(type.name) + "Chain" %}
+        ResultOrError<{{unpackedChain}}> ValidateAndUnpackChain(const {{as_cppType(type.name)}}* chain) {
+            const ChainedStruct* next = chain->nextInChain;
+            {{unpackedChain}} result;
+
+            //* Branching generation block to avoid warnings when the struct is not currently
+            //* extendable:
+            //*   -Wunreachable-code-loop-increment:
+            //*      error: loop will run at most once (loop increment never executed)
+            {% if len(type.extensions) == 0 %}
+                if (next != nullptr) {
+                    return DAWN_VALIDATION_ERROR(
+                        "Unexpected chained struct of type %s found on %s chain.",
+                        next->sType, "{{as_cppType(type.name)}}"
+                    );
+                }
+            {% else %}
+                for (; next != nullptr; next = next->nextInChain) {
+                    switch (next->sType) {
+                        {% for extension in type.extensions %}
+                            case STypeFor<{{as_cppType(extension.name)}}>: {
+                                auto& member = std::get<const {{as_cppType(extension.name)}}*>(result);
+                                if (member != nullptr) {
+                                    return DAWN_VALIDATION_ERROR(
+                                        "Duplicate chained struct of type %s found on %s chain.",
+                                        next->sType, "{{as_cppType(type.name)}}"
+                                    );
+                                } else {
+                                    member = static_cast<const {{as_cppType(extension.name)}}*>(next);
+                                }
+                                break;
+                            }
+                        {% endfor %}
+                        default:
+                          return DAWN_VALIDATION_ERROR(
+                              "Unexpected chained struct of type %s found on %s chain.",
+                              next->sType, "{{as_cppType(type.name)}}"
+                          );
+                    }
+                }
+            {% endif %}
+            return result;
+        }
+
+    {% endif %}
+{% endfor %}
+
 }  // namespace {{native_namespace}}
diff --git a/generator/templates/dawn/native/ChainUtils.h b/generator/templates/dawn/native/ChainUtils.h
index 112ed0c..d4a479e 100644
--- a/generator/templates/dawn/native/ChainUtils.h
+++ b/generator/templates/dawn/native/ChainUtils.h
@@ -23,8 +23,11 @@
 {% set native_namespace = namespace_name.namespace_case() %}
 {% set native_dir = impl_dir + namespace_name.Dirs() %}
 {% set prefix = metadata.proc_table_prefix.lower() %}
+#include <tuple>
+
 #include "{{native_dir}}/{{prefix}}_platform.h"
 #include "{{native_dir}}/Error.h"
+#include "{{native_dir}}/{{namespace}}_structs_autogen.h"
 
 namespace {{native_namespace}} {
 
@@ -35,7 +38,7 @@
     {% for value in types["s type"].values %}
         {% if value.valid and value.name.get() in types %}
             template <>
-            inline {{namespace}}::SType STypeFor<{{as_cppEnum(value.name)}}> = {{namespace}}::SType::{{as_cppEnum(value.name)}};
+            constexpr inline {{namespace}}::SType STypeFor<{{as_cppEnum(value.name)}}> = {{namespace}}::SType::{{as_cppEnum(value.name)}};
         {% endif %}
     {% endfor %}
 
@@ -139,6 +142,25 @@
         return ValidateSingleSTypeInner(chain, sType, sTypes...);
     }
 
+    //
+    // Unpacked chain types structs and helpers.
+    //   Note that unpacked types are tuples to enable further templating extensions based on
+    //   typing via something like std::get<const Extension*> in templated functions.
+    //
+    {% for type in by_category["structure"] %}
+        {% if type.extensible == "in" %}
+            {% set unpackedChain = "Unpacked" + as_cppType(type.name) + "Chain" %}
+            using {{unpackedChain}} = std::tuple<
+                {% for extension in type.extensions %}
+                    const {{as_cppType(extension.name)}}*{{ "," if not loop.last else "" }}
+                {% endfor %}
+            >;
+            ResultOrError<{{unpackedChain}}> ValidateAndUnpackChain(const {{as_cppType(type.name)}}* chain);
+
+        {% endif %}
+    {% endfor %}
+
+
 }  // namespace {{native_namespace}}
 
 #endif  // {{DIR}}_CHAIN_UTILS_H_