tint: Implement uniformity analaysis
This implements the uniformity analysis as currently described in the
WGSL specification. Uniformity issues are presented as warnings, and
will be switched to errors in a future release.
A follow-up patch will improve the error messages, which currently
just show the point at which a uniformity was detected.
In a future release, once we have obtained initial feedback from
users, uniformity issues will become errors.
Bug: tint:880
Change-Id: I7d0b3080932c786c5d50b55720fec6d19f00d356
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/88368
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Alan Baker <alanbaker@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/docs/tint/origin-trial-changes.md b/docs/tint/origin-trial-changes.md
index 31f91ce..3876816 100644
--- a/docs/tint/origin-trial-changes.md
+++ b/docs/tint/origin-trial-changes.md
@@ -1,5 +1,12 @@
# Tint changes during Origin Trial
+## Changes for M103
+
+### New features
+
+* Produce warnings for when calling barriers, textureSample, and derivative
+builtins in non-uniform control flow [tint:880](crbug.com/tint/880)
+
## Changes for M102
### New Features
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 843f7fd..4a22a67 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -375,6 +375,8 @@
"resolver/resolver_constants.cc",
"resolver/sem_helper.cc",
"resolver/sem_helper.h",
+ "resolver/uniformity.cc",
+ "resolver/uniformity.h",
"resolver/validator.cc",
"resolver/validator.h",
"scope_stack.h",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 9635bef..d2273fd 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -256,6 +256,8 @@
resolver/resolver.h
resolver/sem_helper.cc
resolver/sem_helper.h
+ resolver/uniformity.cc
+ resolver/uniformity.h
resolver/validator.cc
resolver/validator.h
scope_stack.h
@@ -833,6 +835,13 @@
writer/text_generator_test.cc
)
+ # Uniformity analysis tests depend on WGSL reader
+ if(${TINT_BUILD_WGSL_READER})
+ list(APPEND TINT_TEST_SRCS
+ resolver/uniformity_test.cc
+ )
+ endif()
+
# Inspector tests depend on WGSL reader
if(${TINT_BUILD_WGSL_READER})
list(APPEND TINT_TEST_SRCS
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index fc7a34d..962dd02 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -50,6 +50,7 @@
#include "src/tint/ast/variable_decl_statement.h"
#include "src/tint/ast/vector.h"
#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/resolver/uniformity.h"
#include "src/tint/sem/array.h"
#include "src/tint/sem/atomic.h"
#include "src/tint/sem/call.h"
@@ -145,6 +146,10 @@
return false;
}
+ if (!AnalyzeUniformity(builder_, dependencies_)) {
+ // TODO(jrprice): Reject programs that fail uniformity analysis.
+ }
+
bool result = true;
for (auto* node : builder_->ASTNodes().Objects()) {
if (marked_.count(node) == 0) {
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
new file mode 100644
index 0000000..381a6da
--- /dev/null
+++ b/src/tint/resolver/uniformity.cc
@@ -0,0 +1,1275 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/resolver/uniformity.h"
+
+#include <limits>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/resolver/dependency_graph.h"
+#include "src/tint/scope_stack.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/if_statement.h"
+#include "src/tint/sem/info.h"
+#include "src/tint/sem/loop_statement.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/switch_statement.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/utils/block_allocator.h"
+#include "src/tint/utils/map.h"
+#include "src/tint/utils/unique_vector.h"
+
+// Set to `1` to dump the uniformity graph for each function in graphviz format.
+#define TINT_DUMP_UNIFORMITY_GRAPH 0
+
+namespace tint::resolver {
+
+namespace {
+
+/// CallSiteTag describes the uniformity requirements on the call sites of a function.
+enum CallSiteTag {
+ CallSiteRequiredToBeUniform,
+ CallSiteNoRestriction,
+};
+
+/// FunctionTag describes a functions effects on uniformity.
+enum FunctionTag {
+ SubsequentControlFlowMayBeNonUniform,
+ ReturnValueMayBeNonUniform,
+ NoRestriction,
+};
+
+/// ParameterTag describes the uniformity requirements of values passed to a function parameter.
+enum ParameterTag {
+ ParameterRequiredToBeUniform,
+ ParameterRequiredToBeUniformForSubsequentControlFlow,
+ ParameterRequiredToBeUniformForReturnValue,
+ ParameterNoRestriction,
+};
+
+/// ParameterInfo holds information about the uniformity requirements and effects for a particular
+/// function parameter.
+struct ParameterInfo {
+ /// The parameter's uniformity requirements.
+ ParameterTag tag = ParameterNoRestriction;
+ /// Will be `true` if this function may cause the contents of this pointer parameter to become
+ /// non-uniform.
+ bool pointer_may_become_non_uniform = false;
+ /// The parameters that are required to be uniform for the contents of this pointer parameter to
+ /// be uniform at function exit.
+ std::vector<const sem::Parameter*> pointer_param_output_sources;
+};
+
+/// FunctionInfo holds information about the uniformity requirements and effects for a particular
+/// function.
+struct FunctionInfo {
+ /// The call site uniformity requirements.
+ CallSiteTag callsite_tag;
+ /// The function's uniformity effects.
+ FunctionTag function_tag;
+ /// The uniformity requirements of the function's parameters.
+ std::vector<ParameterInfo> parameters;
+};
+
+/// Node represents a node in the graph of control flow and value nodes within the analysis of a
+/// single function.
+struct Node {
+ /// Constructor
+ /// @param t the node tag (used for debugging)
+ Node([[maybe_unused]] std::string t, const ast::Node* a)
+ :
+#if TINT_DUMP_UNIFORMITY_GRAPH
+ tag(t),
+#endif
+ ast(a) {
+ }
+
+#if TINT_DUMP_UNIFORMITY_GRAPH
+ /// The node tag.
+ const std::string tag;
+#endif
+
+ /// The corresponding AST node, or nullptr.
+ const ast::Node* ast = nullptr;
+
+ /// The function call argument index, or UINT32_MAX.
+ uint32_t arg_index = std::numeric_limits<uint32_t>::max();
+
+ /// The set of edges from this node to other nodes in the graph.
+ utils::UniqueVector<Node*> edges;
+
+ /// The node that this node was visited from, or nullptr if not visited.
+ Node* visited_from = nullptr;
+
+ /// Add a edge to the `to` node.
+ /// @param to the destination node
+ void AddEdge(Node* to) { edges.add(to); }
+};
+
+/// UniformityGraph is used to analyze the uniformity requirements and effects of functions in a
+/// module.
+class UniformityGraph {
+ public:
+ /// Constructor.
+ /// @param builder the program to analyze
+ explicit UniformityGraph(ProgramBuilder* builder)
+ : builder_(builder), sem_(builder->Sem()), diagnostics_(builder->Diagnostics()) {}
+
+ /// Destructor.
+ ~UniformityGraph() {}
+
+ /// Build and analyze the graph to determine whether the program satisfies the uniformity
+ /// constraints of WGSL.
+ /// @param dependency_graph the dependency-ordered module-scope declarations
+ /// @returns true if all uniformity constraints are satisfied, otherise false
+ bool Build(const DependencyGraph& dependency_graph) {
+#if TINT_DUMP_UNIFORMITY_GRAPH
+ std::cout << "digraph G {\n";
+ std::cout << "rankdir=BT\n";
+#endif
+
+ // Process all functions in the module.
+ bool success = true;
+ for (auto* decl : dependency_graph.ordered_globals) {
+ if (auto* func = decl->As<ast::Function>()) {
+ if (!ProcessFunction(func)) {
+ success = false;
+ break;
+ }
+ }
+ }
+
+#if TINT_DUMP_UNIFORMITY_GRAPH
+ std::cout << "\n}\n";
+#endif
+
+ return success;
+ }
+
+ private:
+ const ProgramBuilder* builder_;
+ const sem::Info& sem_;
+ diag::List& diagnostics_;
+
+ /// Map of analyzed function results.
+ std::unordered_map<const ast::Function*, FunctionInfo> functions_;
+
+ /// Allocator for nodes.
+ utils::BlockAllocator<Node> nodes_;
+
+ /// Name of the function currently being analyzed.
+ std::string current_function_;
+
+ /// Special `RequiredToBeUniform` node.
+ Node* required_to_be_uniform_;
+ /// Special `MayBeNonUniform` node.
+ Node* may_be_non_uniform_;
+ /// Special `CF_return` node.
+ Node* cf_return_;
+ /// Special `Value_return` node.
+ Node* value_return_;
+ /// Special `{param}_return` nodes for pointer parameters.
+ std::unordered_map<const sem::Parameter*, Node*> pointer_param_returns_;
+
+ /// Map from variables to their value nodes in the graph, scoped with respect to control flow.
+ ScopeStack<const sem::Variable*, Node*> variables_;
+
+ /// The set of a local read-write vars that are in scope at any given point in the process.
+ /// Includes pointer parameters.
+ std::unordered_set<const sem::Variable*> local_var_decls_;
+
+ /// LoopSwitchInfo tracks information about the value of variables for a control flow construct.
+ struct LoopSwitchInfo {
+ /// The type of this control flow construct.
+ std::string type;
+ /// The input values for local variables at the start of this construct.
+ std::unordered_map<const sem::Variable*, Node*> var_in_nodes;
+ /// The exit values for local variables at the end of this construct.
+ std::unordered_map<const sem::Variable*, Node*> var_exit_nodes;
+ };
+
+ /// Map from control flow statements to the corresponding LoopSwitchInfo structure.
+ std::unordered_map<const sem::Statement*, LoopSwitchInfo> loop_switch_infos_;
+
+ /// A list of tags that have already been used within the current function.
+ std::unordered_set<std::string> tags_;
+
+ /// Create a new node.
+ /// @param tag a tag used to identify the node for debugging purposes.
+ /// @param ast the optional AST node that this node corresponds to
+ /// @returns the new node
+ Node* CreateNode(std::string tag, const ast::Node* ast = nullptr) {
+ std::string unique_tag = tag;
+
+#if TINT_DUMP_UNIFORMITY_GRAPH
+ // Make the tag unique.
+ // This only matters if we're dumping the graph.
+ int suffix = 0;
+ while (tags_.count(unique_tag)) {
+ unique_tag = tag + "_$" + std::to_string(++suffix);
+ }
+ tags_.insert(unique_tag);
+#endif
+
+ return nodes_.Create(current_function_ + "." + unique_tag, ast);
+ }
+
+ /// Process a function.
+ /// @param func the function to process
+ /// @returns true if there are no uniformity issues, false otherwise
+ bool ProcessFunction(const ast::Function* func) {
+ nodes_.Reset();
+ variables_.Clear();
+ pointer_param_returns_.clear();
+ tags_.clear();
+
+ current_function_ = builder_->Symbols().NameFor(func->symbol);
+
+ // Create special nodes.
+ Node* cf_start = CreateNode("CF_start");
+ required_to_be_uniform_ = CreateNode("RequiredToBeUniform");
+ may_be_non_uniform_ = CreateNode("MayBeNonUniform");
+ cf_return_ = CreateNode("CF_return");
+ if (func->return_type) {
+ value_return_ = CreateNode("Value_return");
+ }
+
+ // Create nodes for parameters.
+ std::vector<Node*> param_init_values(func->params.size(), nullptr);
+ for (size_t i = 0; i < func->params.size(); i++) {
+ auto* param = func->params[i];
+ auto name = builder_->Symbols().NameFor(param->symbol);
+ auto sem = sem_.Get<sem::Parameter>(param);
+
+ Node* node_init;
+ if (sem->Type()->Is<sem::Pointer>()) {
+ node_init = CreateNode("ptrparam_" + name + "_init");
+ pointer_param_returns_[sem] = CreateNode("ptrparam_" + name + "_return");
+ local_var_decls_.insert(sem);
+ } else {
+ node_init = CreateNode("param_" + name);
+ }
+ param_init_values[i] = node_init;
+ variables_.Set(sem, node_init);
+ }
+
+ // Process function body.
+ if (func->body) {
+ ProcessStatement(cf_start, func->body);
+ }
+
+#if TINT_DUMP_UNIFORMITY_GRAPH
+ // Dump the graph for this function as a subgraph.
+ std::cout << "\nsubgraph cluster_" << current_function_ << " {\n";
+ std::cout << " label=" << current_function_ << ";";
+ for (auto* node : nodes_.Objects()) {
+ std::cout << "\n \"" << node->tag << "\";";
+ for (auto* edge : node->edges) {
+ std::cout << "\n \"" << node->tag << "\" -> \"" << edge->tag << "\";";
+ }
+ }
+ std::cout << "\n}\n";
+#endif
+
+ FunctionInfo& info = functions_[func];
+ info.callsite_tag = CallSiteNoRestriction;
+ info.function_tag = NoRestriction;
+ info.parameters.resize(func->params.size());
+
+ // Look at which nodes are reachable from "RequiredToBeUniform".
+ {
+ utils::UniqueVector<Node*> reachable;
+ Traverse(required_to_be_uniform_, reachable);
+ if (reachable.contains(may_be_non_uniform_)) {
+ MakeError();
+ return false;
+ }
+ if (reachable.contains(cf_start)) {
+ info.callsite_tag = CallSiteRequiredToBeUniform;
+ }
+
+ // Set the parameter tag to ParameterRequiredToBeUniform for each parameter node that
+ // was reachable.
+ for (size_t i = 0; i < func->params.size(); i++) {
+ auto* param = func->params[i];
+ if (reachable.contains(variables_.Get(sem_.Get(param)))) {
+ info.parameters[i].tag = ParameterRequiredToBeUniform;
+ }
+ }
+ }
+
+ // Look at which nodes are reachable from "CF_return"
+ {
+ utils::UniqueVector<Node*> reachable;
+ Traverse(cf_return_, reachable);
+ if (reachable.contains(may_be_non_uniform_)) {
+ info.function_tag = SubsequentControlFlowMayBeNonUniform;
+ }
+
+ // Set the parameter tag to ParameterRequiredToBeUniformForSubsequentControlFlow for
+ // each parameter node that was reachable.
+ for (size_t i = 0; i < func->params.size(); i++) {
+ auto* param = func->params[i];
+ if (reachable.contains(variables_.Get(sem_.Get(param)))) {
+ info.parameters[i].tag = ParameterRequiredToBeUniformForSubsequentControlFlow;
+ }
+ }
+ }
+
+ // If "Value_return" exists, look at which nodes are reachable from it
+ if (value_return_) {
+ utils::UniqueVector<Node*> reachable;
+ Traverse(value_return_, reachable);
+ if (reachable.contains(may_be_non_uniform_)) {
+ info.function_tag = ReturnValueMayBeNonUniform;
+ }
+
+ // Set the parameter tag to ParameterRequiredToBeUniformForReturnValue for each
+ // parameter node that was reachable.
+ for (size_t i = 0; i < func->params.size(); i++) {
+ auto* param = func->params[i];
+ if (reachable.contains(variables_.Get(sem_.Get(param)))) {
+ info.parameters[i].tag = ParameterRequiredToBeUniformForReturnValue;
+ }
+ }
+ }
+
+ // Traverse the graph for each pointer parameter.
+ for (size_t i = 0; i < func->params.size(); i++) {
+ auto* param_dest = sem_.Get<sem::Parameter>(func->params[i]);
+ if (!param_dest->Type()->Is<sem::Pointer>()) {
+ continue;
+ }
+
+ // Reset "visited" state for all nodes.
+ for (auto* node : nodes_.Objects()) {
+ node->visited_from = nullptr;
+ }
+
+ utils::UniqueVector<Node*> reachable;
+ Traverse(pointer_param_returns_[param_dest], reachable);
+ if (reachable.contains(may_be_non_uniform_)) {
+ info.parameters[i].pointer_may_become_non_uniform = true;
+ }
+
+ // Check every other parameter to see if they feed into this parameter's final value.
+ for (size_t j = 0; j < func->params.size(); j++) {
+ auto* param_source = sem_.Get<sem::Parameter>(func->params[j]);
+ if (reachable.contains(param_init_values[j])) {
+ info.parameters[i].pointer_param_output_sources.push_back(param_source);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /// Process a statement, returning the new control flow node.
+ /// @param cf the input control flow node
+ /// @param stmt the statement to process d
+ /// @returns the new control flow node
+ Node* ProcessStatement(Node* cf, const ast::Statement* stmt) {
+ return Switch(
+ stmt,
+
+ [&](const ast::AssignmentStatement* a) {
+ auto [cf1, v1] = ProcessExpression(cf, a->rhs);
+ if (a->lhs->Is<ast::PhonyExpression>()) {
+ return cf1;
+ } else {
+ auto [cf2, l2] = ProcessLValueExpression(cf1, a->lhs);
+ l2->AddEdge(v1);
+ return cf2;
+ }
+ },
+
+ [&](const ast::BlockStatement* b) {
+ std::unordered_map<const sem::Variable*, Node*> scoped_assignments;
+ {
+ // Push a new scope for variable assignments in the block.
+ variables_.Push();
+ TINT_DEFER(variables_.Pop());
+
+ for (auto* s : b->statements) {
+ cf = ProcessStatement(cf, s);
+ if (!sem_.Get(s)->Behaviors().Contains(sem::Behavior::kNext)) {
+ break;
+ }
+ }
+
+ if (sem_.Get<sem::FunctionBlockStatement>(b)) {
+ // We've reached the end of the function body.
+ // Add edges from pointer parameter outputs to their current value.
+ for (auto param : pointer_param_returns_) {
+ param.second->AddEdge(variables_.Get(param.first));
+ }
+ }
+
+ scoped_assignments = std::move(variables_.Top());
+ }
+
+ // Propagate all variables assignments to the containing scope if the behavior is
+ // either 'Next' or 'Fallthrough'.
+ auto& behaviors = sem_.Get(b)->Behaviors();
+ if (behaviors.Contains(sem::Behavior::kNext) ||
+ behaviors.Contains(sem::Behavior::kFallthrough)) {
+ for (auto var : scoped_assignments) {
+ variables_.Set(var.first, var.second);
+ }
+ }
+
+ // Remove any variables declared in this scope from the set of in-scope variables.
+ for (auto* d : sem_.Get<sem::BlockStatement>(b)->Decls()) {
+ local_var_decls_.erase(sem_.Get<sem::LocalVariable>(d));
+ }
+
+ return cf;
+ },
+
+ [&](const ast::BreakStatement* b) {
+ // Find the loop or switch statement that we are in.
+ auto* parent = sem_.Get(b)
+ ->FindFirstParent<sem::SwitchStatement, sem::LoopStatement,
+ sem::ForLoopStatement>();
+ TINT_ASSERT(Resolver, loop_switch_infos_.count(parent));
+ auto& info = loop_switch_infos_.at(parent);
+
+ // Propagate variable values to the loop/switch exit nodes.
+ for (auto* var : local_var_decls_) {
+ // Skip variables that were declared inside this loop/switch.
+ if (auto* lv = var->As<sem::LocalVariable>();
+ lv &&
+ lv->Statement()->FindFirstParent([&](auto* s) { return s == parent; })) {
+ continue;
+ }
+
+ // Add an edge from the variable exit node to its value at this point.
+ auto* exit_node = utils::GetOrCreate(info.var_exit_nodes, var, [&]() {
+ auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
+ return CreateNode(name + "_value_" + info.type + "_exit");
+ });
+ exit_node->AddEdge(variables_.Get(var));
+ }
+
+ return cf;
+ },
+
+ [&](const ast::CallStatement* c) {
+ auto [cf1, _] = ProcessCall(cf, c->expr);
+ return cf1;
+ },
+
+ [&](const ast::CompoundAssignmentStatement* c) {
+ // The compound assignment statement `a += b` is equivalent to `a = a + b`.
+ auto [cf1, v1] = ProcessExpression(cf, c->lhs);
+ auto [cf2, v2] = ProcessExpression(cf1, c->rhs);
+ auto* result = CreateNode("binary_expr_result");
+ result->AddEdge(v1);
+ result->AddEdge(v2);
+
+ auto [cf3, l3] = ProcessLValueExpression(cf2, c->lhs);
+ l3->AddEdge(result);
+ return cf3;
+ },
+
+ [&](const ast::ContinueStatement* c) {
+ // Find the loop statement that we are in.
+ auto* parent =
+ sem_.Get(c)->FindFirstParent<sem::LoopStatement, sem::ForLoopStatement>();
+ TINT_ASSERT(Resolver, loop_switch_infos_.count(parent));
+ auto& info = loop_switch_infos_.at(parent);
+
+ // Propagate assignments to the loop input nodes.
+ for (auto* var : local_var_decls_) {
+ // Skip variables that were declared inside this loop.
+ if (auto* lv = var->As<sem::LocalVariable>();
+ lv &&
+ lv->Statement()->FindFirstParent([&](auto* s) { return s == parent; })) {
+ continue;
+ }
+
+ // Add an edge from the variable's loop input node to its value at this point.
+ TINT_ASSERT(Resolver, info.var_in_nodes.count(var));
+ auto* in_node = info.var_in_nodes.at(var);
+ auto* out_node = variables_.Get(var);
+ if (out_node != in_node) {
+ in_node->AddEdge(out_node);
+ }
+ }
+ return cf;
+ },
+
+ [&](const ast::DiscardStatement*) {
+ cf_return_->AddEdge(cf);
+ return cf;
+ },
+
+ [&](const ast::FallthroughStatement*) { return cf; },
+
+ [&](const ast::ForLoopStatement* f) {
+ auto* sem_loop = sem_.Get(f);
+ auto cfx = CreateNode("loop_start");
+
+ // Insert the initializer before the loop.
+ auto cf_init = cf;
+ if (f->initializer) {
+ cf_init = ProcessStatement(cf, f->initializer);
+ }
+ auto cf_start = cf_init;
+
+ auto& info = loop_switch_infos_[sem_loop];
+ info.type = "forloop";
+
+ // Create input nodes for any variables declared before this loop.
+ for (auto* v : local_var_decls_) {
+ auto name = builder_->Symbols().NameFor(v->Declaration()->symbol);
+ auto* in_node = CreateNode(name + "_value_forloop_in");
+ in_node->AddEdge(variables_.Get(v));
+ info.var_in_nodes[v] = in_node;
+ variables_.Set(v, in_node);
+ }
+
+ // Insert the condition at the start of the loop body.
+ if (f->condition) {
+ auto [cf_cond, v] = ProcessExpression(cfx, f->condition);
+ auto* cf_condition_end = CreateNode("for_condition_CFend");
+ cf_condition_end->AddEdge(v);
+ cf_start = cf_condition_end;
+
+ // Propagate assignments to the loop exit nodes.
+ for (auto* var : local_var_decls_) {
+ auto* exit_node = utils::GetOrCreate(info.var_exit_nodes, var, [&]() {
+ auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
+ return CreateNode(name + "_value_" + info.type + "_exit");
+ });
+ exit_node->AddEdge(variables_.Get(var));
+ }
+ }
+ auto cf1 = ProcessStatement(cf_start, f->body);
+
+ // Insert the continuing statement at the end of the loop body.
+ if (f->continuing) {
+ auto cf2 = ProcessStatement(cf1, f->continuing);
+ cfx->AddEdge(cf2);
+ } else {
+ cfx->AddEdge(cf1);
+ }
+ cfx->AddEdge(cf);
+
+ // Add edges from variable loop input nodes to their values at the end of the loop.
+ for (auto v : info.var_in_nodes) {
+ auto* in_node = v.second;
+ auto* out_node = variables_.Get(v.first);
+ if (out_node != in_node) {
+ in_node->AddEdge(out_node);
+ }
+ }
+
+ // Set each variable's exit node as its value in the outer scope.
+ for (auto v : info.var_exit_nodes) {
+ variables_.Set(v.first, v.second);
+ }
+
+ loop_switch_infos_.erase(sem_loop);
+
+ if (sem_loop->Behaviors() == sem::Behaviors{sem::Behavior::kNext}) {
+ return cf;
+ } else {
+ return cfx;
+ }
+ },
+
+ [&](const ast::IfStatement* i) {
+ auto* sem_if = sem_.Get(i);
+ auto [cfx, v] = ProcessExpression(cf, i->condition);
+
+ std::unordered_map<const sem::Variable*, Node*> true_vars;
+ std::unordered_map<const sem::Variable*, Node*> false_vars;
+
+ // Helper to process a statement with a new scope for variable assignments.
+ // Populates `assigned_vars` with new nodes for any variables that are assigned in
+ // this statement.
+ auto process_in_scope =
+ [&](Node* cf_in, const ast::Statement* s,
+ std::unordered_map<const sem::Variable*, Node*>& assigned_vars) {
+ // Push a new scope for variable assignments.
+ variables_.Push();
+
+ // Process the statement.
+ auto cf_out = ProcessStatement(cf_in, s);
+
+ assigned_vars = variables_.Top();
+
+ // Pop the scope and return.
+ variables_.Pop();
+ return cf_out;
+ };
+
+ auto cf1 = process_in_scope(v, i->body, true_vars);
+
+ bool true_has_next = sem_.Get(i->body)->Behaviors().Contains(sem::Behavior::kNext);
+ bool false_has_next = true;
+
+ Node* cf2 = nullptr;
+ if (i->else_statement) {
+ cf2 = process_in_scope(v, i->else_statement, false_vars);
+
+ false_has_next =
+ sem_.Get(i->else_statement)->Behaviors().Contains(sem::Behavior::kNext);
+ }
+
+ // Update values for any variables assigned in the if or else blocks.
+ for (auto var : local_var_decls_) {
+ // Skip variables not assigned in either block.
+ if (true_vars.count(var) == 0 && false_vars.count(var) == 0) {
+ continue;
+ }
+
+ // Create an exit node for the variable.
+ auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
+ auto* out_node = CreateNode(name + "_value_if_exit");
+
+ // Add edges to the assigned value or the initial value.
+ // Only add edges if the behavior for that block contains 'Next'.
+ if (true_has_next) {
+ if (true_vars.count(var)) {
+ out_node->AddEdge(true_vars.at(var));
+ } else {
+ out_node->AddEdge(variables_.Get(var));
+ }
+ }
+ if (false_has_next) {
+ if (false_vars.count(var)) {
+ out_node->AddEdge(false_vars.at(var));
+ } else {
+ out_node->AddEdge(variables_.Get(var));
+ }
+ }
+
+ variables_.Set(var, out_node);
+ }
+
+ if (sem_if->Behaviors() != sem::Behaviors{sem::Behavior::kNext}) {
+ auto* cf_end = CreateNode("if_CFend");
+ cf_end->AddEdge(cf1);
+ if (cf2) {
+ cf_end->AddEdge(cf2);
+ }
+ return cf_end;
+ }
+ return cf;
+ },
+
+ [&](const ast::IncrementDecrementStatement* i) {
+ // The increment/decrement statement `i++` is equivalent to `i = i + 1`.
+ auto [cf1, v1] = ProcessExpression(cf, i->lhs);
+ auto* result = CreateNode("incdec_result");
+ result->AddEdge(v1);
+ result->AddEdge(cf1);
+
+ auto [cf2, l2] = ProcessLValueExpression(cf1, i->lhs);
+ l2->AddEdge(result);
+ return cf2;
+ },
+
+ [&](const ast::LoopStatement* l) {
+ auto* sem_loop = sem_.Get(l);
+ auto cfx = CreateNode("loop_start");
+
+ auto& info = loop_switch_infos_[sem_loop];
+ info.type = "loop";
+
+ // Create input nodes for any variables declared before this loop.
+ for (auto* v : local_var_decls_) {
+ auto name = builder_->Symbols().NameFor(v->Declaration()->symbol);
+ auto* in_node = CreateNode(name + "_value_loop_in");
+ in_node->AddEdge(variables_.Get(v));
+ info.var_in_nodes[v] = in_node;
+ variables_.Set(v, in_node);
+ }
+
+ auto cf1 = ProcessStatement(cfx, l->body);
+ if (l->continuing) {
+ auto cf2 = ProcessStatement(cf1, l->continuing);
+ cfx->AddEdge(cf2);
+ } else {
+ cfx->AddEdge(cf1);
+ }
+ cfx->AddEdge(cf);
+
+ // Add edges from variable loop input nodes to their values at the end of the loop.
+ for (auto v : info.var_in_nodes) {
+ auto* in_node = v.second;
+ auto* out_node = variables_.Get(v.first);
+ if (out_node != in_node) {
+ in_node->AddEdge(out_node);
+ }
+ }
+
+ // Set each variable's exit node as its value in the outer scope.
+ for (auto v : info.var_exit_nodes) {
+ variables_.Set(v.first, v.second);
+ }
+
+ loop_switch_infos_.erase(sem_loop);
+
+ if (sem_loop->Behaviors() == sem::Behaviors{sem::Behavior::kNext}) {
+ return cf;
+ } else {
+ return cfx;
+ }
+ },
+ [&](const ast::ReturnStatement* r) {
+ Node* cf_ret;
+ if (r->value) {
+ auto [cf1, v] = ProcessExpression(cf, r->value);
+ cf_return_->AddEdge(cf1);
+ value_return_->AddEdge(v);
+ cf_ret = cf1;
+ } else {
+ TINT_ASSERT(Resolver, cf != nullptr);
+ cf_return_->AddEdge(cf);
+ cf_ret = cf;
+ }
+
+ // Add edges from each pointer parameter output to its current value.
+ for (auto param : pointer_param_returns_) {
+ param.second->AddEdge(variables_.Get(param.first));
+ }
+
+ return cf_ret;
+ },
+ [&](const ast::SwitchStatement* s) {
+ auto* sem_switch = sem_.Get(s);
+ auto [cfx, v] = ProcessExpression(cf, s->condition);
+
+ Node* cf_end = nullptr;
+ if (sem_switch->Behaviors() != sem::Behaviors{sem::Behavior::kNext}) {
+ cf_end = CreateNode("switch_CFend");
+ }
+
+ auto& info = loop_switch_infos_[sem_switch];
+ info.type = "switch";
+
+ auto cf_n = v;
+ bool previous_case_has_fallthrough = false;
+ for (auto* c : s->body) {
+ auto* sem_case = sem_.Get(c);
+
+ if (previous_case_has_fallthrough) {
+ cf_n = ProcessStatement(cf_n, c->body);
+ } else {
+ variables_.Push();
+ cf_n = ProcessStatement(v, c->body);
+ }
+
+ if (cf_end) {
+ cf_end->AddEdge(cf_n);
+ }
+
+ bool has_fallthrough =
+ sem_case->Behaviors().Contains(sem::Behavior::kFallthrough);
+ if (!has_fallthrough) {
+ if (sem_case->Behaviors().Contains(sem::Behavior::kNext)) {
+ // Propagate variable values to the switch exit nodes.
+ for (auto* var : local_var_decls_) {
+ // Skip variables that were declared inside the switch.
+ if (auto* lv = var->As<sem::LocalVariable>();
+ lv && lv->Statement()->FindFirstParent(
+ [&](auto* st) { return st == sem_switch; })) {
+ continue;
+ }
+
+ // Add an edge from the variable exit node to its new value.
+ auto* exit_node =
+ utils::GetOrCreate(info.var_exit_nodes, var, [&]() {
+ auto name =
+ builder_->Symbols().NameFor(var->Declaration()->symbol);
+ return CreateNode(name + "_value_" + info.type + "_exit");
+ });
+ exit_node->AddEdge(variables_.Get(var));
+ }
+ }
+ variables_.Pop();
+ }
+ previous_case_has_fallthrough = has_fallthrough;
+ }
+
+ // Update nodes for any variables assigned in the switch statement.
+ for (auto var : info.var_exit_nodes) {
+ variables_.Set(var.first, var.second);
+ }
+
+ return cf_end ? cf_end : cf;
+ },
+ [&](const ast::VariableDeclStatement* decl) {
+ Node* node;
+ if (decl->variable->constructor) {
+ auto [cf1, v] = ProcessExpression(cf, decl->variable->constructor);
+ cf = cf1;
+ node = v;
+ } else {
+ node = cf;
+ }
+ variables_.Set(sem_.Get(decl->variable), node);
+
+ if (!decl->variable->is_const) {
+ local_var_decls_.insert(sem_.Get<sem::LocalVariable>(decl->variable));
+ }
+
+ return cf;
+ },
+ [&](Default) {
+ TINT_ICE(Resolver, diagnostics_)
+ << "unknown statement type: " << std::string(stmt->TypeInfo().name);
+ return nullptr;
+ });
+ }
+
+ /// Process an identifier expression.
+ /// @param cf the input control flow node
+ /// @param ident the identifier expression to process
+ /// @returns a pair of (control flow node, value node)
+ std::pair<Node*, Node*> ProcessIdentExpression(Node* cf,
+ const ast::IdentifierExpression* ident) {
+ // Helper to check if the entry point attribute of `obj` indicates non-uniformity.
+ auto has_nonuniform_entry_point_attribute = [](auto* obj) {
+ // Only the num_workgroups and workgroup_id builtins are uniform.
+ if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(obj->attributes)) {
+ if (builtin->builtin == ast::Builtin::kNumWorkgroups ||
+ builtin->builtin == ast::Builtin::kWorkgroupId) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ auto name = builder_->Symbols().NameFor(ident->symbol);
+ auto* sem = sem_.Get<sem::VariableUser>(ident)->Variable();
+ return Switch(
+ sem,
+
+ [&](const sem::Parameter* param) {
+ auto* user_func = param->Owner()->As<sem::Function>();
+ if (user_func && user_func->Declaration()->IsEntryPoint()) {
+ if (auto* str = param->Type()->As<sem::Struct>()) {
+ // We consider the whole struct to be non-uniform if any one of its members
+ // is non-uniform.
+ bool uniform = true;
+ for (auto* member : str->Members()) {
+ if (has_nonuniform_entry_point_attribute(member->Declaration())) {
+ uniform = false;
+ }
+ }
+ return std::make_pair(cf, uniform ? cf : may_be_non_uniform_);
+ } else {
+ if (has_nonuniform_entry_point_attribute(param->Declaration())) {
+ return std::make_pair(cf, may_be_non_uniform_);
+ }
+ return std::make_pair(cf, cf);
+ }
+ } else {
+ auto* result = CreateNode(name + "_result");
+ auto* x = variables_.Get(param);
+ result->AddEdge(cf);
+ result->AddEdge(x);
+ return std::make_pair(cf, result);
+ }
+ },
+
+ [&](const sem::GlobalVariable* global) {
+ if (global->Declaration()->is_const || global->Access() == ast::Access::kRead) {
+ return std::make_pair(cf, cf);
+ } else {
+ return std::make_pair(cf, may_be_non_uniform_);
+ }
+ },
+
+ [&](const sem::LocalVariable* local) {
+ auto* result = CreateNode(name + "_result");
+ result->AddEdge(cf);
+ if (auto* x = variables_.Get(local)) {
+ result->AddEdge(x);
+ }
+ return std::make_pair(cf, result);
+ },
+
+ [&](Default) {
+ TINT_ICE(Resolver, diagnostics_)
+ << "unknown identifier expression type: " << std::string(sem->TypeInfo().name);
+ return std::pair<Node*, Node*>(nullptr, nullptr);
+ });
+ }
+
+ /// Process an expression.
+ /// @param cf the input control flow node
+ /// @param expr the expression to process
+ /// @returns a pair of (control flow node, value node)
+ std::pair<Node*, Node*> ProcessExpression(Node* cf, const ast::Expression* expr) {
+ return Switch(
+ expr,
+
+ [&](const ast::BinaryExpression* b) {
+ if (b->IsLogical()) {
+ // Short-circuiting binary operators are a special case.
+ auto [cf1, v1] = ProcessExpression(cf, b->lhs);
+ auto [cf2, v2] = ProcessExpression(v1, b->rhs);
+ return std::pair<Node*, Node*>(cf2, v2);
+ } else {
+ auto [cf1, v1] = ProcessExpression(cf, b->lhs);
+ auto [cf2, v2] = ProcessExpression(cf1, b->rhs);
+ auto* result = CreateNode("binary_expr_result");
+ result->AddEdge(v1);
+ result->AddEdge(v2);
+ return std::pair<Node*, Node*>(cf2, result);
+ }
+ },
+
+ [&](const ast::BitcastExpression* b) { return ProcessExpression(cf, b->expr); },
+
+ [&](const ast::CallExpression* c) { return ProcessCall(cf, c); },
+
+ [&](const ast::IdentifierExpression* i) { return ProcessIdentExpression(cf, i); },
+
+ [&](const ast::IndexAccessorExpression* i) {
+ auto [cf1, v1] = ProcessExpression(cf, i->object);
+ auto [cf2, v2] = ProcessExpression(cf1, i->index);
+ auto* result = CreateNode("index_accessor_result");
+ result->AddEdge(v1);
+ result->AddEdge(v2);
+ return std::pair<Node*, Node*>(cf2, result);
+ },
+
+ [&](const ast::LiteralExpression*) { return std::make_pair(cf, cf); },
+
+ [&](const ast::MemberAccessorExpression* m) {
+ return ProcessExpression(cf, m->structure);
+ },
+
+ [&](const ast::UnaryOpExpression* u) {
+ if (u->op == ast::UnaryOp::kIndirection) {
+ // Cut the analysis short, since we only need to know the originating variable
+ // which is being accessed.
+ auto* source_var = sem_.Get(u)->SourceVariable();
+ auto* value = variables_.Get(source_var);
+ if (!value) {
+ value = cf;
+ }
+ return std::pair<Node*, Node*>(cf, value);
+ }
+ return ProcessExpression(cf, u->expr);
+ },
+
+ [&](Default) {
+ TINT_ICE(Resolver, diagnostics_)
+ << "unknown expression type: " << std::string(expr->TypeInfo().name);
+ return std::pair<Node*, Node*>(nullptr, nullptr);
+ });
+ }
+
+ /// Process an LValue expression.
+ /// @param cf the input control flow node
+ /// @param expr the expression to process
+ /// @returns a pair of (control flow node, variable node)
+ std::pair<Node*, Node*> ProcessLValueExpression(Node* cf, const ast::Expression* expr) {
+ return Switch(
+ expr,
+
+ [&](const ast::IdentifierExpression* i) {
+ auto name = builder_->Symbols().NameFor(i->symbol);
+ auto* sem = sem_.Get<sem::VariableUser>(i);
+ if (sem->Variable()->Is<sem::GlobalVariable>()) {
+ return std::make_pair(cf, may_be_non_uniform_);
+ } else if (auto* local = sem->Variable()->As<sem::LocalVariable>()) {
+ // Create a new value node for this variable.
+ auto* value = CreateNode(name + "_lvalue");
+ auto* old_value = variables_.Set(local, value);
+
+ // Aggregate values link back to their previous value, as they can never become
+ // uniform again.
+ if (!local->Type()->UnwrapRef()->is_scalar() && old_value) {
+ value->AddEdge(old_value);
+ }
+
+ return std::make_pair(cf, value);
+ } else {
+ TINT_ICE(Resolver, diagnostics_)
+ << "unknown lvalue identifier expression type: "
+ << std::string(sem->Variable()->TypeInfo().name);
+ return std::pair<Node*, Node*>(nullptr, nullptr);
+ }
+ },
+
+ [&](const ast::IndexAccessorExpression* i) {
+ auto [cf1, l1] = ProcessLValueExpression(cf, i->object);
+ auto [cf2, v2] = ProcessExpression(cf1, i->index);
+ l1->AddEdge(v2);
+ return std::pair<Node*, Node*>(cf2, l1);
+ },
+
+ [&](const ast::MemberAccessorExpression* m) {
+ return ProcessLValueExpression(cf, m->structure);
+ },
+
+ [&](const ast::UnaryOpExpression* u) {
+ if (u->op == ast::UnaryOp::kIndirection) {
+ // Cut the analysis short, since we only need to know the originating variable
+ // that is being written to.
+ auto* source_var = sem_.Get(u)->SourceVariable();
+ auto name = builder_->Symbols().NameFor(source_var->Declaration()->symbol);
+ auto* deref = CreateNode(name + "_deref");
+ auto* old_value = variables_.Set(source_var, deref);
+
+ // Aggregate values link back to their previous value, as they can never become
+ // uniform again.
+ if (!source_var->Type()->UnwrapRef()->UnwrapPtr()->is_scalar() && old_value) {
+ deref->AddEdge(old_value);
+ }
+
+ return std::pair<Node*, Node*>(cf, deref);
+ }
+ return ProcessLValueExpression(cf, u->expr);
+ },
+
+ [&](Default) {
+ TINT_ICE(Resolver, diagnostics_)
+ << "unknown lvalue expression type: " << std::string(expr->TypeInfo().name);
+ return std::pair<Node*, Node*>(nullptr, nullptr);
+ });
+ }
+
+ /// Process a function call expression.
+ /// @param cf the input control flow node
+ /// @param call the function call to process
+ /// @returns a pair of (control flow node, value node)
+ std::pair<Node*, Node*> ProcessCall(Node* cf, const ast::CallExpression* call) {
+ std::string name;
+ if (call->target.name) {
+ name = builder_->Symbols().NameFor(call->target.name->symbol);
+ } else {
+ name = call->target.type->FriendlyName(builder_->Symbols());
+ }
+
+ // Process call arguments
+ Node* cf_last_arg = cf;
+ std::vector<Node*> args;
+ for (size_t i = 0; i < call->args.size(); i++) {
+ auto [cf_i, arg_i] = ProcessExpression(cf_last_arg, call->args[i]);
+
+ // Capture the index of this argument in a new node.
+ // Note: This is an additional node that isn't described in the specification, for the
+ // purpose of providing diagnostic information.
+ Node* arg_node = CreateNode(name + "_arg_" + std::to_string(i), call);
+ arg_node->arg_index = static_cast<uint32_t>(i);
+ arg_node->AddEdge(arg_i);
+
+ cf_last_arg = cf_i;
+ args.push_back(arg_node);
+ }
+
+ Node* result = CreateNode("Result_" + name);
+ Node* cf_after = CreateNode("CF_after_" + name, call);
+
+ // Get tags for the callee.
+ CallSiteTag callsite_tag = CallSiteNoRestriction;
+ FunctionTag function_tag = NoRestriction;
+ auto* sem = sem_.Get(call);
+ const FunctionInfo* func_info = nullptr;
+ Switch(
+ sem->Target(),
+ [&](const sem::Builtin* builtin) {
+ // Most builtins have no restrictions. The exceptions are barriers, derivatives, and
+ // some texture sampling builtins.
+ if (builtin->IsBarrier()) {
+ callsite_tag = CallSiteRequiredToBeUniform;
+ } else if (builtin->IsDerivative() ||
+ builtin->Type() == sem::BuiltinType::kTextureSample ||
+ builtin->Type() == sem::BuiltinType::kTextureSampleBias ||
+ builtin->Type() == sem::BuiltinType::kTextureSampleCompare) {
+ callsite_tag = CallSiteRequiredToBeUniform;
+ function_tag = ReturnValueMayBeNonUniform;
+ } else {
+ callsite_tag = CallSiteNoRestriction;
+ function_tag = NoRestriction;
+ }
+ },
+ [&](const sem::Function* func) {
+ // We must have already analyzed the user-defined function since we process
+ // functions in dependency order.
+ TINT_ASSERT(Resolver, functions_.count(func->Declaration()));
+ auto& info = functions_.at(func->Declaration());
+ callsite_tag = info.callsite_tag;
+ function_tag = info.function_tag;
+ func_info = &info;
+ },
+ [&](const sem::TypeConstructor*) {
+ callsite_tag = CallSiteNoRestriction;
+ function_tag = NoRestriction;
+ },
+ [&](const sem::TypeConversion*) {
+ callsite_tag = CallSiteNoRestriction;
+ function_tag = NoRestriction;
+ },
+ [&](Default) {
+ TINT_ICE(Resolver, diagnostics_) << "unhandled function call target: " << name;
+ });
+
+ if (callsite_tag == CallSiteRequiredToBeUniform) {
+ // Note: This deviates from the rules in the specification, which would add the edge
+ // directly to the incoming CF node. Going through CF_after instead makes it easier to
+ // produce diagnostics that can identify the function being called.
+ required_to_be_uniform_->AddEdge(cf_after);
+ }
+ cf_after->AddEdge(cf_last_arg);
+
+ if (function_tag == SubsequentControlFlowMayBeNonUniform) {
+ cf_after->AddEdge(may_be_non_uniform_);
+ } else if (function_tag == ReturnValueMayBeNonUniform) {
+ result->AddEdge(may_be_non_uniform_);
+ }
+
+ result->AddEdge(cf_after);
+
+ // For each argument, add edges based on parameter tags.
+ for (size_t i = 0; i < args.size(); i++) {
+ if (func_info) {
+ switch (func_info->parameters[i].tag) {
+ case ParameterRequiredToBeUniform:
+ required_to_be_uniform_->AddEdge(args[i]);
+ break;
+ case ParameterRequiredToBeUniformForSubsequentControlFlow:
+ cf_after->AddEdge(args[i]);
+ break;
+ case ParameterRequiredToBeUniformForReturnValue:
+ result->AddEdge(args[i]);
+ break;
+ case ParameterNoRestriction:
+ break;
+ }
+
+ auto* sem_arg = sem_.Get(call->args[i]);
+ if (sem_arg->Type()->Is<sem::Pointer>()) {
+ auto* ptr_result =
+ CreateNode(name + "_ptrarg_" + std::to_string(i) + "_result");
+ if (func_info->parameters[i].pointer_may_become_non_uniform) {
+ ptr_result->AddEdge(may_be_non_uniform_);
+ } else {
+ // Add edges from the resulting pointer value to any other arguments that
+ // feed it.
+ for (auto* source : func_info->parameters[i].pointer_param_output_sources) {
+ ptr_result->AddEdge(args[source->Index()]);
+ }
+ }
+
+ // Update the current stored value for this pointer argument.
+ auto* source_var = sem_arg->SourceVariable();
+ TINT_ASSERT(Resolver, source_var);
+ variables_.Set(source_var, ptr_result);
+ }
+ } else {
+ // All builtin function parameters are RequiredToBeUniformForReturnValue, as are
+ // parameters for type constructors and type conversions.
+ // The arrayLength() builtin is a special case, as there is currently no way for it
+ // to have a non-uniform return value.
+ auto* builtin = sem->Target()->As<sem::Builtin>();
+ if (!builtin || builtin->Type() != sem::BuiltinType::kArrayLength) {
+ result->AddEdge(args[i]);
+ }
+ }
+ }
+
+ return {cf_after, result};
+ }
+
+ /// Recursively traverse a graph starting at `node`, inserting all nodes that are reached into
+ /// `reachable`.
+ /// @param node the starting node
+ /// @param reachable the set of reachable nodes to populate
+ void Traverse(Node* node, utils::UniqueVector<Node*>& reachable) {
+ reachable.add(node);
+ for (auto* to : node->edges) {
+ if (to->visited_from == nullptr) {
+ to->visited_from = node;
+ Traverse(to, reachable);
+ }
+ }
+ }
+
+ /// Generate an error for a required_to_be_uniform->may_be_non_uniform path.
+ void MakeError() {
+ // Trace back to find a node that is required to be uniform that was reachable from a
+ // non-uniform value or control flow node.
+ Node* current = may_be_non_uniform_;
+ while (current) {
+ TINT_ASSERT(Resolver, current->visited_from);
+ if (current->visited_from == required_to_be_uniform_) {
+ break;
+ }
+ current = current->visited_from;
+ }
+
+ // The node will always have an corresponding call expression.
+ auto* call = current->ast->As<ast::CallExpression>();
+ TINT_ASSERT(Resolver, call);
+ auto* target = sem_.Get(call)->Target();
+
+ std::string name;
+ if (auto* builtin = target->As<sem::Builtin>()) {
+ name = builtin->str();
+ } else if (auto* user = target->As<sem::Function>()) {
+ name = builder_->Symbols().NameFor(user->Declaration()->symbol);
+ }
+
+ // TODO(jrprice): Switch to error instead of warning when feedback has settled.
+ if (current->arg_index != std::numeric_limits<uint32_t>::max()) {
+ // The requirement was on a function parameter.
+ auto param_name = builder_->Symbols().NameFor(
+ target->Parameters()[current->arg_index]->Declaration()->symbol);
+ diagnostics_.add_warning(
+ diag::System::Resolver,
+ "parameter '" + param_name + "' of '" + name + "' must be uniform",
+ call->args[current->arg_index]->source);
+ // TODO(jrprice): Show the reason why.
+ } else {
+ // The requirement was on a function callsite.
+ diagnostics_.add_warning(diag::System::Resolver,
+ "'" + name + "' must only be called from uniform control flow",
+ call->source);
+ // TODO(jrprice): Show full call stack to the problematic builtin.
+ }
+ }
+};
+
+} // namespace
+
+bool AnalyzeUniformity(ProgramBuilder* builder, const DependencyGraph& dependency_graph) {
+ if (builder->AST().Extensions().count(
+ ast::Enable::ExtensionKind::kChromiumDisableUniformityAnalysis)) {
+ return true;
+ }
+
+ UniformityGraph graph(builder);
+ return graph.Build(dependency_graph);
+}
+
+} // namespace tint::resolver
diff --git a/src/tint/resolver/uniformity.h b/src/tint/resolver/uniformity.h
new file mode 100644
index 0000000..39827cf
--- /dev/null
+++ b/src/tint/resolver/uniformity.h
@@ -0,0 +1,36 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_RESOLVER_UNIFORMITY_H_
+#define SRC_TINT_RESOLVER_UNIFORMITY_H_
+
+// Forward declarations.
+namespace tint {
+namespace resolver {
+struct DependencyGraph;
+} // namespace resolver
+class ProgramBuilder;
+} // namespace tint
+
+namespace tint::resolver {
+
+/// Analyze the uniformity of a program.
+/// @param builder the program to analyze
+/// @param dependency_graph the dependency-ordered module-scope declarations
+/// @returns true if there are no uniformity issues, false otherwise
+bool AnalyzeUniformity(ProgramBuilder* builder, const resolver::DependencyGraph& dependency_graph);
+
+} // namespace tint::resolver
+
+#endif // SRC_TINT_RESOLVER_UNIFORMITY_H_
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
new file mode 100644
index 0000000..bb33f89
--- /dev/null
+++ b/src/tint/resolver/uniformity_test.cc
@@ -0,0 +1,4888 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/resolver/uniformity.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace tint::number_suffixes; // NOLINT
+
+namespace tint::resolver {
+namespace {
+
+class UniformityAnalysisTestBase {
+ protected:
+ /// Parse and resolve a WGSL shader.
+ /// @param src the WGSL source code
+ /// @param should_pass true if `src` should pass the analysis, otherwise false
+ void RunTest(std::string src, bool should_pass) {
+ auto file = std::make_unique<Source::File>("test", src);
+ auto program = reader::wgsl::Parse(file.get());
+
+ diag::Formatter::Style style;
+ style.print_newline_at_end = false;
+ error_ = diag::Formatter(style).format(program.Diagnostics());
+
+ bool valid = program.IsValid();
+ if (should_pass) {
+ EXPECT_TRUE(valid) << error_;
+ if (program.Diagnostics().count() == 1u) {
+ EXPECT_THAT(program.Diagnostics().str(), ::testing::HasSubstr("unreachable"));
+ } else {
+ EXPECT_EQ(program.Diagnostics().count(), 0u) << error_;
+ }
+ } else {
+ // TODO(jrprice): expect false when uniformity issues become errors.
+ EXPECT_TRUE(valid) << error_;
+ }
+ }
+
+ /// Build and resolve a program from a ProgramBuilder object.
+ /// @param builder the program builder
+ /// @returns true on success, false on failure
+ bool RunTest(ProgramBuilder&& builder) {
+ auto program = Program(std::move(builder));
+
+ diag::Formatter::Style style;
+ style.print_newline_at_end = false;
+ error_ = diag::Formatter(style).format(program.Diagnostics());
+
+ return program.IsValid();
+ }
+
+ /// The error message from the parser or resolver, if any.
+ std::string error_;
+};
+
+class UniformityAnalysisTest : public UniformityAnalysisTestBase, public ::testing::Test {};
+
+class BasicTest : public UniformityAnalysisTestBase,
+ public ::testing::TestWithParam<std::tuple<int, int>> {
+ public:
+ /// Enum for the if-statement condition guarding a function call.
+ enum Condition {
+ // Uniform conditions:
+ kTrue,
+ kFalse,
+ kLiteral,
+ kModuleLet,
+ kPipelineOverridable,
+ kFuncLetUniformRhs,
+ kFuncVarUniform,
+ kFuncUniformRetVal,
+ kUniformBuffer,
+ kROStorageBuffer,
+ kLastUniformCondition = kROStorageBuffer,
+ // MayBeNonUniform conditions:
+ kFuncLetNonUniformRhs,
+ kFuncVarNonUniform,
+ kFuncNonUniformRetVal,
+ kRWStorageBuffer,
+ // End of range marker:
+ kEndOfConditionRange,
+ };
+
+ /// Enum for the function call statement.
+ enum Function {
+ // NoRestrictionFunctions:
+ kUserNoRestriction,
+ kMin,
+ kTextureSampleLevel,
+ kLastNoRestrictionFunction = kTextureSampleLevel,
+ // RequiredToBeUniform functions:
+ kUserRequiredToBeUniform,
+ kWorkgroupBarrier,
+ kStorageBarrier,
+ kTextureSample,
+ kTextureSampleBias,
+ kTextureSampleCompare,
+ kDpdx,
+ kDpdxCoarse,
+ kDpdxFine,
+ kDpdy,
+ kDpdyCoarse,
+ kDpdyFine,
+ kFwidth,
+ kFwidthCoarse,
+ kFwidthFine,
+ // End of range marker:
+ kEndOfFunctionRange,
+ };
+
+ /// Convert a condition to its string representation.
+ static std::string ConditionToStr(Condition c) {
+ switch (c) {
+ case kTrue:
+ return "true";
+ case kFalse:
+ return "false";
+ case kLiteral:
+ return "7 == 7";
+ case kModuleLet:
+ return "module_let == 0";
+ case kPipelineOverridable:
+ return "pipeline_overridable == 0";
+ case kFuncLetUniformRhs:
+ return "let_uniform_rhs == 0";
+ case kFuncVarUniform:
+ return "func_uniform == 0";
+ case kFuncUniformRetVal:
+ return "func_uniform_retval() == 0";
+ case kUniformBuffer:
+ return "u == 0";
+ case kROStorageBuffer:
+ return "ro == 0";
+ case kFuncLetNonUniformRhs:
+ return "let_nonuniform_rhs == 0";
+ case kFuncVarNonUniform:
+ return "func_non_uniform == 0";
+ case kFuncNonUniformRetVal:
+ return "func_nonuniform_retval() == 0";
+ case kRWStorageBuffer:
+ return "rw == 0";
+ case kEndOfConditionRange:
+ return "<invalid>";
+ }
+ return "<invalid>";
+ }
+
+ /// Convert a function call to its string representation.
+ static std::string FunctionToStr(Function f) {
+ switch (f) {
+ case kUserNoRestriction:
+ return "user_no_restriction()";
+ case kMin:
+ return "min(1, 1)";
+ case kTextureSampleLevel:
+ return "textureSampleLevel(t, s, vec2(0.5, 0.5), 0.0)";
+ case kUserRequiredToBeUniform:
+ return "user_required_to_be_uniform()";
+ case kWorkgroupBarrier:
+ return "workgroupBarrier()";
+ case kStorageBarrier:
+ return "storageBarrier()";
+ case kTextureSample:
+ return "textureSample(t, s, vec2(0.5, 0.5))";
+ case kTextureSampleBias:
+ return "textureSampleBias(t, s, vec2(0.5, 0.5), 2.0)";
+ case kTextureSampleCompare:
+ return "textureSampleCompare(td, sc, vec2(0.5, 0.5), 0.5)";
+ case kDpdx:
+ return "dpdx(1.0)";
+ case kDpdxCoarse:
+ return "dpdxCoarse(1.0)";
+ case kDpdxFine:
+ return "dpdxFine(1.0)";
+ case kDpdy:
+ return "dpdy(1.0)";
+ case kDpdyCoarse:
+ return "dpdyCoarse(1.0)";
+ case kDpdyFine:
+ return "dpdyFine(1.0)";
+ case kFwidth:
+ return "fwidth(1.0)";
+ case kFwidthCoarse:
+ return "fwidthCoarse(1.0)";
+ case kFwidthFine:
+ return "fwidthFine(1.0)";
+ case kEndOfFunctionRange:
+ return "<invalid>";
+ }
+ return "<invalid>";
+ }
+
+ /// @returns true if `c` is a condition that may be non-uniform.
+ static bool MayBeNonUniform(Condition c) { return c > kLastUniformCondition; }
+
+ /// @returns true if `f` is a function call that is required to be uniform.
+ static bool RequiredToBeUniform(Function f) { return f > kLastNoRestrictionFunction; }
+
+ /// Convert a test parameter pair of condition+function to a string that can be used as part of
+ /// a test name.
+ static std::string ParamsToName(::testing::TestParamInfo<ParamType> params) {
+ Condition c = static_cast<Condition>(std::get<0>(params.param));
+ Function f = static_cast<Function>(std::get<1>(params.param));
+ std::string name;
+#define CASE(c) \
+ case c: \
+ name += #c; \
+ break
+
+ switch (c) {
+ CASE(kTrue);
+ CASE(kFalse);
+ CASE(kLiteral);
+ CASE(kModuleLet);
+ CASE(kPipelineOverridable);
+ CASE(kFuncLetUniformRhs);
+ CASE(kFuncVarUniform);
+ CASE(kFuncUniformRetVal);
+ CASE(kUniformBuffer);
+ CASE(kROStorageBuffer);
+ CASE(kFuncLetNonUniformRhs);
+ CASE(kFuncVarNonUniform);
+ CASE(kFuncNonUniformRetVal);
+ CASE(kRWStorageBuffer);
+ case kEndOfConditionRange:
+ break;
+ }
+ name += "_";
+ switch (f) {
+ CASE(kUserNoRestriction);
+ CASE(kMin);
+ CASE(kTextureSampleLevel);
+ CASE(kUserRequiredToBeUniform);
+ CASE(kWorkgroupBarrier);
+ CASE(kStorageBarrier);
+ CASE(kTextureSample);
+ CASE(kTextureSampleBias);
+ CASE(kTextureSampleCompare);
+ CASE(kDpdx);
+ CASE(kDpdxCoarse);
+ CASE(kDpdxFine);
+ CASE(kDpdy);
+ CASE(kDpdyCoarse);
+ CASE(kDpdyFine);
+ CASE(kFwidth);
+ CASE(kFwidthCoarse);
+ CASE(kFwidthFine);
+ case kEndOfFunctionRange:
+ break;
+ }
+#undef CASE
+
+ return name;
+ }
+};
+
+// Test the uniformity constraints for a function call inside a conditional statement.
+TEST_P(BasicTest, ConditionalFunctionCall) {
+ auto condition = static_cast<Condition>(std::get<0>(GetParam()));
+ auto function = static_cast<Function>(std::get<1>(GetParam()));
+ auto src = R"(
+var<private> p : i32;
+var<workgroup> w : i32;
+@group(0) @binding(0) var<uniform> u : i32;
+@group(0) @binding(0) var<storage, read> ro : i32;
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+@group(1) @binding(0) var t : texture_2d<f32>;
+@group(1) @binding(1) var td : texture_depth_2d;
+@group(1) @binding(2) var s : sampler;
+@group(1) @binding(3) var sc : sampler_comparison;
+
+let module_let : i32 = 42;
+@id(42) override pipeline_overridable : i32;
+
+fn user_no_restriction() {}
+fn user_required_to_be_uniform() { workgroupBarrier(); }
+
+fn func_uniform_retval() -> i32 { return u; }
+fn func_nonuniform_retval() -> i32 { return rw; }
+
+fn foo() {
+ let let_uniform_rhs = 7;
+ let let_nonuniform_rhs = rw;
+
+ var func_uniform = 7;
+ var func_non_uniform = 7;
+ func_non_uniform = rw;
+
+ if ()" + ConditionToStr(condition) +
+ R"() {
+ )" + FunctionToStr(function) +
+ R"(;
+ }
+}
+)";
+
+ bool should_pass = !(MayBeNonUniform(condition) && RequiredToBeUniform(function));
+ RunTest(src, should_pass);
+ if (!should_pass) {
+ EXPECT_THAT(error_, ::testing::StartsWith("test:31:5 warning: "));
+ EXPECT_THAT(error_, ::testing::HasSubstr("must only be called from uniform control flow"));
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ UniformityAnalysisTest,
+ BasicTest,
+ ::testing::Combine(::testing::Range<int>(0, BasicTest::kEndOfConditionRange),
+ ::testing::Range<int>(0, BasicTest::kEndOfFunctionRange)),
+ BasicTest::ParamsToName);
+
+////////////////////////////////////////////////////////////////////////////////
+/// Test specific function and parameter tags that are not tested above.
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(UniformityAnalysisTest, SubsequentControlFlowMayBeNonUniform_Pass) {
+ // Call a function that causes subsequent control flow to be non-uniform, and then call another
+ // function that doesn't require uniformity.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+var<private> p : i32;
+
+fn foo() {
+ if (rw == 0) {
+ p = 42;
+ return;
+ }
+ p = 5;
+ return;
+}
+
+fn bar() {
+ if (p == 42) {
+ p = 7;
+ }
+}
+
+fn main() {
+ foo();
+ bar();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, SubsequentControlFlowMayBeNonUniform_Fail) {
+ // Call a function that causes subsequent control flow to be non-uniform, and then call another
+ // function that requires uniformity.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+var<private> p : i32;
+
+fn foo() {
+ if (rw == 0) {
+ p = 42;
+ return;
+ }
+ p = 5;
+ return;
+}
+
+fn main() {
+ foo();
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:17:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ParameterNoRestriction_Pass) {
+ // Pass a non-uniform value as an argument, and then try to use the return value for
+ // control-flow guarding a barrier.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+var<private> p : i32;
+
+fn foo(i : i32) -> i32 {
+ if (i == 0) {
+ // This assignment is non-uniform, but shouldn't affect the return value.
+ p = 42;
+ }
+ return 7;
+}
+
+fn bar() {
+ let x = foo(rw);
+ if (x == 7) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ParameterRequiredToBeUniform_Pass) {
+ // Pass a uniform value as an argument to a function that uses that parameter for control-flow
+ // guarding a barrier.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> ro : i32;
+
+fn foo(i : i32) {
+ if (i == 0) {
+ workgroupBarrier();
+ }
+}
+
+fn bar() {
+ foo(ro);
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ParameterRequiredToBeUniform_Fail) {
+ // Pass a non-uniform value as an argument to a function that uses that parameter for
+ // control-flow guarding a barrier.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo(i : i32) {
+ if (i == 0) {
+ workgroupBarrier();
+ }
+}
+
+fn bar() {
+ foo(rw);
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:11:7 warning: parameter 'i' of 'foo' must be uniform
+ foo(rw);
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ParameterRequiredToBeUniformForReturnValue_Pass) {
+ // Pass a uniform value as an argument to a function that uses that parameter to produce the
+ // return value, and then use the return value for control-flow guarding a barrier.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> ro : i32;
+
+fn foo(i : i32) -> i32 {
+ return 1 + i;
+}
+
+fn bar() {
+ if (foo(ro) == 7) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ParameterRequiredToBeUniformForReturnValue_Fail) {
+ // Pass a non-uniform value as an argument to a function that uses that parameter to produce the
+ // return value, and then use the return value for control-flow guarding a barrier.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo(i : i32) -> i32 {
+ return 1 + i;
+}
+
+fn bar() {
+ if (foo(rw) == 7) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ParameterRequiredToBeUniformForSubsequentControlFlow_Pass) {
+ // Pass a uniform value as an argument to a function that uses that parameter return early, and
+ // then invoke a barrier after calling that function.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> ro : i32;
+
+var<private> p : i32;
+
+fn foo(i : i32) {
+ if (i == 0) {
+ p = 42;
+ return;
+ }
+ p = 5;
+ return;
+}
+
+fn bar() {
+ foo(ro);
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ParameterRequiredToBeUniformForSubsequentControlFlow_Fail) {
+ // Pass a non-uniform value as an argument to a function that uses that parameter return early,
+ // and then invoke a barrier after calling that function.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+var<private> p : i32;
+
+fn foo(i : i32) {
+ if (i == 0) {
+ p = 42;
+ return;
+ }
+ p = 5;
+ return;
+}
+
+fn bar() {
+ foo(rw);
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:17:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Test shader IO attributes.
+////////////////////////////////////////////////////////////////////////////////
+
+struct BuiltinEntry {
+ std::string name;
+ std::string type;
+ bool uniform;
+ BuiltinEntry(std::string n, std::string t, bool u) : name(n), type(t), uniform(u) {}
+};
+
+class ComputeBuiltin : public UniformityAnalysisTestBase,
+ public ::testing::TestWithParam<BuiltinEntry> {};
+TEST_P(ComputeBuiltin, AsParam) {
+ auto src = R"(
+@stage(compute) @workgroup_size(64)
+fn main(@builtin()" +
+ GetParam().name + R"() b : )" + GetParam().type + R"() {
+ if (all(vec3(b) == vec3(0u))) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ bool should_pass = GetParam().uniform;
+ RunTest(src, should_pass);
+ if (!should_pass) {
+ EXPECT_EQ(
+ error_,
+ R"(test:5:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+ }
+}
+
+TEST_P(ComputeBuiltin, InStruct) {
+ auto src = R"(
+struct S {
+ @builtin()" + GetParam().name +
+ R"() b : )" + GetParam().type + R"(
+}
+
+@stage(compute) @workgroup_size(64)
+fn main(s : S) {
+ if (all(vec3(s.b) == vec3(0u))) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ bool should_pass = GetParam().uniform;
+ RunTest(src, should_pass);
+ if (!should_pass) {
+ EXPECT_EQ(
+ error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(UniformityAnalysisTest,
+ ComputeBuiltin,
+ ::testing::Values(BuiltinEntry{"local_invocation_id", "vec3<u32>", false},
+ BuiltinEntry{"local_invocation_index", "u32", false},
+ BuiltinEntry{"global_invocation_id", "vec3<u32>", false},
+ BuiltinEntry{"workgroup_id", "vec3<u32>", true},
+ BuiltinEntry{"num_workgroups", "vec3<u32>", true}),
+ [](const ::testing::TestParamInfo<ComputeBuiltin::ParamType>& p) {
+ return p.param.name;
+ });
+
+TEST_F(UniformityAnalysisTest, ComputeBuiltin_MixedAttributesInStruct) {
+ // Mix both non-uniform and uniform shader IO attributes in the same structure. Even accessing
+ // just uniform member causes non-uniformity in this case.
+ auto src = R"(
+struct S {
+ @builtin(num_workgroups) num_groups : vec3<u32>,
+ @builtin(local_invocation_index) idx : u32,
+}
+
+@stage(compute) @workgroup_size(64)
+fn main(s : S) {
+ if (s.num_groups.x == 0u) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+class FragmentBuiltin : public UniformityAnalysisTestBase,
+ public ::testing::TestWithParam<BuiltinEntry> {};
+TEST_P(FragmentBuiltin, AsParam) {
+ auto src = R"(
+@stage(fragment)
+fn main(@builtin()" +
+ GetParam().name + R"() b : )" + GetParam().type + R"() {
+ if (u32(vec4(b).x) == 0u) {
+ dpdx(0.5);
+ }
+}
+)";
+
+ bool should_pass = GetParam().uniform;
+ RunTest(src, should_pass);
+ if (!should_pass) {
+ EXPECT_EQ(error_,
+ R"(test:5:5 warning: 'dpdx' must only be called from uniform control flow
+ dpdx(0.5);
+ ^^^^
+)");
+ }
+}
+
+TEST_P(FragmentBuiltin, InStruct) {
+ auto src = R"(
+struct S {
+ @builtin()" + GetParam().name +
+ R"() b : )" + GetParam().type + R"(
+}
+
+@stage(fragment)
+fn main(s : S) {
+ if (u32(vec4(s.b).x) == 0u) {
+ dpdx(0.5);
+ }
+}
+)";
+
+ bool should_pass = GetParam().uniform;
+ RunTest(src, should_pass);
+ if (!should_pass) {
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'dpdx' must only be called from uniform control flow
+ dpdx(0.5);
+ ^^^^
+)");
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(UniformityAnalysisTest,
+ FragmentBuiltin,
+ ::testing::Values(BuiltinEntry{"position", "vec4<f32>", false},
+ BuiltinEntry{"front_facing", "bool", false},
+ BuiltinEntry{"sample_index", "u32", false},
+ BuiltinEntry{"sample_mask", "u32", false}),
+ [](const ::testing::TestParamInfo<FragmentBuiltin::ParamType>& p) {
+ return p.param.name;
+ });
+
+TEST_F(UniformityAnalysisTest, FragmentLocation) {
+ auto src = R"(
+@stage(fragment)
+fn main(@location(0) l : f32) {
+ if (l == 0.0) {
+ dpdx(0.5);
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:5:5 warning: 'dpdx' must only be called from uniform control flow
+ dpdx(0.5);
+ ^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, FragmentLocation_InStruct) {
+ auto src = R"(
+struct S {
+ @location(0) l : f32
+}
+
+@stage(fragment)
+fn main(s : S) {
+ if (s.l == 0.0) {
+ dpdx(0.5);
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'dpdx' must only be called from uniform control flow
+ dpdx(0.5);
+ ^^^^
+)");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Test loop conditions and conditional break/continue statements.
+////////////////////////////////////////////////////////////////////////////////
+
+namespace LoopTest {
+
+enum ControlFlowInterrupt {
+ kBreak,
+ kContinue,
+ kReturn,
+ kDiscard,
+};
+enum Condition {
+ kNone,
+ kUniform,
+ kNonUniform,
+};
+
+using LoopTestParams = std::tuple<int, int>;
+
+static std::string ToStr(ControlFlowInterrupt interrupt) {
+ switch (interrupt) {
+ case kBreak:
+ return "break";
+ case kContinue:
+ return "continue";
+ case kReturn:
+ return "return";
+ case kDiscard:
+ return "discard";
+ }
+ return "";
+}
+
+static std::string ToStr(Condition condition) {
+ switch (condition) {
+ case kNone:
+ return "uncondtiional";
+ case kUniform:
+ return "uniform";
+ case kNonUniform:
+ return "nonuniform";
+ }
+ return "";
+}
+
+class LoopTest : public UniformityAnalysisTestBase,
+ public ::testing::TestWithParam<LoopTestParams> {
+ protected:
+ std::string MakeInterrupt(ControlFlowInterrupt interrupt, Condition condition) {
+ switch (condition) {
+ case kNone:
+ return ToStr(interrupt);
+ case kUniform:
+ return "if (uniform_var == 42) { " + ToStr(interrupt) + "; }";
+ case kNonUniform:
+ return "if (nonuniform_var == 42) { " + ToStr(interrupt) + "; }";
+ }
+ return "<invalid>";
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(UniformityAnalysisTest,
+ LoopTest,
+ ::testing::Combine(::testing::Range<int>(0, kDiscard + 1),
+ ::testing::Range<int>(0, kNonUniform + 1)),
+ [](const ::testing::TestParamInfo<LoopTestParams>& p) {
+ ControlFlowInterrupt interrupt =
+ static_cast<ControlFlowInterrupt>(std::get<0>(p.param));
+ auto condition = static_cast<Condition>(std::get<1>(p.param));
+ return ToStr(interrupt) + "_" + ToStr(condition);
+ });
+
+TEST_P(LoopTest, CallInBody_InterruptAfter) {
+ // Test control-flow interrupt in a loop after a function call that requires uniform control
+ // flow.
+ auto interrupt = static_cast<ControlFlowInterrupt>(std::get<0>(GetParam()));
+ auto condition = static_cast<Condition>(std::get<1>(GetParam()));
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> uniform_var : i32;
+@group(0) @binding(0) var<storage, read_write> nonuniform_var : i32;
+
+fn foo() {
+ loop {
+ // Pretend that this isn't an infinite loop, in case the interrupt is a
+ // continue statement.
+ if (false) {
+ break;
+ }
+
+ workgroupBarrier();
+ )" + MakeInterrupt(interrupt, condition) +
+ R"(;
+ }
+}
+)";
+
+ if (condition == kNonUniform) {
+ RunTest(src, false);
+ EXPECT_EQ(
+ error_,
+ R"(test:13:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+ } else {
+ RunTest(src, true);
+ }
+}
+
+TEST_P(LoopTest, CallInBody_InterruptBefore) {
+ // Test control-flow interrupt in a loop before a function call that requires uniform control
+ // flow.
+ auto interrupt = static_cast<ControlFlowInterrupt>(std::get<0>(GetParam()));
+ auto condition = static_cast<Condition>(std::get<1>(GetParam()));
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> uniform_var : i32;
+@group(0) @binding(0) var<storage, read_write> nonuniform_var : i32;
+
+fn foo() {
+ loop {
+ // Pretend that this isn't an infinite loop, in case the interrupt is a
+ // continue statement.
+ if (false) {
+ break;
+ }
+
+ )" + MakeInterrupt(interrupt, condition) +
+ R"(;
+ workgroupBarrier();
+ }
+}
+)";
+
+ if (condition == kNonUniform) {
+ RunTest(src, false);
+ EXPECT_EQ(
+ error_,
+ R"(test:14:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+ } else {
+ RunTest(src, true);
+ }
+}
+
+TEST_P(LoopTest, CallInContinuing_InterruptInBody) {
+ // Test control-flow interrupt in a loop with a function call that requires uniform control flow
+ // in the continuing statement.
+ auto interrupt = static_cast<ControlFlowInterrupt>(std::get<0>(GetParam()));
+ auto condition = static_cast<Condition>(std::get<1>(GetParam()));
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> uniform_var : i32;
+@group(0) @binding(0) var<storage, read_write> nonuniform_var : i32;
+
+fn foo() {
+ loop {
+ // Pretend that this isn't an infinite loop, in case the interrupt is a
+ // continue statement.
+ if (false) {
+ break;
+ }
+
+ )" + MakeInterrupt(interrupt, condition) +
+ R"(;
+ continuing {
+ workgroupBarrier();
+ }
+ }
+}
+)";
+
+ if (condition == kNonUniform) {
+ RunTest(src, false);
+ EXPECT_EQ(
+ error_,
+ R"(test:15:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+ } else {
+ RunTest(src, true);
+ }
+}
+
+TEST_F(UniformityAnalysisTest, Loop_CallInBody_UniformBreakInContinuing) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> n : i32;
+
+fn foo() {
+ var i = 0;
+ loop {
+ workgroupBarrier();
+ continuing {
+ i = i + 1;
+ if (i == n) {
+ break;
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_CallInBody_NonUniformBreakInContinuing) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn foo() {
+ var i = 0;
+ loop {
+ workgroupBarrier();
+ continuing {
+ i = i + 1;
+ if (i == n) {
+ break;
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:7:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_CallInContinuing_UniformBreakInContinuing) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> n : i32;
+
+fn foo() {
+ var i = 0;
+ loop {
+ continuing {
+ workgroupBarrier();
+ i = i + 1;
+ if (i == n) {
+ break;
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_CallInContinuing_NonUniformBreakInContinuing) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn foo() {
+ var i = 0;
+ loop {
+ continuing {
+ workgroupBarrier();
+ i = i + 1;
+ if (i == n) {
+ break;
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+class LoopDeadCodeTest : public UniformityAnalysisTestBase, public ::testing::TestWithParam<int> {};
+
+INSTANTIATE_TEST_SUITE_P(UniformityAnalysisTest,
+ LoopDeadCodeTest,
+ ::testing::Range<int>(0, kDiscard + 1),
+ [](const ::testing::TestParamInfo<LoopDeadCodeTest::ParamType>& p) {
+ return ToStr(static_cast<ControlFlowInterrupt>(p.param));
+ });
+
+TEST_P(LoopDeadCodeTest, AfterInterrupt) {
+ // Dead code after a control-flow interrupt in a loop shouldn't cause uniformity errors.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn foo() {
+ loop {
+ )" + ToStr(static_cast<ControlFlowInterrupt>(GetParam())) +
+ R"(;
+ if (n == 42) {
+ workgroupBarrier();
+ }
+ continuing {
+ // Pretend that this isn't an infinite loop, in case the interrupt is a
+ // continue statement.
+ if (false) {
+ break;
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_VarBecomesNonUniformInLoopAfterBarrier) {
+ // Use a variable for a conditional barrier in a loop, and then assign a non-uniform value to
+ // that variable later in that loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ v = non_uniform;
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_VarBecomesNonUniformInLoopAfterBarrier_BreakAtEnd) {
+ // Use a variable for a conditional barrier in a loop, and then assign a non-uniform value to
+ // that variable later in that loop. End the loop with a break statement to prevent the
+ // non-uniform value from causing an issue.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (v == 0) {
+ workgroupBarrier();
+ }
+
+ v = non_uniform;
+ break;
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_ConditionalAssignNonUniformWithBreak_BarrierInLoop) {
+ // In a conditional block, assign a non-uniform value and then break, then use a variable for a
+ // conditional barrier later in the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (true) {
+ v = non_uniform;
+ break;
+ }
+ if (v == 0) {
+ workgroupBarrier();
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_ConditionalAssignNonUniformWithConditionalBreak_BarrierInLoop) {
+ // In a conditional block, assign a non-uniform value and then conditionally break, then use a
+ // variable for a conditional barrier later in the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (true) {
+ v = non_uniform;
+ if (true) {
+ break;
+ }
+ }
+ if (v == 0) {
+ workgroupBarrier();
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_ConditionalAssignNonUniformWithBreak_BarrierAfterLoop) {
+ // In a conditional block, assign a non-uniform value and then break, then use a variable for a
+ // conditional barrier after the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (true) {
+ v = non_uniform;
+ break;
+ }
+ v = 5;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:15:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_VarBecomesUniformBeforeSomeExits_BarrierAfterLoop) {
+ // Assign a non-uniform value, have two exit points only one of which assigns a uniform value,
+ // then use a variable for a conditional barrier after the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (true) {
+ break;
+ }
+
+ v = non_uniform;
+
+ if (false) {
+ v = 6;
+ break;
+ }
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:20:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_VarBecomesUniformBeforeAllExits_BarrierAfterLoop) {
+ // Assign a non-uniform value, have two exit points both of which assigns a uniform value,
+ // then use a variable for a conditional barrier after the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (true) {
+ v = 5;
+ break;
+ }
+
+ v = non_uniform;
+
+ if (false) {
+ v = 6;
+ break;
+ }
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_AssignNonUniformBeforeConditionalBreak_BarrierAfterLoop) {
+ // Assign a non-uniform value and then break in a conditional block, then use a variable for a
+ // conditional barrier after the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ v = non_uniform;
+ if (true) {
+ if (false) {
+ v = 5;
+ } else {
+ break;
+ }
+ v = 5;
+ }
+ v = 5;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:20:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_VarBecomesNonUniformBeforeConditionalContinue_BarrierAtStart) {
+ // Use a variable for a conditional barrier in a loop, assign a non-uniform value to
+ // that variable later in that loop, then perform a conditional continue before assigning a
+ // uniform value to that variable.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ v = non_uniform;
+ if (true) {
+ continue;
+ }
+
+ v = 5;
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ Loop_VarBecomesUniformBeforeConditionalContinue_BarrierInContinuing) {
+ // Use a variable for a conditional barrier in the continuing statement of a loop, assign a
+ // non-uniform value to that variable later in that loop, then conditionally assign a uniform
+ // value before continuing.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ v = non_uniform;
+
+ if (false) {
+ v = 5;
+ continue;
+ }
+
+ continuing {
+ if (v == 0) {
+ workgroupBarrier();
+ }
+ if (true) {
+ break;
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:16:9 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_VarBecomesNonUniformBeforeConditionalContinue) {
+ // Use a variable for a conditional barrier in a loop, assign a non-uniform value to
+ // that variable later in that loop, then perform a conditional continue before assigning a
+ // uniform value to that variable.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ v = non_uniform;
+ if (true) {
+ continue;
+ }
+
+ v = 5;
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Loop_VarBecomesNonUniformInNestedLoopWithBreak_BarrierInLoop) {
+ // Use a variable for a conditional barrier in a loop, then conditionally assign a non-uniform
+ // value to that variable followed by a break in a nested loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ loop {
+ if (true) {
+ v = non_uniform;
+ break;
+ }
+ v = 5;
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ Loop_VarBecomesNonUniformInNestedLoopWithBreak_BecomesUniformAgain_BarrierAfterLoop) {
+ // Conditionally assign a non-uniform value followed by a break in a nested loop, assign a
+ // uniform value in the outer loop, and then use a variable for a conditional barrier after the
+ // loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ loop {
+ if (false) {
+ break;
+ }
+
+ loop {
+ if (true) {
+ v = non_uniform;
+ break;
+ }
+ }
+ v = 5;
+ }
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_NonUniformValueNeverReachesContinuing) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ loop {
+ var v = non_uniform;
+ return;
+
+ continuing {
+ if (v == 0) {
+ workgroupBarrier();
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_NonUniformBreakInBody_Reconverge) {
+ // Loops reconverge at exit, so test that we can call workgroupBarrier() after a loop that
+ // contains a non-uniform conditional break.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn foo() {
+ var i = 0;
+ loop {
+ if (i == n) {
+ break;
+ }
+ i = i + 1;
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_NonUniformFunctionInBody_Reconverge) {
+ // Loops reconverge at exit, so test that we can call workgroupBarrier() after a loop that
+ // contains a call to a function that causes non-uniform control flow.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn bar() {
+ if (n == 42) {
+ return;
+ } else {
+ return;
+ }
+}
+
+fn foo() {
+ loop {
+ bar();
+ break;
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Loop_NonUniformFunctionDiscard_NoReconvergence) {
+ // Loops should not reconverge after non-uniform discard statements.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn bar() {
+ if (n == 42) {
+ discard;
+ }
+}
+
+fn foo() {
+ loop {
+ bar();
+ break;
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:15:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_CallInside_UniformCondition) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> n : i32;
+
+fn foo() {
+ for (var i = 0; i < n; i = i + 1) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_CallInside_NonUniformCondition) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn foo() {
+ for (var i = 0; i < n; i = i + 1) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_CallInside_InitializerCausesNonUniformFlow) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn bar() -> i32 {
+ if (n == 42) {
+ return 1;
+ } else {
+ return 2;
+ }
+}
+
+fn foo() {
+ for (var i = bar(); i < 10; i = i + 1) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_CallInside_ContinuingCausesNonUniformFlow) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn bar() -> i32 {
+ if (n == 42) {
+ return 1;
+ } else {
+ return 2;
+ }
+}
+
+fn foo() {
+ for (var i = 0; i < 10; i = i + bar()) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_VarBecomesNonUniformInContinuing_BarrierInLoop) {
+ // Use a variable for a conditional barrier in a loop, and then assign a non-uniform value to
+ // that variable in the continuing statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; v = non_uniform) {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_VarBecomesUniformInContinuing_BarrierInLoop) {
+ // Use a variable for a conditional barrier in a loop, and then assign a uniform value to that
+ // variable in the continuing statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; v = 5) {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ v = non_uniform;
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_VarBecomesNonUniformInContinuing_BarrierAfterLoop) {
+ // Use a variable for a conditional barrier after a loop, and assign a non-uniform value to
+ // that variable in the continuing statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; v = non_uniform) {
+ v = 5;
+ }
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_VarBecomesUniformInContinuing_BarrierAfterLoop) {
+ // Use a variable for a conditional barrier after a loop, and assign a uniform value to that
+ // variable in the continuing statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; v = 5) {
+ v = non_uniform;
+ }
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_VarBecomesNonUniformInLoopAfterBarrier) {
+ // Use a variable for a conditional barrier in a loop, and then assign a non-uniform value to
+ // that variable later in that loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; i++) {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ v = non_uniform;
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_ConditionalAssignNonUniformWithBreak_BarrierInLoop) {
+ // In a conditional block, assign a non-uniform value and then break, then use a variable for a
+ // conditional barrier later in the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; i++) {
+ if (true) {
+ v = non_uniform;
+ break;
+ }
+ if (v == 0) {
+ workgroupBarrier();
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_ConditionalAssignNonUniformWithBreak_BarrierAfterLoop) {
+ // In a conditional block, assign a non-uniform value and then break, then use a variable for a
+ // conditional barrier after the loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; i++) {
+ if (true) {
+ v = non_uniform;
+ break;
+ }
+ v = 5;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:15:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_VarRemainsNonUniformAtLoopEnd_BarrierAfterLoop) {
+ // Assign a non-uniform value, assign a uniform value before all explicit break points but leave
+ // the value non-uniform at loop exit, then use a variable for a conditional barrier after the
+ // loop.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; i++) {
+ if (true) {
+ v = 5;
+ break;
+ }
+
+ v = non_uniform;
+
+ if (true) {
+ v = 6;
+ break;
+ }
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:21:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ ForLoop_VarBecomesNonUniformBeforeConditionalContinue_BarrierAtStart) {
+ // Use a variable for a conditional barrier in a loop, assign a non-uniform value to
+ // that variable later in that loop, then perform a conditional continue before assigning a
+ // uniform value to that variable.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; i++) {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ v = non_uniform;
+ if (true) {
+ continue;
+ }
+
+ v = 5;
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_VarBecomesNonUniformBeforeConditionalContinue) {
+ // Use a variable for a conditional barrier in a loop, assign a non-uniform value to
+ // that variable later in that loop, then perform a conditional continue before assigning a
+ // uniform value to that variable.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ for (var i = 0; i < 10; i++) {
+ if (v == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ v = non_uniform;
+ if (true) {
+ continue;
+ }
+
+ v = 5;
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ForLoop_NonUniformCondition_Reconverge) {
+ // Loops reconverge at exit, so test that we can call workgroupBarrier() after a loop that has a
+ // non-uniform condition.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn foo() {
+ for (var i = 0; i < n; i = i + 1) {
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+} // namespace LoopTest
+
+////////////////////////////////////////////////////////////////////////////////
+/// If-else statement tests.
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(UniformityAnalysisTest, IfElse_UniformCondition_BarrierInTrueBlock) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> uniform_global : i32;
+
+fn foo() {
+ if (uniform_global == 42) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_UniformCondition_BarrierInElseBlock) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> uniform_global : i32;
+
+fn foo() {
+ if (uniform_global == 42) {
+ } else {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_UniformCondition_BarrierInElseIfBlock) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> uniform_global : i32;
+
+fn foo() {
+ if (uniform_global == 42) {
+ } else if (true) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_NonUniformCondition_BarrierInTrueBlock) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (non_uniform == 42) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_NonUniformCondition_BarrierInElseBlock) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (non_uniform == 42) {
+ } else {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:7:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_ShortCircuitingCondition_NonUniformLHS_And) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+var<private> p : i32;
+
+fn main() {
+ if ((non_uniform_global == 42) && false) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_ShortCircuitingCondition_NonUniformRHS_And) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+var<private> p : i32;
+
+fn main() {
+ if (false && (non_uniform_global == 42)) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_ShortCircuitingCondition_NonUniformLHS_Or) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+var<private> p : i32;
+
+fn main() {
+ if ((non_uniform_global == 42) || true) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_ShortCircuitingCondition_NonUniformRHS_Or) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+var<private> p : i32;
+
+fn main() {
+ if (true || (non_uniform_global == 42)) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_NonUniformCondition_BarrierInElseIfBlock) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (non_uniform == 42) {
+ } else if (true) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:7:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_VarBecomesNonUniform_BeforeCondition) {
+ // Use a function-scope variable for control-flow guarding a barrier, and then assign to that
+ // variable before checking the condition.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v = 0;
+ v = rw;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_VarBecomesNonUniform_AfterCondition) {
+ // Use a function-scope variable for control-flow guarding a barrier, and then assign to that
+ // variable after checking the condition.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v = 0;
+ if (v == 0) {
+ v = rw;
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_VarBecomesNonUniformInIf_BarrierInElse) {
+ // Assign a non-uniform value to a variable in an if-block, and then use that variable for a
+ // conditional barrier in the else block.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ if (true) {
+ v = non_uniform;
+ } else {
+ if (v == 0) {
+ workgroupBarrier();
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_AssignNonUniformInIf_AssignUniformInElse) {
+ // Assign a non-uniform value to a variable in an if-block and a uniform value in the else
+ // block, and then use that variable for a conditional barrier after the if-else statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ if (true) {
+ if (true) {
+ v = non_uniform;
+ } else {
+ v = 5;
+ }
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:15:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_AssignNonUniformInIfWithReturn) {
+ // Assign a non-uniform value to a variable in an if-block followed by a return, and then use
+ // that variable for a conditional barrier after the if-else statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ if (true) {
+ v = non_uniform;
+ return;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_AssignNonUniformBeforeIf_BothBranchesAssignUniform) {
+ // Assign a non-uniform value to a variable before and if-else statement, assign uniform values
+ // in both branch of the if-else, and then use that variable for a conditional barrier after
+ // the if-else statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ v = non_uniform;
+ if (true) {
+ v = 5;
+ } else {
+ v = 6;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_AssignNonUniformBeforeIf_OnlyTrueBranchAssignsUniform) {
+ // Assign a non-uniform value to a variable before and if-else statement, assign a uniform value
+ // in the true branch of the if-else, and then use that variable for a conditional barrier after
+ // the if-else statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ v = non_uniform;
+ if (true) {
+ v = 5;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:12:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_AssignNonUniformBeforeIf_OnlyFalseBranchAssignsUniform) {
+ // Assign a non-uniform value to a variable before and if-else statement, assign a uniform value
+ // in the false branch of the if-else, and then use that variable for a conditional barrier
+ // after the if-else statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ v = non_uniform;
+ if (true) {
+ } else {
+ v = 5;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:13:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ IfElse_AssignNonUniformBeforeIf_OnlyTrueBranchAssignsUniform_FalseBranchReturns) {
+ // Assign a non-uniform value to a variable before and if-else statement, assign a uniform value
+ // in the true branch of the if-else, leave the variable untouched in the false branch and just
+ // return, and then use that variable for a conditional barrier after the if-else statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ v = non_uniform;
+ if (true) {
+ v = 5;
+ } else {
+ return;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ IfElse_AssignNonUniformBeforeIf_OnlyFalseBranchAssignsUniform_TrueBranchReturns) {
+ // Assign a non-uniform value to a variable before and if-else statement, assign a uniform value
+ // in the false branch of the if-else, leave the variable untouched in the true branch and just
+ // return, and then use that variable for a conditional barrier after the if-else statement.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ v = non_uniform;
+ if (true) {
+ return;
+ } else {
+ v = 5;
+ }
+
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_NonUniformCondition_Reconverge) {
+ // If statements reconverge at exit, so test that we can call workgroupBarrier() after an if
+ // statement with a non-uniform condition.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (non_uniform == 42) {
+ } else {
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_ShortCircuitingNonUniformConditionLHS_Reconverge) {
+ // If statements reconverge at exit, so test that we can call workgroupBarrier() after an if
+ // statement with a non-uniform condition that uses short-circuiting.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (non_uniform == 42 || true) {
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_ShortCircuitingNonUniformConditionRHS_Reconverge) {
+ // If statements reconverge at exit, so test that we can call workgroupBarrier() after an if
+ // statement with a non-uniform condition that uses short-circuiting.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (false && non_uniform == 42) {
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_NonUniformFunctionCall_Reconverge) {
+ // If statements reconverge at exit, so test that we can call workgroupBarrier() after an if
+ // statement with a non-uniform condition.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar() {
+ if (non_uniform == 42) {
+ return;
+ } else {
+ return;
+ }
+}
+
+fn foo() {
+ if (non_uniform == 42) {
+ bar();
+ } else {
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_NonUniformReturn_NoReconverge) {
+ // If statements should not reconverge after non-uniform returns.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (non_uniform == 42) {
+ return;
+ } else {
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IfElse_NonUniformDiscard_NoReconverge) {
+ // If statements should not reconverge after non-uniform discards.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ if (non_uniform == 42) {
+ discard;
+ } else {
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Switch statement tests.
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformCondition_BarrierInCase) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ switch (non_uniform) {
+ case 42: {
+ workgroupBarrier();
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:7:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformCondition_BarrierInDefault) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ switch (non_uniform) {
+ default: {
+ workgroupBarrier();
+ break;
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:7:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformBreak) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ switch (condition) {
+ case 42: {
+ if (non_uniform == 42) {
+ break;
+ }
+ workgroupBarrier();
+ }
+ default: {
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:11:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformBreakInDifferentCase) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ switch (condition) {
+ case 0: {
+ if (non_uniform == 42) {
+ break;
+ }
+ }
+ case 42: {
+ workgroupBarrier();
+ }
+ default: {
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformBreakInDifferentCase_Fallthrough) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ switch (condition) {
+ case 0: {
+ if (non_uniform == 42) {
+ break;
+ }
+ fallthrough;
+ }
+ case 42: {
+ workgroupBarrier();
+ }
+ default: {
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesNonUniformInDifferentCase_WithBreak) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = 0;
+ switch (condition) {
+ case 0: {
+ x = non_uniform;
+ break;
+ }
+ case 42: {
+ if (x == 0) {
+ workgroupBarrier();
+ }
+ }
+ default: {
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesNonUniformInDifferentCase_WithFallthrough) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = 0;
+ switch (condition) {
+ case 0: {
+ x = non_uniform;
+ fallthrough;
+ }
+ case 42: {
+ if (x == 0) {
+ workgroupBarrier();
+ }
+ }
+ default: {
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:9 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesUniformInDifferentCase_WithBreak) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = non_uniform;
+ switch (condition) {
+ case 0: {
+ x = 5;
+ break;
+ }
+ case 42: {
+ if (x == 0) {
+ workgroupBarrier();
+ }
+ }
+ default: {
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:9 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesUniformInDifferentCase_WithFallthrough) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = non_uniform;
+ switch (condition) {
+ case 0: {
+ x = 5;
+ fallthrough;
+ }
+ case 42: {
+ if (x == 0) {
+ workgroupBarrier();
+ }
+ }
+ default: {
+ }
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesNonUniformInCase_BarrierAfter) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = 0;
+ switch (condition) {
+ case 0: {
+ x = non_uniform;
+ }
+ case 42: {
+ x = 5;
+ }
+ default: {
+ x = 6;
+ }
+ }
+ if (x == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:19:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesUniformInAllCases_BarrierAfter) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = non_uniform;
+ switch (condition) {
+ case 0: {
+ x = 4;
+ }
+ case 42: {
+ x = 5;
+ }
+ default: {
+ x = 6;
+ }
+ }
+ if (x == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesUniformInSomeCases_BarrierAfter) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = non_uniform;
+ switch (condition) {
+ case 0: {
+ x = 4;
+ }
+ case 42: {
+ }
+ default: {
+ x = 6;
+ }
+ }
+ if (x == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:18:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesUniformInCasesThatDontReturn_BarrierAfter) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = non_uniform;
+ switch (condition) {
+ case 0: {
+ x = 4;
+ }
+ case 42: {
+ return;
+ }
+ default: {
+ x = 6;
+ }
+ }
+ if (x == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_VarBecomesUniformAfterConditionalBreak_BarrierAfter) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = non_uniform;
+ switch (condition) {
+ case 0: {
+ x = 4;
+ }
+ case 42: {
+ }
+ default: {
+ if (false) {
+ break;
+ }
+ x = 6;
+ }
+ }
+ if (x == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:21:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NestedInLoop_VarBecomesNonUniformWithBreak_BarrierInLoop) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = 0;
+ loop {
+ if (x == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ switch (condition) {
+ case 0: {
+ x = non_uniform;
+ break;
+ }
+ default: {
+ x = 6;
+ }
+ }
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NestedInLoop_VarBecomesNonUniformWithBreak_BarrierAfterLoop) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(0) var<uniform> condition : i32;
+
+fn foo() {
+ var x = 0;
+ loop {
+ if (false) {
+ break;
+ }
+ switch (condition) {
+ case 0: {
+ x = non_uniform;
+ break;
+ }
+ default: {
+ x = 6;
+ }
+ }
+ x = 5;
+ }
+ if (x == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformCondition_Reconverge) {
+ // Switch statements reconverge at exit, so test that we can call workgroupBarrier() after a
+ // switch statement that contains a non-uniform conditional break.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ switch (non_uniform) {
+ default: {
+ break;
+ }
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformBreak_Reconverge) {
+ // Switch statements reconverge at exit, so test that we can call workgroupBarrier() after a
+ // switch statement that contains a non-uniform conditional break.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ switch (42) {
+ default: {
+ if (non_uniform == 0) {
+ break;
+ }
+ break;
+ }
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformFunctionCall_Reconverge) {
+ // Switch statements reconverge at exit, so test that we can call workgroupBarrier() after a
+ // switch statement that contains a call to a function that causes non-uniform control flow.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn bar() {
+ if (n == 42) {
+ return;
+ } else {
+ return;
+ }
+}
+
+fn foo() {
+ switch (42) {
+ default: {
+ bar();
+ break;
+ }
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, Switch_NonUniformFunctionDiscard_NoReconvergence) {
+ // Switch statements should not reconverge after non-uniform discards.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> n : i32;
+
+fn bar() {
+ if (n == 42) {
+ discard;
+ }
+}
+
+fn foo() {
+ switch (42) {
+ default: {
+ bar();
+ break;
+ }
+ }
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:17:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Pointer tests.
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThroughPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ *&v = non_uniform;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThroughCapturedPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ let pv = &v;
+ *pv = non_uniform;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThroughPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = non_uniform;
+ *&v = 42;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThroughCapturedPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = non_uniform;
+ let pv = &v;
+ *pv = 42;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThroughCapturedPointer_InNonUniformControlFlow) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ let pv = &v;
+ if (non_uniform == 0) {
+ *pv = 42;
+ }
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:11:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, LoadNonUniformThroughPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = non_uniform;
+ if (*&v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:7:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, LoadNonUniformThroughCapturedPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = non_uniform;
+ let pv = &v;
+ if (*pv == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, LoadUniformThroughPointer) {
+ auto src = R"(
+fn foo() {
+ var v = 42;
+ if (*&v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, LoadUniformThroughCapturedPointer) {
+ auto src = R"(
+fn foo() {
+ var v = 42;
+ let pv = &v;
+ if (*pv == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, StoreNonUniformAfterCapturingPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ let pv = &v;
+ v = non_uniform;
+ if (*pv == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StoreUniformAfterCapturingPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = non_uniform;
+ let pv = &v;
+ v = 42;
+ if (*pv == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThroughLongChainOfPointers) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ let pv1 = &*&v;
+ let pv2 = &*&*pv1;
+ *&*&*pv2 = non_uniform;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, LoadNonUniformThroughLongChainOfPointers) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = non_uniform;
+ let pv1 = &*&v;
+ let pv2 = &*&*pv1;
+ if (*&*&*pv2 == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThenNonUniformThroughDifferentPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ let pv1 = &v;
+ let pv2 = &v;
+ *pv1 = 42;
+ *pv2 = non_uniform;
+ if (*pv1 == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:11:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThenUniformThroughDifferentPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ var v = 0;
+ let pv1 = &v;
+ let pv2 = &v;
+ *pv1 = non_uniform;
+ *pv2 = 42;
+ if (*pv1 == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, UnmodifiedPointerParameterNonUniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+}
+
+fn foo() {
+ var v = non_uniform;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:11:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, UnmodifiedPointerParameterUniform) {
+ auto src = R"(
+fn bar(p : ptr<function, i32>) {
+}
+
+fn foo() {
+ var v = 42;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThroughPointerInFunctionCall) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+ *p = non_uniform;
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:12:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThroughPointerInFunctionCall) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+ *p = 42;
+}
+
+fn foo() {
+ var v = non_uniform;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThroughPointerInFunctionCallViaArg) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>, a : i32) {
+ *p = a;
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v, non_uniform);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:12:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThroughPointerInFunctionCallViaPointerArg) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>, a : ptr<function, i32>) {
+ *p = *a;
+}
+
+fn foo() {
+ var v = 0;
+ var a = non_uniform;
+ bar(&v, &a);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:13:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThroughPointerInFunctionCallViaArg) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>, a : i32) {
+ *p = a;
+}
+
+fn foo() {
+ var v = non_uniform;
+ bar(&v, 42);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThroughPointerInFunctionCallViaPointerArg) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>, a : ptr<function, i32>) {
+ *p = *a;
+}
+
+fn foo() {
+ var v = non_uniform;
+ var a = 42;
+ bar(&v, &a);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignNonUniformThroughPointerInFunctionCallChain) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn f3(p : ptr<function, i32>, a : ptr<function, i32>) {
+ *p = *a;
+}
+
+fn f2(p : ptr<function, i32>, a : ptr<function, i32>) {
+ f3(p, a);
+}
+
+fn f1(p : ptr<function, i32>, a : ptr<function, i32>) {
+ f2(p, a);
+}
+
+fn foo() {
+ var v = 0;
+ var a = non_uniform;
+ f1(&v, &a);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:21:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignUniformThroughPointerInFunctionCallChain) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn f3(p : ptr<function, i32>, a : ptr<function, i32>) {
+ *p = *a;
+}
+
+fn f2(p : ptr<function, i32>, a : ptr<function, i32>) {
+ f3(p, a);
+}
+
+fn f1(p : ptr<function, i32>, a : ptr<function, i32>) {
+ f2(p, a);
+}
+
+fn foo() {
+ var v = non_uniform;
+ var a = 42;
+ f1(&v, &a);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, MakePointerParamUniformInReturnExpression) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn zoo(p : ptr<function, i32>) -> i32 {
+ *p = 5;
+ return 6;
+}
+
+fn bar(p : ptr<function, i32>) -> i32 {
+ *p = non_uniform;
+ return zoo(p);
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, MakePointerParamNonUniformInReturnExpression) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn zoo(p : ptr<function, i32>) -> i32 {
+ *p = non_uniform;
+ return 6;
+}
+
+fn bar(p : ptr<function, i32>) -> i32 {
+ *p = 5;
+ return zoo(p);
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:18:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, PointerParamAssignNonUniformInTrueAndUniformInFalse) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+ if (true) {
+ *p = non_uniform;
+ } else {
+ *p = 5;
+ }
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:16:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ConditionalAssignNonUniformToPointerParamAndReturn) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+ if (true) {
+ *p = non_uniform;
+ return;
+ }
+ *p = 5;
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:16:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ConditionalAssignNonUniformToPointerParamAndBreakFromSwitch) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(1) var<uniform> condition : i32;
+
+fn bar(p : ptr<function, i32>) {
+ switch (condition) {
+ case 0 {
+ if (true) {
+ *p = non_uniform;
+ break;
+ }
+ *p = 5;
+ }
+ default {
+ *p = 6;
+ }
+ }
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:24:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ConditionalAssignNonUniformToPointerParamAndBreakFromLoop) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+ loop {
+ if (true) {
+ *p = non_uniform;
+ break;
+ }
+ *p = 5;
+ }
+}
+
+fn foo() {
+ var v = 0;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:18:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ConditionalAssignNonUniformToPointerParamAndContinue) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo(p : ptr<function, i32>) {
+ loop {
+ if (*p == 0) {
+ workgroupBarrier();
+ break;
+ }
+
+ if (true) {
+ *p = non_uniform;
+ continue;
+ }
+ *p = 5;
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:7:7 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, PointerParamMaybeBecomesUniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+ if (true) {
+ *p = 5;
+ return;
+ }
+}
+
+fn foo() {
+ var v = non_uniform;
+ bar(&v);
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:15:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, NonUniformPointerParameterBecomesUniform_AfterUse) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(a : ptr<function, i32>, b : ptr<function, i32>) {
+ *b = *a;
+ *a = 0;
+}
+
+fn foo() {
+ var a = non_uniform;
+ var b = 0;
+ bar(&a, &b);
+ if (b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, NonUniformPointerParameterBecomesUniform_BeforeUse) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(a : ptr<function, i32>, b : ptr<function, i32>) {
+ *a = 0;
+ *b = *a;
+}
+
+fn foo() {
+ var a = non_uniform;
+ var b = 0;
+ bar(&a, &b);
+ if (b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, UniformPointerParameterBecomesNonUniform_BeforeUse) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(a : ptr<function, i32>, b : ptr<function, i32>) {
+ *a = non_uniform;
+ *b = *a;
+}
+
+fn foo() {
+ var a = 0;
+ var b = 0;
+ bar(&a, &b);
+ if (b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, UniformPointerParameterBecomesNonUniform_AfterUse) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(a : ptr<function, i32>, b : ptr<function, i32>) {
+ *b = *a;
+ *a = non_uniform;
+}
+
+fn foo() {
+ var a = 0;
+ var b = 0;
+ bar(&a, &b);
+ if (b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, NonUniformPointerParameterUpdatedInPlace) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) {
+ (*p)++;
+}
+
+fn foo() {
+ var v = non_uniform;
+ bar(&v);
+ if (v == 1) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:12:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MultiplePointerParametersBecomeNonUniform) {
+ // The analysis traverses the tree for each pointer parameter, and we need to make sure that we
+ // reset the "visited" state of nodes in between these traversals to properly capture each of
+ // their uniformity states.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(a : ptr<function, i32>, b : ptr<function, i32>) {
+ *a = non_uniform;
+ *b = non_uniform;
+}
+
+fn foo() {
+ var a = 0;
+ var b = 0;
+ bar(&a, &b);
+ if (b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MultiplePointerParametersWithEdgesToEachOther) {
+ // The analysis traverses the tree for each pointer parameter, and we need to make sure that we
+ // reset the "visited" state of nodes in between these traversals to properly capture each of
+ // their uniformity states.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(a : ptr<function, i32>, b : ptr<function, i32>, c : ptr<function, i32>) {
+ *a = *a;
+ *b = *b;
+ *c = *a + *b;
+}
+
+fn foo() {
+ var a = non_uniform;
+ var b = 0;
+ var c = 0;
+ bar(&a, &b, &c);
+ if (c == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:16:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MaximumNumberOfPointerParameters) {
+ // Create a function with the maximum number of parameters, all pointers, to stress the
+ // quadratic nature of the analysis.
+ ProgramBuilder b;
+ auto& ty = b.ty;
+
+ // fn foo(p0 : ptr<function, i32>, p1 : ptr<function, i32>, ...) {
+ // let rhs = *p0 + *p1 + ... + *p244;
+ // *p1 = rhs;
+ // *p2 = rhs;
+ // ...
+ // *p254 = rhs;
+ // }
+ ast::VariableList params;
+ ast::StatementList foo_body;
+ const ast::Expression* rhs_init = b.Deref("p0");
+ for (int i = 1; i < 255; i++) {
+ rhs_init = b.Add(rhs_init, b.Deref("p" + std::to_string(i)));
+ }
+ foo_body.push_back(b.Decl(b.Let("rhs", nullptr, rhs_init)));
+ for (int i = 0; i < 255; i++) {
+ params.push_back(
+ b.Param("p" + std::to_string(i), ty.pointer(ty.i32(), ast::StorageClass::kFunction)));
+ if (i > 0) {
+ foo_body.push_back(b.Assign(b.Deref("p" + std::to_string(i)), "rhs"));
+ }
+ }
+ b.Func("foo", std::move(params), ty.void_(), foo_body);
+
+ // var<private> non_uniform_global : i32;
+ // fn main() {
+ // var v0 : i32;
+ // var v1 : i32;
+ // ...
+ // var v254 : i32;
+ // v0 = non_uniform_global;
+ // foo(&v0, &v1, ..., &v254);
+ // if (v254 == 0) {
+ // workgroupBarrier();
+ // }
+ // }
+ b.Global("non_uniform_global", ty.i32(), ast::StorageClass::kPrivate);
+ ast::StatementList main_body;
+ ast::ExpressionList args;
+ for (int i = 0; i < 255; i++) {
+ auto name = "v" + std::to_string(i);
+ main_body.push_back(b.Decl(b.Var(name, ty.i32())));
+ args.push_back(b.AddressOf(name));
+ }
+ main_body.push_back(b.Assign("v0", "non_uniform_global"));
+ main_body.push_back(b.CallStmt(b.create<ast::CallExpression>(b.Expr("foo"), args)));
+ main_body.push_back(
+ b.If(b.Equal("v254", 0_i), b.Block(b.CallStmt(b.Call("workgroupBarrier")))));
+ b.Func("main", {}, ty.void_(), main_body);
+
+ // TODO(jrprice): Expect false when uniformity issues become errors.
+ EXPECT_TRUE(RunTest(std::move(b))) << error_;
+ EXPECT_EQ(error_,
+ R"(warning: 'workgroupBarrier' must only be called from uniform control flow)");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Tests to cover access to aggregate types.
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(UniformityAnalysisTest, VectorElement_Uniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> v : vec4<i32>;
+
+fn foo() {
+ if (v[2] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_NonUniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> v : array<i32>;
+
+fn foo() {
+ if (v[2] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_BecomesNonUniform_BeforeCondition) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[2] = rw;
+ if (v[2] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_BecomesNonUniform_AfterCondition) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ if (v[2] == 0) {
+ v[2] = rw;
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_DifferentElementBecomesNonUniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ if (v[2] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_ElementBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ v[1] = 42;
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_DifferentElementBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, the whole vector
+ // is still considered to be non-uniform.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ v[2] = 42;
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_NonUniform_AnyBuiltin) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = non_uniform_global;
+ if (any(v == vec4(42))) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_Uniform) {
+ auto src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn foo() {
+ if (s.b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_NonUniform) {
+ auto src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> s : S;
+
+fn foo() {
+ if (s.b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_BecomesNonUniform_BeforeCondition) {
+ auto src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.b = rw;
+ if (s.b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:12:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_BecomesNonUniform_AfterCondition) {
+ auto src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ if (s.b == 0) {
+ s.b = rw;
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_DifferentMemberBecomesNonUniform) {
+ auto src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.a = rw;
+ if (s.b == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:12:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_MemberBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to a member, that member is
+ // still considered to be non-uniform.
+ auto src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.a = rw;
+ s.a = 0;
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:13:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_DifferentMemberBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to a member, the whole struct
+ // is still considered to be non-uniform.
+ auto src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.a = rw;
+ s.b = 0;
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:13:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_Uniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read> arr : array<i32>;
+
+fn foo() {
+ if (arr[7] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_NonUniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> arr : array<i32>;
+
+fn foo() {
+ if (arr[7] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_BecomesNonUniform_BeforeCondition) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[2] = rw;
+ if (arr[2] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_BecomesNonUniform_AfterCondition) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ if (arr[2] == 0) {
+ arr[2] = rw;
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_DifferentElementBecomesNonUniform) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ if (arr[2] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_DifferentElementBecomesNonUniformThroughPointer) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ let pa = &arr[1];
+ *pa = rw;
+ if (arr[2] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ arr[1] = 42;
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_DifferentElementBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, the whole array
+ // is still considered to be non-uniform.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ arr[2] = 42;
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniformThroughPointer) {
+ // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element through a
+ // pointer, the whole array is still considered to be non-uniform.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ let pa = &arr[2];
+ arr[1] = rw;
+ *pa = 42;
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Miscellaneous statement and expression tests.
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(UniformityAnalysisTest, TypeConstructor) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+fn foo() {
+ if (i32(non_uniform_global) == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Conversion) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+fn foo() {
+ if (f32(non_uniform_global) == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, Bitcast) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+fn foo() {
+ if (bitcast<f32>(non_uniform_global) == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, CompoundAssignment_NonUniformRHS) {
+ // Use compound assignment with a non-uniform RHS on a variable.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v = 0;
+ v += rw;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, CompoundAssignment_UniformRHS_StillNonUniform) {
+ // Use compound assignment with a uniform RHS on a variable that is already non-uniform.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v = rw;
+ v += 1;
+ if (v == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, PhonyAssignment_LhsCausesNonUniformControlFlow) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> nonuniform_var : i32;
+
+fn bar() -> i32 {
+ if (nonuniform_var == 42) {
+ return 1;
+ } else {
+ return 2;
+ }
+}
+
+fn foo() {
+ _ = bar();
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ShortCircuiting_CausesNonUniformControlFlow) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
+
+var<private> p : i32;
+
+fn main() {
+ let b = (non_uniform_global == 42) && false;
+ workgroupBarrier();
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:8:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, DeadCode_AfterReturn) {
+ // Dead code after a return statement shouldn't cause uniformity errors.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ return;
+ if (non_uniform == 42) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, DeadCode_AfterDiscard) {
+ // Dead code after a discard statement shouldn't cause uniformity errors.
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn foo() {
+ discard;
+ if (non_uniform == 42) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ArrayLength) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> arr : array<f32>;
+
+fn foo() {
+ for (var i = 0u; i < arrayLength(&arr); i++) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, WorkgroupAtomics) {
+ auto src = R"(
+var<workgroup> a : atomic<i32>;
+
+fn foo() {
+ if (atomicAdd(&a, 1) == 1) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StorageAtomics) {
+ auto src = R"(
+@group(0) @binding(0) var<storage, read_write> a : atomic<i32>;
+
+fn foo() {
+ if (atomicAdd(&a, 1) == 1) {
+ storageBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:6:5 warning: 'storageBarrier' must only be called from uniform control flow
+ storageBarrier();
+ ^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, DisableAnalysisWithExtension) {
+ auto src = R"(
+enable chromium_disable_uniformity_analysis;
+
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ if (rw == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+} // namespace
+} // namespace tint::resolver
diff --git a/src/tint/scope_stack.h b/src/tint/scope_stack.h
index 6892b40..6838f5b 100644
--- a/src/tint/scope_stack.h
+++ b/src/tint/scope_stack.h
@@ -71,6 +71,16 @@
return V{};
}
+ /// Return the top scope of the stack.
+ /// @returns the top scope of the stack
+ const std::unordered_map<K, V>& Top() const { return stack_.back(); }
+
+ /// Clear the scope stack.
+ void Clear() {
+ stack_.clear();
+ stack_.push_back({});
+ }
+
private:
std::vector<std::unordered_map<K, V>> stack_;
};
diff --git a/src/tint/scope_stack_test.cc b/src/tint/scope_stack_test.cc
index 8ed1f81..aeb7e73 100644
--- a/src/tint/scope_stack_test.cc
+++ b/src/tint/scope_stack_test.cc
@@ -67,5 +67,25 @@
EXPECT_EQ(s.Get(b), 25u);
}
+TEST_F(ScopeStackTest, Clear) {
+ ScopeStack<Symbol, uint32_t> s;
+ Symbol a(1, ID());
+ Symbol b(2, ID());
+
+ EXPECT_EQ(s.Set(a, 5u), 0u);
+ EXPECT_EQ(s.Get(a), 5u);
+
+ s.Push();
+
+ EXPECT_EQ(s.Set(b, 10u), 0u);
+ EXPECT_EQ(s.Get(b), 10u);
+
+ s.Push();
+
+ s.Clear();
+ EXPECT_EQ(s.Get(a), 0u);
+ EXPECT_EQ(s.Get(b), 0u);
+}
+
} // namespace
} // namespace tint
diff --git a/test/tint/BUILD.gn b/test/tint/BUILD.gn
index 784fedb..e17c6c7 100644
--- a/test/tint/BUILD.gn
+++ b/test/tint/BUILD.gn
@@ -272,6 +272,7 @@
"../../src/tint/resolver/struct_storage_class_use_test.cc",
"../../src/tint/resolver/type_constructor_validation_test.cc",
"../../src/tint/resolver/type_validation_test.cc",
+ "../../src/tint/resolver/uniformity_test.cc",
"../../src/tint/resolver/validation_test.cc",
"../../src/tint/resolver/validator_is_storeable_test.cc",
"../../src/tint/resolver/var_let_test.cc",
diff --git a/test/tint/benchmark/uniformity-analysis-pointer-parameters.wgsl b/test/tint/benchmark/uniformity-analysis-pointer-parameters.wgsl
new file mode 100644
index 0000000..f73abef
--- /dev/null
+++ b/test/tint/benchmark/uniformity-analysis-pointer-parameters.wgsl
@@ -0,0 +1,1065 @@
+// Create a function with the maximum number of parameters, all pointers, to stress the
+// quadratic nature of the uniformity analysis.
+
+fn foo(
+ p0 : ptr<function, i32>,
+ p1 : ptr<function, i32>,
+ p2 : ptr<function, i32>,
+ p3 : ptr<function, i32>,
+ p4 : ptr<function, i32>,
+ p5 : ptr<function, i32>,
+ p6 : ptr<function, i32>,
+ p7 : ptr<function, i32>,
+ p8 : ptr<function, i32>,
+ p9 : ptr<function, i32>,
+ p10 : ptr<function, i32>,
+ p11 : ptr<function, i32>,
+ p12 : ptr<function, i32>,
+ p13 : ptr<function, i32>,
+ p14 : ptr<function, i32>,
+ p15 : ptr<function, i32>,
+ p16 : ptr<function, i32>,
+ p17 : ptr<function, i32>,
+ p18 : ptr<function, i32>,
+ p19 : ptr<function, i32>,
+ p20 : ptr<function, i32>,
+ p21 : ptr<function, i32>,
+ p22 : ptr<function, i32>,
+ p23 : ptr<function, i32>,
+ p24 : ptr<function, i32>,
+ p25 : ptr<function, i32>,
+ p26 : ptr<function, i32>,
+ p27 : ptr<function, i32>,
+ p28 : ptr<function, i32>,
+ p29 : ptr<function, i32>,
+ p30 : ptr<function, i32>,
+ p31 : ptr<function, i32>,
+ p32 : ptr<function, i32>,
+ p33 : ptr<function, i32>,
+ p34 : ptr<function, i32>,
+ p35 : ptr<function, i32>,
+ p36 : ptr<function, i32>,
+ p37 : ptr<function, i32>,
+ p38 : ptr<function, i32>,
+ p39 : ptr<function, i32>,
+ p40 : ptr<function, i32>,
+ p41 : ptr<function, i32>,
+ p42 : ptr<function, i32>,
+ p43 : ptr<function, i32>,
+ p44 : ptr<function, i32>,
+ p45 : ptr<function, i32>,
+ p46 : ptr<function, i32>,
+ p47 : ptr<function, i32>,
+ p48 : ptr<function, i32>,
+ p49 : ptr<function, i32>,
+ p50 : ptr<function, i32>,
+ p51 : ptr<function, i32>,
+ p52 : ptr<function, i32>,
+ p53 : ptr<function, i32>,
+ p54 : ptr<function, i32>,
+ p55 : ptr<function, i32>,
+ p56 : ptr<function, i32>,
+ p57 : ptr<function, i32>,
+ p58 : ptr<function, i32>,
+ p59 : ptr<function, i32>,
+ p60 : ptr<function, i32>,
+ p61 : ptr<function, i32>,
+ p62 : ptr<function, i32>,
+ p63 : ptr<function, i32>,
+ p64 : ptr<function, i32>,
+ p65 : ptr<function, i32>,
+ p66 : ptr<function, i32>,
+ p67 : ptr<function, i32>,
+ p68 : ptr<function, i32>,
+ p69 : ptr<function, i32>,
+ p70 : ptr<function, i32>,
+ p71 : ptr<function, i32>,
+ p72 : ptr<function, i32>,
+ p73 : ptr<function, i32>,
+ p74 : ptr<function, i32>,
+ p75 : ptr<function, i32>,
+ p76 : ptr<function, i32>,
+ p77 : ptr<function, i32>,
+ p78 : ptr<function, i32>,
+ p79 : ptr<function, i32>,
+ p80 : ptr<function, i32>,
+ p81 : ptr<function, i32>,
+ p82 : ptr<function, i32>,
+ p83 : ptr<function, i32>,
+ p84 : ptr<function, i32>,
+ p85 : ptr<function, i32>,
+ p86 : ptr<function, i32>,
+ p87 : ptr<function, i32>,
+ p88 : ptr<function, i32>,
+ p89 : ptr<function, i32>,
+ p90 : ptr<function, i32>,
+ p91 : ptr<function, i32>,
+ p92 : ptr<function, i32>,
+ p93 : ptr<function, i32>,
+ p94 : ptr<function, i32>,
+ p95 : ptr<function, i32>,
+ p96 : ptr<function, i32>,
+ p97 : ptr<function, i32>,
+ p98 : ptr<function, i32>,
+ p99 : ptr<function, i32>,
+ p100 : ptr<function, i32>,
+ p101 : ptr<function, i32>,
+ p102 : ptr<function, i32>,
+ p103 : ptr<function, i32>,
+ p104 : ptr<function, i32>,
+ p105 : ptr<function, i32>,
+ p106 : ptr<function, i32>,
+ p107 : ptr<function, i32>,
+ p108 : ptr<function, i32>,
+ p109 : ptr<function, i32>,
+ p110 : ptr<function, i32>,
+ p111 : ptr<function, i32>,
+ p112 : ptr<function, i32>,
+ p113 : ptr<function, i32>,
+ p114 : ptr<function, i32>,
+ p115 : ptr<function, i32>,
+ p116 : ptr<function, i32>,
+ p117 : ptr<function, i32>,
+ p118 : ptr<function, i32>,
+ p119 : ptr<function, i32>,
+ p120 : ptr<function, i32>,
+ p121 : ptr<function, i32>,
+ p122 : ptr<function, i32>,
+ p123 : ptr<function, i32>,
+ p124 : ptr<function, i32>,
+ p125 : ptr<function, i32>,
+ p126 : ptr<function, i32>,
+ p127 : ptr<function, i32>,
+ p128 : ptr<function, i32>,
+ p129 : ptr<function, i32>,
+ p130 : ptr<function, i32>,
+ p131 : ptr<function, i32>,
+ p132 : ptr<function, i32>,
+ p133 : ptr<function, i32>,
+ p134 : ptr<function, i32>,
+ p135 : ptr<function, i32>,
+ p136 : ptr<function, i32>,
+ p137 : ptr<function, i32>,
+ p138 : ptr<function, i32>,
+ p139 : ptr<function, i32>,
+ p140 : ptr<function, i32>,
+ p141 : ptr<function, i32>,
+ p142 : ptr<function, i32>,
+ p143 : ptr<function, i32>,
+ p144 : ptr<function, i32>,
+ p145 : ptr<function, i32>,
+ p146 : ptr<function, i32>,
+ p147 : ptr<function, i32>,
+ p148 : ptr<function, i32>,
+ p149 : ptr<function, i32>,
+ p150 : ptr<function, i32>,
+ p151 : ptr<function, i32>,
+ p152 : ptr<function, i32>,
+ p153 : ptr<function, i32>,
+ p154 : ptr<function, i32>,
+ p155 : ptr<function, i32>,
+ p156 : ptr<function, i32>,
+ p157 : ptr<function, i32>,
+ p158 : ptr<function, i32>,
+ p159 : ptr<function, i32>,
+ p160 : ptr<function, i32>,
+ p161 : ptr<function, i32>,
+ p162 : ptr<function, i32>,
+ p163 : ptr<function, i32>,
+ p164 : ptr<function, i32>,
+ p165 : ptr<function, i32>,
+ p166 : ptr<function, i32>,
+ p167 : ptr<function, i32>,
+ p168 : ptr<function, i32>,
+ p169 : ptr<function, i32>,
+ p170 : ptr<function, i32>,
+ p171 : ptr<function, i32>,
+ p172 : ptr<function, i32>,
+ p173 : ptr<function, i32>,
+ p174 : ptr<function, i32>,
+ p175 : ptr<function, i32>,
+ p176 : ptr<function, i32>,
+ p177 : ptr<function, i32>,
+ p178 : ptr<function, i32>,
+ p179 : ptr<function, i32>,
+ p180 : ptr<function, i32>,
+ p181 : ptr<function, i32>,
+ p182 : ptr<function, i32>,
+ p183 : ptr<function, i32>,
+ p184 : ptr<function, i32>,
+ p185 : ptr<function, i32>,
+ p186 : ptr<function, i32>,
+ p187 : ptr<function, i32>,
+ p188 : ptr<function, i32>,
+ p189 : ptr<function, i32>,
+ p190 : ptr<function, i32>,
+ p191 : ptr<function, i32>,
+ p192 : ptr<function, i32>,
+ p193 : ptr<function, i32>,
+ p194 : ptr<function, i32>,
+ p195 : ptr<function, i32>,
+ p196 : ptr<function, i32>,
+ p197 : ptr<function, i32>,
+ p198 : ptr<function, i32>,
+ p199 : ptr<function, i32>,
+ p200 : ptr<function, i32>,
+ p201 : ptr<function, i32>,
+ p202 : ptr<function, i32>,
+ p203 : ptr<function, i32>,
+ p204 : ptr<function, i32>,
+ p205 : ptr<function, i32>,
+ p206 : ptr<function, i32>,
+ p207 : ptr<function, i32>,
+ p208 : ptr<function, i32>,
+ p209 : ptr<function, i32>,
+ p210 : ptr<function, i32>,
+ p211 : ptr<function, i32>,
+ p212 : ptr<function, i32>,
+ p213 : ptr<function, i32>,
+ p214 : ptr<function, i32>,
+ p215 : ptr<function, i32>,
+ p216 : ptr<function, i32>,
+ p217 : ptr<function, i32>,
+ p218 : ptr<function, i32>,
+ p219 : ptr<function, i32>,
+ p220 : ptr<function, i32>,
+ p221 : ptr<function, i32>,
+ p222 : ptr<function, i32>,
+ p223 : ptr<function, i32>,
+ p224 : ptr<function, i32>,
+ p225 : ptr<function, i32>,
+ p226 : ptr<function, i32>,
+ p227 : ptr<function, i32>,
+ p228 : ptr<function, i32>,
+ p229 : ptr<function, i32>,
+ p230 : ptr<function, i32>,
+ p231 : ptr<function, i32>,
+ p232 : ptr<function, i32>,
+ p233 : ptr<function, i32>,
+ p234 : ptr<function, i32>,
+ p235 : ptr<function, i32>,
+ p236 : ptr<function, i32>,
+ p237 : ptr<function, i32>,
+ p238 : ptr<function, i32>,
+ p239 : ptr<function, i32>,
+ p240 : ptr<function, i32>,
+ p241 : ptr<function, i32>,
+ p242 : ptr<function, i32>,
+ p243 : ptr<function, i32>,
+ p244 : ptr<function, i32>,
+ p245 : ptr<function, i32>,
+ p246 : ptr<function, i32>,
+ p247 : ptr<function, i32>,
+ p248 : ptr<function, i32>,
+ p249 : ptr<function, i32>,
+ p250 : ptr<function, i32>,
+ p251 : ptr<function, i32>,
+ p252 : ptr<function, i32>,
+ p253 : ptr<function, i32>,
+ p254 : ptr<function, i32>,
+) {
+ var rhs = *p0 + *p1 + *p2 + *p3 + *p4 + *p5 + *p6 + *p7;
+ rhs += *p8 + *p9 + *p10 + *p11 + *p12 + *p13 + *p14 + *p15;
+ rhs += *p16 + *p17 + *p18 + *p19 + *p20 + *p21 + *p22 + *p23;
+ rhs += *p24 + *p25 + *p26 + *p27 + *p28 + *p29 + *p30 + *p31;
+ rhs += *p32 + *p33 + *p34 + *p35 + *p36 + *p37 + *p38 + *p39;
+ rhs += *p40 + *p41 + *p42 + *p43 + *p44 + *p45 + *p46 + *p47;
+ rhs += *p48 + *p49 + *p50 + *p51 + *p52 + *p53 + *p54 + *p55;
+ rhs += *p56 + *p57 + *p58 + *p59 + *p60 + *p61 + *p62 + *p63;
+ rhs += *p64 + *p65 + *p66 + *p67 + *p68 + *p69 + *p70 + *p71;
+ rhs += *p72 + *p73 + *p74 + *p75 + *p76 + *p77 + *p78 + *p79;
+ rhs += *p80 + *p81 + *p82 + *p83 + *p84 + *p85 + *p86 + *p87;
+ rhs += *p88 + *p89 + *p90 + *p91 + *p92 + *p93 + *p94 + *p95;
+ rhs += *p96 + *p97 + *p98 + *p99 + *p100 + *p101 + *p102 + *p103;
+ rhs += *p104 + *p105 + *p106 + *p107 + *p108 + *p109 + *p110 + *p111;
+ rhs += *p112 + *p113 + *p114 + *p115 + *p116 + *p117 + *p118 + *p119;
+ rhs += *p120 + *p121 + *p122 + *p123 + *p124 + *p125 + *p126 + *p127;
+ rhs += *p128 + *p129 + *p130 + *p131 + *p132 + *p133 + *p134 + *p135;
+ rhs += *p136 + *p137 + *p138 + *p139 + *p140 + *p141 + *p142 + *p143;
+ rhs += *p144 + *p145 + *p146 + *p147 + *p148 + *p149 + *p150 + *p151;
+ rhs += *p152 + *p153 + *p154 + *p155 + *p156 + *p157 + *p158 + *p159;
+ rhs += *p160 + *p161 + *p162 + *p163 + *p164 + *p165 + *p166 + *p167;
+ rhs += *p168 + *p169 + *p170 + *p171 + *p172 + *p173 + *p174 + *p175;
+ rhs += *p176 + *p177 + *p178 + *p179 + *p180 + *p181 + *p182 + *p183;
+ rhs += *p184 + *p185 + *p186 + *p187 + *p188 + *p189 + *p190 + *p191;
+ rhs += *p192 + *p193 + *p194 + *p195 + *p196 + *p197 + *p198 + *p199;
+ rhs += *p200 + *p201 + *p202 + *p203 + *p204 + *p205 + *p206 + *p207;
+ rhs += *p208 + *p209 + *p210 + *p211 + *p212 + *p213 + *p214 + *p215;
+ rhs += *p216 + *p217 + *p218 + *p219 + *p220 + *p221 + *p222 + *p223;
+ rhs += *p224 + *p225 + *p226 + *p227 + *p228 + *p229 + *p230 + *p231;
+ rhs += *p232 + *p233 + *p234 + *p235 + *p236 + *p237 + *p238 + *p239;
+ rhs += *p240 + *p241 + *p242 + *p243 + *p244 + *p245 + *p246 + *p247;
+ rhs += *p248 + *p249 + *p250 + *p251 + *p252 + *p253 + *p254;
+ *p1 = rhs;
+ *p2 = rhs;
+ *p3 = rhs;
+ *p4 = rhs;
+ *p5 = rhs;
+ *p6 = rhs;
+ *p7 = rhs;
+ *p8 = rhs;
+ *p9 = rhs;
+ *p10 = rhs;
+ *p11 = rhs;
+ *p12 = rhs;
+ *p13 = rhs;
+ *p14 = rhs;
+ *p15 = rhs;
+ *p16 = rhs;
+ *p17 = rhs;
+ *p18 = rhs;
+ *p19 = rhs;
+ *p20 = rhs;
+ *p21 = rhs;
+ *p22 = rhs;
+ *p23 = rhs;
+ *p24 = rhs;
+ *p25 = rhs;
+ *p26 = rhs;
+ *p27 = rhs;
+ *p28 = rhs;
+ *p29 = rhs;
+ *p30 = rhs;
+ *p31 = rhs;
+ *p32 = rhs;
+ *p33 = rhs;
+ *p34 = rhs;
+ *p35 = rhs;
+ *p36 = rhs;
+ *p37 = rhs;
+ *p38 = rhs;
+ *p39 = rhs;
+ *p40 = rhs;
+ *p41 = rhs;
+ *p42 = rhs;
+ *p43 = rhs;
+ *p44 = rhs;
+ *p45 = rhs;
+ *p46 = rhs;
+ *p47 = rhs;
+ *p48 = rhs;
+ *p49 = rhs;
+ *p50 = rhs;
+ *p51 = rhs;
+ *p52 = rhs;
+ *p53 = rhs;
+ *p54 = rhs;
+ *p55 = rhs;
+ *p56 = rhs;
+ *p57 = rhs;
+ *p58 = rhs;
+ *p59 = rhs;
+ *p60 = rhs;
+ *p61 = rhs;
+ *p62 = rhs;
+ *p63 = rhs;
+ *p64 = rhs;
+ *p65 = rhs;
+ *p66 = rhs;
+ *p67 = rhs;
+ *p68 = rhs;
+ *p69 = rhs;
+ *p70 = rhs;
+ *p71 = rhs;
+ *p72 = rhs;
+ *p73 = rhs;
+ *p74 = rhs;
+ *p75 = rhs;
+ *p76 = rhs;
+ *p77 = rhs;
+ *p78 = rhs;
+ *p79 = rhs;
+ *p80 = rhs;
+ *p81 = rhs;
+ *p82 = rhs;
+ *p83 = rhs;
+ *p84 = rhs;
+ *p85 = rhs;
+ *p86 = rhs;
+ *p87 = rhs;
+ *p88 = rhs;
+ *p89 = rhs;
+ *p90 = rhs;
+ *p91 = rhs;
+ *p92 = rhs;
+ *p93 = rhs;
+ *p94 = rhs;
+ *p95 = rhs;
+ *p96 = rhs;
+ *p97 = rhs;
+ *p98 = rhs;
+ *p99 = rhs;
+ *p100 = rhs;
+ *p101 = rhs;
+ *p102 = rhs;
+ *p103 = rhs;
+ *p104 = rhs;
+ *p105 = rhs;
+ *p106 = rhs;
+ *p107 = rhs;
+ *p108 = rhs;
+ *p109 = rhs;
+ *p110 = rhs;
+ *p111 = rhs;
+ *p112 = rhs;
+ *p113 = rhs;
+ *p114 = rhs;
+ *p115 = rhs;
+ *p116 = rhs;
+ *p117 = rhs;
+ *p118 = rhs;
+ *p119 = rhs;
+ *p120 = rhs;
+ *p121 = rhs;
+ *p122 = rhs;
+ *p123 = rhs;
+ *p124 = rhs;
+ *p125 = rhs;
+ *p126 = rhs;
+ *p127 = rhs;
+ *p128 = rhs;
+ *p129 = rhs;
+ *p130 = rhs;
+ *p131 = rhs;
+ *p132 = rhs;
+ *p133 = rhs;
+ *p134 = rhs;
+ *p135 = rhs;
+ *p136 = rhs;
+ *p137 = rhs;
+ *p138 = rhs;
+ *p139 = rhs;
+ *p140 = rhs;
+ *p141 = rhs;
+ *p142 = rhs;
+ *p143 = rhs;
+ *p144 = rhs;
+ *p145 = rhs;
+ *p146 = rhs;
+ *p147 = rhs;
+ *p148 = rhs;
+ *p149 = rhs;
+ *p150 = rhs;
+ *p151 = rhs;
+ *p152 = rhs;
+ *p153 = rhs;
+ *p154 = rhs;
+ *p155 = rhs;
+ *p156 = rhs;
+ *p157 = rhs;
+ *p158 = rhs;
+ *p159 = rhs;
+ *p160 = rhs;
+ *p161 = rhs;
+ *p162 = rhs;
+ *p163 = rhs;
+ *p164 = rhs;
+ *p165 = rhs;
+ *p166 = rhs;
+ *p167 = rhs;
+ *p168 = rhs;
+ *p169 = rhs;
+ *p170 = rhs;
+ *p171 = rhs;
+ *p172 = rhs;
+ *p173 = rhs;
+ *p174 = rhs;
+ *p175 = rhs;
+ *p176 = rhs;
+ *p177 = rhs;
+ *p178 = rhs;
+ *p179 = rhs;
+ *p180 = rhs;
+ *p181 = rhs;
+ *p182 = rhs;
+ *p183 = rhs;
+ *p184 = rhs;
+ *p185 = rhs;
+ *p186 = rhs;
+ *p187 = rhs;
+ *p188 = rhs;
+ *p189 = rhs;
+ *p190 = rhs;
+ *p191 = rhs;
+ *p192 = rhs;
+ *p193 = rhs;
+ *p194 = rhs;
+ *p195 = rhs;
+ *p196 = rhs;
+ *p197 = rhs;
+ *p198 = rhs;
+ *p199 = rhs;
+ *p200 = rhs;
+ *p201 = rhs;
+ *p202 = rhs;
+ *p203 = rhs;
+ *p204 = rhs;
+ *p205 = rhs;
+ *p206 = rhs;
+ *p207 = rhs;
+ *p208 = rhs;
+ *p209 = rhs;
+ *p210 = rhs;
+ *p211 = rhs;
+ *p212 = rhs;
+ *p213 = rhs;
+ *p214 = rhs;
+ *p215 = rhs;
+ *p216 = rhs;
+ *p217 = rhs;
+ *p218 = rhs;
+ *p219 = rhs;
+ *p220 = rhs;
+ *p221 = rhs;
+ *p222 = rhs;
+ *p223 = rhs;
+ *p224 = rhs;
+ *p225 = rhs;
+ *p226 = rhs;
+ *p227 = rhs;
+ *p228 = rhs;
+ *p229 = rhs;
+ *p230 = rhs;
+ *p231 = rhs;
+ *p232 = rhs;
+ *p233 = rhs;
+ *p234 = rhs;
+ *p235 = rhs;
+ *p236 = rhs;
+ *p237 = rhs;
+ *p238 = rhs;
+ *p239 = rhs;
+ *p240 = rhs;
+ *p241 = rhs;
+ *p242 = rhs;
+ *p243 = rhs;
+ *p244 = rhs;
+ *p245 = rhs;
+ *p246 = rhs;
+ *p247 = rhs;
+ *p248 = rhs;
+ *p249 = rhs;
+ *p250 = rhs;
+ *p251 = rhs;
+ *p252 = rhs;
+ *p253 = rhs;
+ *p254 = rhs;
+}
+
+fn main() {
+ var v0 : i32;
+ var v1 : i32;
+ var v2 : i32;
+ var v3 : i32;
+ var v4 : i32;
+ var v5 : i32;
+ var v6 : i32;
+ var v7 : i32;
+ var v8 : i32;
+ var v9 : i32;
+ var v10 : i32;
+ var v11 : i32;
+ var v12 : i32;
+ var v13 : i32;
+ var v14 : i32;
+ var v15 : i32;
+ var v16 : i32;
+ var v17 : i32;
+ var v18 : i32;
+ var v19 : i32;
+ var v20 : i32;
+ var v21 : i32;
+ var v22 : i32;
+ var v23 : i32;
+ var v24 : i32;
+ var v25 : i32;
+ var v26 : i32;
+ var v27 : i32;
+ var v28 : i32;
+ var v29 : i32;
+ var v30 : i32;
+ var v31 : i32;
+ var v32 : i32;
+ var v33 : i32;
+ var v34 : i32;
+ var v35 : i32;
+ var v36 : i32;
+ var v37 : i32;
+ var v38 : i32;
+ var v39 : i32;
+ var v40 : i32;
+ var v41 : i32;
+ var v42 : i32;
+ var v43 : i32;
+ var v44 : i32;
+ var v45 : i32;
+ var v46 : i32;
+ var v47 : i32;
+ var v48 : i32;
+ var v49 : i32;
+ var v50 : i32;
+ var v51 : i32;
+ var v52 : i32;
+ var v53 : i32;
+ var v54 : i32;
+ var v55 : i32;
+ var v56 : i32;
+ var v57 : i32;
+ var v58 : i32;
+ var v59 : i32;
+ var v60 : i32;
+ var v61 : i32;
+ var v62 : i32;
+ var v63 : i32;
+ var v64 : i32;
+ var v65 : i32;
+ var v66 : i32;
+ var v67 : i32;
+ var v68 : i32;
+ var v69 : i32;
+ var v70 : i32;
+ var v71 : i32;
+ var v72 : i32;
+ var v73 : i32;
+ var v74 : i32;
+ var v75 : i32;
+ var v76 : i32;
+ var v77 : i32;
+ var v78 : i32;
+ var v79 : i32;
+ var v80 : i32;
+ var v81 : i32;
+ var v82 : i32;
+ var v83 : i32;
+ var v84 : i32;
+ var v85 : i32;
+ var v86 : i32;
+ var v87 : i32;
+ var v88 : i32;
+ var v89 : i32;
+ var v90 : i32;
+ var v91 : i32;
+ var v92 : i32;
+ var v93 : i32;
+ var v94 : i32;
+ var v95 : i32;
+ var v96 : i32;
+ var v97 : i32;
+ var v98 : i32;
+ var v99 : i32;
+ var v100 : i32;
+ var v101 : i32;
+ var v102 : i32;
+ var v103 : i32;
+ var v104 : i32;
+ var v105 : i32;
+ var v106 : i32;
+ var v107 : i32;
+ var v108 : i32;
+ var v109 : i32;
+ var v110 : i32;
+ var v111 : i32;
+ var v112 : i32;
+ var v113 : i32;
+ var v114 : i32;
+ var v115 : i32;
+ var v116 : i32;
+ var v117 : i32;
+ var v118 : i32;
+ var v119 : i32;
+ var v120 : i32;
+ var v121 : i32;
+ var v122 : i32;
+ var v123 : i32;
+ var v124 : i32;
+ var v125 : i32;
+ var v126 : i32;
+ var v127 : i32;
+ var v128 : i32;
+ var v129 : i32;
+ var v130 : i32;
+ var v131 : i32;
+ var v132 : i32;
+ var v133 : i32;
+ var v134 : i32;
+ var v135 : i32;
+ var v136 : i32;
+ var v137 : i32;
+ var v138 : i32;
+ var v139 : i32;
+ var v140 : i32;
+ var v141 : i32;
+ var v142 : i32;
+ var v143 : i32;
+ var v144 : i32;
+ var v145 : i32;
+ var v146 : i32;
+ var v147 : i32;
+ var v148 : i32;
+ var v149 : i32;
+ var v150 : i32;
+ var v151 : i32;
+ var v152 : i32;
+ var v153 : i32;
+ var v154 : i32;
+ var v155 : i32;
+ var v156 : i32;
+ var v157 : i32;
+ var v158 : i32;
+ var v159 : i32;
+ var v160 : i32;
+ var v161 : i32;
+ var v162 : i32;
+ var v163 : i32;
+ var v164 : i32;
+ var v165 : i32;
+ var v166 : i32;
+ var v167 : i32;
+ var v168 : i32;
+ var v169 : i32;
+ var v170 : i32;
+ var v171 : i32;
+ var v172 : i32;
+ var v173 : i32;
+ var v174 : i32;
+ var v175 : i32;
+ var v176 : i32;
+ var v177 : i32;
+ var v178 : i32;
+ var v179 : i32;
+ var v180 : i32;
+ var v181 : i32;
+ var v182 : i32;
+ var v183 : i32;
+ var v184 : i32;
+ var v185 : i32;
+ var v186 : i32;
+ var v187 : i32;
+ var v188 : i32;
+ var v189 : i32;
+ var v190 : i32;
+ var v191 : i32;
+ var v192 : i32;
+ var v193 : i32;
+ var v194 : i32;
+ var v195 : i32;
+ var v196 : i32;
+ var v197 : i32;
+ var v198 : i32;
+ var v199 : i32;
+ var v200 : i32;
+ var v201 : i32;
+ var v202 : i32;
+ var v203 : i32;
+ var v204 : i32;
+ var v205 : i32;
+ var v206 : i32;
+ var v207 : i32;
+ var v208 : i32;
+ var v209 : i32;
+ var v210 : i32;
+ var v211 : i32;
+ var v212 : i32;
+ var v213 : i32;
+ var v214 : i32;
+ var v215 : i32;
+ var v216 : i32;
+ var v217 : i32;
+ var v218 : i32;
+ var v219 : i32;
+ var v220 : i32;
+ var v221 : i32;
+ var v222 : i32;
+ var v223 : i32;
+ var v224 : i32;
+ var v225 : i32;
+ var v226 : i32;
+ var v227 : i32;
+ var v228 : i32;
+ var v229 : i32;
+ var v230 : i32;
+ var v231 : i32;
+ var v232 : i32;
+ var v233 : i32;
+ var v234 : i32;
+ var v235 : i32;
+ var v236 : i32;
+ var v237 : i32;
+ var v238 : i32;
+ var v239 : i32;
+ var v240 : i32;
+ var v241 : i32;
+ var v242 : i32;
+ var v243 : i32;
+ var v244 : i32;
+ var v245 : i32;
+ var v246 : i32;
+ var v247 : i32;
+ var v248 : i32;
+ var v249 : i32;
+ var v250 : i32;
+ var v251 : i32;
+ var v252 : i32;
+ var v253 : i32;
+ var v254 : i32;
+ foo(
+ &v0,
+ &v1,
+ &v2,
+ &v3,
+ &v4,
+ &v5,
+ &v6,
+ &v7,
+ &v8,
+ &v9,
+ &v10,
+ &v11,
+ &v12,
+ &v13,
+ &v14,
+ &v15,
+ &v16,
+ &v17,
+ &v18,
+ &v19,
+ &v20,
+ &v21,
+ &v22,
+ &v23,
+ &v24,
+ &v25,
+ &v26,
+ &v27,
+ &v28,
+ &v29,
+ &v30,
+ &v31,
+ &v32,
+ &v33,
+ &v34,
+ &v35,
+ &v36,
+ &v37,
+ &v38,
+ &v39,
+ &v40,
+ &v41,
+ &v42,
+ &v43,
+ &v44,
+ &v45,
+ &v46,
+ &v47,
+ &v48,
+ &v49,
+ &v50,
+ &v51,
+ &v52,
+ &v53,
+ &v54,
+ &v55,
+ &v56,
+ &v57,
+ &v58,
+ &v59,
+ &v60,
+ &v61,
+ &v62,
+ &v63,
+ &v64,
+ &v65,
+ &v66,
+ &v67,
+ &v68,
+ &v69,
+ &v70,
+ &v71,
+ &v72,
+ &v73,
+ &v74,
+ &v75,
+ &v76,
+ &v77,
+ &v78,
+ &v79,
+ &v80,
+ &v81,
+ &v82,
+ &v83,
+ &v84,
+ &v85,
+ &v86,
+ &v87,
+ &v88,
+ &v89,
+ &v90,
+ &v91,
+ &v92,
+ &v93,
+ &v94,
+ &v95,
+ &v96,
+ &v97,
+ &v98,
+ &v99,
+ &v100,
+ &v101,
+ &v102,
+ &v103,
+ &v104,
+ &v105,
+ &v106,
+ &v107,
+ &v108,
+ &v109,
+ &v110,
+ &v111,
+ &v112,
+ &v113,
+ &v114,
+ &v115,
+ &v116,
+ &v117,
+ &v118,
+ &v119,
+ &v120,
+ &v121,
+ &v122,
+ &v123,
+ &v124,
+ &v125,
+ &v126,
+ &v127,
+ &v128,
+ &v129,
+ &v130,
+ &v131,
+ &v132,
+ &v133,
+ &v134,
+ &v135,
+ &v136,
+ &v137,
+ &v138,
+ &v139,
+ &v140,
+ &v141,
+ &v142,
+ &v143,
+ &v144,
+ &v145,
+ &v146,
+ &v147,
+ &v148,
+ &v149,
+ &v150,
+ &v151,
+ &v152,
+ &v153,
+ &v154,
+ &v155,
+ &v156,
+ &v157,
+ &v158,
+ &v159,
+ &v160,
+ &v161,
+ &v162,
+ &v163,
+ &v164,
+ &v165,
+ &v166,
+ &v167,
+ &v168,
+ &v169,
+ &v170,
+ &v171,
+ &v172,
+ &v173,
+ &v174,
+ &v175,
+ &v176,
+ &v177,
+ &v178,
+ &v179,
+ &v180,
+ &v181,
+ &v182,
+ &v183,
+ &v184,
+ &v185,
+ &v186,
+ &v187,
+ &v188,
+ &v189,
+ &v190,
+ &v191,
+ &v192,
+ &v193,
+ &v194,
+ &v195,
+ &v196,
+ &v197,
+ &v198,
+ &v199,
+ &v200,
+ &v201,
+ &v202,
+ &v203,
+ &v204,
+ &v205,
+ &v206,
+ &v207,
+ &v208,
+ &v209,
+ &v210,
+ &v211,
+ &v212,
+ &v213,
+ &v214,
+ &v215,
+ &v216,
+ &v217,
+ &v218,
+ &v219,
+ &v220,
+ &v221,
+ &v222,
+ &v223,
+ &v224,
+ &v225,
+ &v226,
+ &v227,
+ &v228,
+ &v229,
+ &v230,
+ &v231,
+ &v232,
+ &v233,
+ &v234,
+ &v235,
+ &v236,
+ &v237,
+ &v238,
+ &v239,
+ &v240,
+ &v241,
+ &v242,
+ &v243,
+ &v244,
+ &v245,
+ &v246,
+ &v247,
+ &v248,
+ &v249,
+ &v250,
+ &v251,
+ &v252,
+ &v253,
+ &v254,
+ );
+ if (v254 == 0) {
+ workgroupBarrier();
+ }
+}
diff --git a/test/tint/bug/dawn/947.wgsl.expected.glsl b/test/tint/bug/dawn/947.wgsl.expected.glsl
index 413ee0e..60c1105 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.glsl
+++ b/test/tint/bug/dawn/947.wgsl.expected.glsl
@@ -1,3 +1,7 @@
+bug/dawn/947.wgsl:59:20 warning: 'textureSample' must only be called from uniform control flow
+ var srcColor = textureSample(myTexture, mySampler, texcoord);
+ ^^^^^^^^^^^^^
+
#version 310 es
layout(location = 0) out vec2 texcoords_1;
diff --git a/test/tint/bug/dawn/947.wgsl.expected.hlsl b/test/tint/bug/dawn/947.wgsl.expected.hlsl
index 7bcadcb..3b13d95 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.hlsl
+++ b/test/tint/bug/dawn/947.wgsl.expected.hlsl
@@ -1,3 +1,7 @@
+bug/dawn/947.wgsl:59:20 warning: 'textureSample' must only be called from uniform control flow
+ var srcColor = textureSample(myTexture, mySampler, texcoord);
+ ^^^^^^^^^^^^^
+
cbuffer cbuffer_uniforms : register(b0, space0) {
uint4 uniforms[1];
};
diff --git a/test/tint/bug/dawn/947.wgsl.expected.msl b/test/tint/bug/dawn/947.wgsl.expected.msl
index 15efcd0..07a7f73 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.msl
+++ b/test/tint/bug/dawn/947.wgsl.expected.msl
@@ -1,3 +1,7 @@
+bug/dawn/947.wgsl:59:20 warning: 'textureSample' must only be called from uniform control flow
+ var srcColor = textureSample(myTexture, mySampler, texcoord);
+ ^^^^^^^^^^^^^
+
#include <metal_stdlib>
using namespace metal;
diff --git a/test/tint/bug/dawn/947.wgsl.expected.spvasm b/test/tint/bug/dawn/947.wgsl.expected.spvasm
index df48a9a..a74165c 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.spvasm
+++ b/test/tint/bug/dawn/947.wgsl.expected.spvasm
@@ -1,3 +1,7 @@
+bug/dawn/947.wgsl:59:20 warning: 'textureSample' must only be called from uniform control flow
+ var srcColor = textureSample(myTexture, mySampler, texcoord);
+ ^^^^^^^^^^^^^
+
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
diff --git a/test/tint/bug/dawn/947.wgsl.expected.wgsl b/test/tint/bug/dawn/947.wgsl.expected.wgsl
index 063f354..6f98645 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.wgsl
+++ b/test/tint/bug/dawn/947.wgsl.expected.wgsl
@@ -1,3 +1,7 @@
+bug/dawn/947.wgsl:59:20 warning: 'textureSample' must only be called from uniform control flow
+ var srcColor = textureSample(myTexture, mySampler, texcoord);
+ ^^^^^^^^^^^^^
+
struct Uniforms {
u_scale : vec2<f32>,
u_offset : vec2<f32>,
diff --git a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl
index b11b4b2..48926b7 100644
--- a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl
+++ b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.glsl
@@ -1,3 +1,7 @@
+bug/fxc/gradient_in_varying_loop/1112.wgsl:23:33 warning: 'textureSample' must only be called from uniform control flow
+ let sampleDepth : f32 = textureSample(depthTexture, Sampler, offset.xy).r;
+ ^^^^^^^^^^^^^
+
#version 310 es
precision mediump float;
diff --git a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl
index 4c8362a..a6c6dd5 100644
--- a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl
+++ b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.hlsl
@@ -1,3 +1,7 @@
+bug/fxc/gradient_in_varying_loop/1112.wgsl:23:33 warning: 'textureSample' must only be called from uniform control flow
+ let sampleDepth : f32 = textureSample(depthTexture, Sampler, offset.xy).r;
+ ^^^^^^^^^^^^^
+
SamplerState tint_symbol : register(s0, space0);
Texture2D<float4> randomTexture : register(t1, space0);
Texture2D<float4> depthTexture : register(t2, space0);
diff --git a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl
index 3b00074..62a41ed 100644
--- a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl
+++ b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.msl
@@ -1,3 +1,7 @@
+bug/fxc/gradient_in_varying_loop/1112.wgsl:23:33 warning: 'textureSample' must only be called from uniform control flow
+ let sampleDepth : f32 = textureSample(depthTexture, Sampler, offset.xy).r;
+ ^^^^^^^^^^^^^
+
#include <metal_stdlib>
using namespace metal;
diff --git a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm
index 49aff3b..31483ae 100644
--- a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm
+++ b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.spvasm
@@ -1,3 +1,7 @@
+bug/fxc/gradient_in_varying_loop/1112.wgsl:23:33 warning: 'textureSample' must only be called from uniform control flow
+ let sampleDepth : f32 = textureSample(depthTexture, Sampler, offset.xy).r;
+ ^^^^^^^^^^^^^
+
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
diff --git a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl
index 0998c72..1761161 100644
--- a/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl
+++ b/test/tint/bug/fxc/gradient_in_varying_loop/1112.wgsl.expected.wgsl
@@ -1,3 +1,7 @@
+bug/fxc/gradient_in_varying_loop/1112.wgsl:23:33 warning: 'textureSample' must only be called from uniform control flow
+ let sampleDepth : f32 = textureSample(depthTexture, Sampler, offset.xy).r;
+ ^^^^^^^^^^^^^
+
@group(0) @binding(0) var Sampler : sampler;
@group(0) @binding(1) var randomTexture : texture_2d<f32>;
diff --git a/test/tint/bug/tint/1118.wgsl.expected.glsl b/test/tint/bug/tint/1118.wgsl.expected.glsl
index 1a8acc6..39bc459 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.glsl
+++ b/test/tint/bug/tint/1118.wgsl.expected.glsl
@@ -1,3 +1,7 @@
+bug/tint/1118.wgsl:64:31 warning: 'dpdx' must only be called from uniform control flow
+ normalW = normalize(-(cross(dpdx(x_62), dpdy(x_64))));
+ ^^^^
+
#version 310 es
precision mediump float;
diff --git a/test/tint/bug/tint/1118.wgsl.expected.hlsl b/test/tint/bug/tint/1118.wgsl.expected.hlsl
index 117a578..467aea6 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.hlsl
+++ b/test/tint/bug/tint/1118.wgsl.expected.hlsl
@@ -1,3 +1,7 @@
+bug/tint/1118.wgsl:64:31 warning: 'dpdx' must only be called from uniform control flow
+ normalW = normalize(-(cross(dpdx(x_62), dpdy(x_64))));
+ ^^^^
+
static float fClipDistance3 = 0.0f;
static float fClipDistance4 = 0.0f;
cbuffer cbuffer_x_29 : register(b0, space0) {
diff --git a/test/tint/bug/tint/1118.wgsl.expected.msl b/test/tint/bug/tint/1118.wgsl.expected.msl
index 06a0d7d..a8d14c5 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.msl
+++ b/test/tint/bug/tint/1118.wgsl.expected.msl
@@ -1,3 +1,7 @@
+bug/tint/1118.wgsl:64:31 warning: 'dpdx' must only be called from uniform control flow
+ normalW = normalize(-(cross(dpdx(x_62), dpdy(x_64))));
+ ^^^^
+
#include <metal_stdlib>
using namespace metal;
diff --git a/test/tint/bug/tint/1118.wgsl.expected.spvasm b/test/tint/bug/tint/1118.wgsl.expected.spvasm
index 55d6f1d..26653b6 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/1118.wgsl.expected.spvasm
@@ -1,3 +1,7 @@
+bug/tint/1118.wgsl:64:31 warning: 'dpdx' must only be called from uniform control flow
+ normalW = normalize(-(cross(dpdx(x_62), dpdy(x_64))));
+ ^^^^
+
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
diff --git a/test/tint/bug/tint/1118.wgsl.expected.wgsl b/test/tint/bug/tint/1118.wgsl.expected.wgsl
index 1274782..d2a3bc3 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.wgsl
+++ b/test/tint/bug/tint/1118.wgsl.expected.wgsl
@@ -1,3 +1,7 @@
+bug/tint/1118.wgsl:64:31 warning: 'dpdx' must only be called from uniform control flow
+ normalW = normalize(-(cross(dpdx(x_62), dpdy(x_64))));
+ ^^^^
+
struct Scene {
vEyePosition : vec4<f32>,
}
diff --git a/test/tint/bug/tint/943.spvasm.expected.glsl b/test/tint/bug/tint/943.spvasm.expected.glsl
index c488064..f581b5d 100644
--- a/test/tint/bug/tint/943.spvasm.expected.glsl
+++ b/test/tint/bug/tint/943.spvasm.expected.glsl
@@ -1,3 +1,4 @@
+warning: parameter 'dimInner' of 'mm_matMul_i1_i1_i1_' must be uniform
#version 310 es
struct Uniforms {
diff --git a/test/tint/bug/tint/943.spvasm.expected.hlsl b/test/tint/bug/tint/943.spvasm.expected.hlsl
index bdf9b12..89accf2 100644
--- a/test/tint/bug/tint/943.spvasm.expected.hlsl
+++ b/test/tint/bug/tint/943.spvasm.expected.hlsl
@@ -1,3 +1,4 @@
+warning: parameter 'dimInner' of 'mm_matMul_i1_i1_i1_' must be uniform
static int dimAOuter_1 = 0;
cbuffer cbuffer_x_48 : register(b3, space0) {
uint4 x_48[5];
diff --git a/test/tint/bug/tint/943.spvasm.expected.msl b/test/tint/bug/tint/943.spvasm.expected.msl
index 648486b..031bf63 100644
--- a/test/tint/bug/tint/943.spvasm.expected.msl
+++ b/test/tint/bug/tint/943.spvasm.expected.msl
@@ -1,3 +1,4 @@
+warning: parameter 'dimInner' of 'mm_matMul_i1_i1_i1_' must be uniform
#include <metal_stdlib>
using namespace metal;
diff --git a/test/tint/bug/tint/943.spvasm.expected.spvasm b/test/tint/bug/tint/943.spvasm.expected.spvasm
index 315f30e..228d7df 100644
--- a/test/tint/bug/tint/943.spvasm.expected.spvasm
+++ b/test/tint/bug/tint/943.spvasm.expected.spvasm
@@ -1,3 +1,4 @@
+warning: parameter 'dimInner' of 'mm_matMul_i1_i1_i1_' must be uniform
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
diff --git a/test/tint/bug/tint/943.spvasm.expected.wgsl b/test/tint/bug/tint/943.spvasm.expected.wgsl
index fe02e98..aa383da 100644
--- a/test/tint/bug/tint/943.spvasm.expected.wgsl
+++ b/test/tint/bug/tint/943.spvasm.expected.wgsl
@@ -1,3 +1,4 @@
+warning: parameter 'dimInner' of 'mm_matMul_i1_i1_i1_' must be uniform
struct Uniforms {
NAN : f32,
@size(12)
diff --git a/test/tint/bug/tint/948.wgsl.expected.glsl b/test/tint/bug/tint/948.wgsl.expected.glsl
index e5cd11e..a675fe9 100644
--- a/test/tint/bug/tint/948.wgsl.expected.glsl
+++ b/test/tint/bug/tint/948.wgsl.expected.glsl
@@ -1,3 +1,7 @@
+bug/tint/948.wgsl:146:33 warning: 'textureSampleBias' must only be called from uniform control flow
+ let x_217 : vec4<f32> = textureSampleBias(animationMapTexture, animationMapSampler, vec2<f32>(((x_208 + 0.5) / x_211), (0.125 * x_214)), 0.0);
+ ^^^^^^^^^^^^^^^^^
+
#version 310 es
precision mediump float;
diff --git a/test/tint/bug/tint/948.wgsl.expected.hlsl b/test/tint/bug/tint/948.wgsl.expected.hlsl
index 5a253ad..1869503 100644
--- a/test/tint/bug/tint/948.wgsl.expected.hlsl
+++ b/test/tint/bug/tint/948.wgsl.expected.hlsl
@@ -1,3 +1,7 @@
+bug/tint/948.wgsl:146:33 warning: 'textureSampleBias' must only be called from uniform control flow
+ let x_217 : vec4<f32> = textureSampleBias(animationMapTexture, animationMapSampler, vec2<f32>(((x_208 + 0.5) / x_211), (0.125 * x_214)), 0.0);
+ ^^^^^^^^^^^^^^^^^
+
cbuffer cbuffer_x_20 : register(b9, space2) {
uint4 x_20[8];
};
diff --git a/test/tint/bug/tint/948.wgsl.expected.msl b/test/tint/bug/tint/948.wgsl.expected.msl
index 25febaf..f799044 100644
--- a/test/tint/bug/tint/948.wgsl.expected.msl
+++ b/test/tint/bug/tint/948.wgsl.expected.msl
@@ -1,3 +1,7 @@
+bug/tint/948.wgsl:146:33 warning: 'textureSampleBias' must only be called from uniform control flow
+ let x_217 : vec4<f32> = textureSampleBias(animationMapTexture, animationMapSampler, vec2<f32>(((x_208 + 0.5) / x_211), (0.125 * x_214)), 0.0);
+ ^^^^^^^^^^^^^^^^^
+
#include <metal_stdlib>
using namespace metal;
diff --git a/test/tint/bug/tint/948.wgsl.expected.spvasm b/test/tint/bug/tint/948.wgsl.expected.spvasm
index 51c0bb0..4f5102c 100644
--- a/test/tint/bug/tint/948.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/948.wgsl.expected.spvasm
@@ -1,3 +1,7 @@
+bug/tint/948.wgsl:146:33 warning: 'textureSampleBias' must only be called from uniform control flow
+ let x_217 : vec4<f32> = textureSampleBias(animationMapTexture, animationMapSampler, vec2<f32>(((x_208 + 0.5) / x_211), (0.125 * x_214)), 0.0);
+ ^^^^^^^^^^^^^^^^^
+
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
diff --git a/test/tint/bug/tint/948.wgsl.expected.wgsl b/test/tint/bug/tint/948.wgsl.expected.wgsl
index 3c11fec..791079b 100644
--- a/test/tint/bug/tint/948.wgsl.expected.wgsl
+++ b/test/tint/bug/tint/948.wgsl.expected.wgsl
@@ -1,3 +1,7 @@
+bug/tint/948.wgsl:146:33 warning: 'textureSampleBias' must only be called from uniform control flow
+ let x_217 : vec4<f32> = textureSampleBias(animationMapTexture, animationMapSampler, vec2<f32>(((x_208 + 0.5) / x_211), (0.125 * x_214)), 0.0);
+ ^^^^^^^^^^^^^^^^^
+
struct LeftOver {
time : f32,
@size(12)
diff --git a/test/tint/bug/tint/949.wgsl.expected.glsl b/test/tint/bug/tint/949.wgsl.expected.glsl
index fbb9d32..e60090b 100644
--- a/test/tint/bug/tint/949.wgsl.expected.glsl
+++ b/test/tint/bug/tint/949.wgsl.expected.glsl
@@ -1,3 +1,7 @@
+bug/tint/949.wgsl:326:29 warning: 'textureSample' must only be called from uniform control flow
+ let x_397 : vec4<f32> = textureSample(TextureSamplerTexture, TextureSamplerSampler, (x_394 + x_395));
+ ^^^^^^^^^^^^^
+
#version 310 es
precision mediump float;
diff --git a/test/tint/bug/tint/949.wgsl.expected.hlsl b/test/tint/bug/tint/949.wgsl.expected.hlsl
index 4869166..2bdadf0 100644
--- a/test/tint/bug/tint/949.wgsl.expected.hlsl
+++ b/test/tint/bug/tint/949.wgsl.expected.hlsl
@@ -1,3 +1,7 @@
+bug/tint/949.wgsl:326:29 warning: 'textureSample' must only be called from uniform control flow
+ let x_397 : vec4<f32> = textureSample(TextureSamplerTexture, TextureSamplerSampler, (x_394 + x_395));
+ ^^^^^^^^^^^^^
+
struct lightingInfo {
float3 diffuse;
float3 specular;
diff --git a/test/tint/bug/tint/949.wgsl.expected.msl b/test/tint/bug/tint/949.wgsl.expected.msl
index fb89dec..0701a4a 100644
--- a/test/tint/bug/tint/949.wgsl.expected.msl
+++ b/test/tint/bug/tint/949.wgsl.expected.msl
@@ -1,3 +1,7 @@
+bug/tint/949.wgsl:326:29 warning: 'textureSample' must only be called from uniform control flow
+ let x_397 : vec4<f32> = textureSample(TextureSamplerTexture, TextureSamplerSampler, (x_394 + x_395));
+ ^^^^^^^^^^^^^
+
#include <metal_stdlib>
using namespace metal;
diff --git a/test/tint/bug/tint/949.wgsl.expected.spvasm b/test/tint/bug/tint/949.wgsl.expected.spvasm
index e2fce0a..287968a 100644
--- a/test/tint/bug/tint/949.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/949.wgsl.expected.spvasm
@@ -1,3 +1,7 @@
+bug/tint/949.wgsl:326:29 warning: 'textureSample' must only be called from uniform control flow
+ let x_397 : vec4<f32> = textureSample(TextureSamplerTexture, TextureSamplerSampler, (x_394 + x_395));
+ ^^^^^^^^^^^^^
+
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
diff --git a/test/tint/bug/tint/949.wgsl.expected.wgsl b/test/tint/bug/tint/949.wgsl.expected.wgsl
index c16bf96..48c21aa 100644
--- a/test/tint/bug/tint/949.wgsl.expected.wgsl
+++ b/test/tint/bug/tint/949.wgsl.expected.wgsl
@@ -1,3 +1,7 @@
+bug/tint/949.wgsl:326:29 warning: 'textureSample' must only be called from uniform control flow
+ let x_397 : vec4<f32> = textureSample(TextureSamplerTexture, TextureSamplerSampler, (x_394 + x_395));
+ ^^^^^^^^^^^^^
+
struct lightingInfo {
diffuse : vec3<f32>,
specular : vec3<f32>,