tint: Function calls do not affect control flow uniformity

We now use the MergeReturn transform for SPIR-V, which is the only
backend that requires special handling to meet these function call
reconvergence requirements.

Fixed: tint:1627, tint:1726
Change-Id: I25f848f4b9ff0fd301b8a27a220bb09cdb2867ca
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/107364
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
James Price 2022-10-31 16:33:11 +00:00 committed by Dawn LUCI CQ
parent 3e1bc0a6da
commit cc85ed6dd1
2 changed files with 34 additions and 894 deletions

View File

@ -72,7 +72,6 @@ enum CallSiteTag {
/// FunctionTag describes a functions effects on uniformity.
enum FunctionTag {
SubsequentControlFlowMayBeNonUniform,
ReturnValueMayBeNonUniform,
NoRestriction,
};
@ -80,7 +79,6 @@ enum FunctionTag {
/// ParameterTag describes the uniformity requirements of values passed to a function parameter.
enum ParameterTag {
ParameterRequiredToBeUniform,
ParameterRequiredToBeUniformForSubsequentControlFlow,
ParameterRequiredToBeUniformForReturnValue,
ParameterNoRestriction,
};
@ -163,7 +161,6 @@ struct FunctionInfo {
required_to_be_uniform = CreateNode("RequiredToBeUniform");
may_be_non_uniform = CreateNode("MayBeNonUniform");
cf_start = CreateNode("CF_start");
cf_return = CreateNode("CF_return");
if (func->return_type) {
value_return = CreateNode("Value_return");
}
@ -208,8 +205,6 @@ struct FunctionInfo {
Node* may_be_non_uniform;
/// Special `CF_start` node.
Node* cf_start;
/// Special `CF_return` node.
Node* cf_return;
/// Special `Value_return` node.
Node* value_return;
@ -339,8 +334,7 @@ class UniformityGraph {
// Process function body.
if (func->body) {
auto* cf = ProcessStatement(current_function_->cf_start, func->body);
current_function_->cf_return->AddEdge(cf);
ProcessStatement(current_function_->cf_start, func->body);
}
#if TINT_DUMP_UNIFORMITY_GRAPH
@ -378,25 +372,6 @@ class UniformityGraph {
}
}
// Look at which nodes are reachable from "CF_return"
{
utils::UniqueVector<Node*, 4> reachable;
Traverse(current_function_->cf_return, &reachable);
if (reachable.Contains(current_function_->may_be_non_uniform)) {
current_function_->function_tag = SubsequentControlFlowMayBeNonUniform;
}
// Set the parameter tag to ParameterRequiredToBeUniformForSubsequentControlFlow for
// each parameter node that was reachable.
for (size_t i = 0; i < func->params.Length(); i++) {
auto* param = func->params[i];
if (reachable.Contains(current_function_->variables.Get(sem_.Get(param)))) {
current_function_->parameters[i].tag =
ParameterRequiredToBeUniformForSubsequentControlFlow;
}
}
}
// If "Value_return" exists, look at which nodes are reachable from it
if (current_function_->value_return) {
utils::UniqueVector<Node*, 4> reachable;
@ -918,12 +893,10 @@ class UniformityGraph {
Node* cf_ret;
if (r->value) {
auto [cf1, v] = ProcessExpression(cf, r->value);
current_function_->cf_return->AddEdge(cf1);
current_function_->value_return->AddEdge(v);
cf_ret = cf1;
} else {
TINT_ASSERT(Resolver, cf != nullptr);
current_function_->cf_return->AddEdge(cf);
cf_ret = cf;
}
@ -1395,10 +1368,7 @@ class UniformityGraph {
}
cf_after->AddEdge(call_node);
if (function_tag == SubsequentControlFlowMayBeNonUniform) {
cf_after->AddEdge(current_function_->may_be_non_uniform);
cf_after->affects_control_flow = true;
} else if (function_tag == ReturnValueMayBeNonUniform) {
if (function_tag == ReturnValueMayBeNonUniform) {
result->AddEdge(current_function_->may_be_non_uniform);
}
@ -1411,10 +1381,6 @@ class UniformityGraph {
case ParameterRequiredToBeUniform:
current_function_->required_to_be_uniform->AddEdge(args[i]);
break;
case ParameterRequiredToBeUniformForSubsequentControlFlow:
cf_after->AddEdge(args[i]);
args[i]->affects_control_flow = true;
break;
case ParameterRequiredToBeUniformForReturnValue:
result->AddEdge(args[i]);
break;
@ -1544,26 +1510,9 @@ class UniformityGraph {
auto* control_flow = TraceBackAlongPathUntil(
non_uniform_source, [](Node* node) { return node->affects_control_flow; });
if (control_flow) {
if (auto* call = control_flow->ast->As<ast::CallExpression>()) {
if (control_flow->type == Node::kFunctionCallArgument) {
auto idx = control_flow->arg_index;
diagnostics_.add_note(diag::System::Resolver,
"non-uniform function call argument causes subsequent "
"control flow to be non-uniform",
call->args[idx]->source);
// Recurse into the target function.
if (auto* user = SemCall(call)->Target()->As<sem::Function>()) {
auto& callee = functions_.at(user->Declaration());
ShowCauseOfNonUniformity(callee, callee.cf_return,
callee.parameters[idx].init_value);
}
}
} else {
diagnostics_.add_note(diag::System::Resolver,
"control flow depends on non-uniform value",
control_flow->ast->source);
}
// TODO(jrprice): There are cases where the function with uniformity requirements is not
// actually inside this control flow construct, for example:
// - A conditional interrupt (e.g. break), with a barrier elsewhere in the loop
@ -1619,21 +1568,6 @@ class UniformityGraph {
auto target_name = builder_->Symbols().NameFor(
c->target.name->As<ast::IdentifierExpression>()->symbol);
switch (non_uniform_source->type) {
case Node::kRegular: {
diagnostics_.add_note(
diag::System::Resolver,
"calling '" + target_name +
"' may cause subsequent control flow to be non-uniform",
c->source);
// Recurse into the target function.
if (auto* user = SemCall(c)->Target()->As<sem::Function>()) {
auto& callee = functions_.at(user->Declaration());
ShowCauseOfNonUniformity(callee, callee.cf_return,
callee.may_be_non_uniform);
}
break;
}
case Node::kFunctionCallReturnValue: {
diagnostics_.add_note(
diag::System::Resolver,

View File

@ -338,134 +338,6 @@ INSTANTIATE_TEST_SUITE_P(
/// 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.
std::string 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.
std::string 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:16:3 note: calling 'foo' may cause subsequent control flow to be non-uniform
foo();
^^^
test:7:3 note: control flow depends on non-uniform value
if (rw == 0) {
^^
test:7:7 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
if (rw == 0) {
^^
)");
}
TEST_F(UniformityAnalysisTest, SubsequentControlFlowMayBeNonUniform_Nested_Fail) {
// Indirectly call a function that causes subsequent control flow to be non-uniform, and then
// call another function that requires uniformity.
// The lack of return statement in `foo()` requires that we implicitly add an edge from
// CF_return to that last control flow node of the function.
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> rw : i32;
var<private> p : i32;
fn bar() {
if (rw == 0) {
p = 42;
return;
}
p = 5;
return;
}
fn foo() {
bar();
}
fn main() {
foo();
workgroupBarrier();
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:21:3 warning: 'workgroupBarrier' must only be called from uniform control flow
workgroupBarrier();
^^^^^^^^^^^^^^^^
test:20:3 note: calling 'foo' may cause subsequent control flow to be non-uniform
foo();
^^^
test:16:3 note: calling 'bar' may cause subsequent control flow to be non-uniform
bar();
^^^
test:7:3 note: control flow depends on non-uniform value
if (rw == 0) {
^^
test:7:7 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
if (rw == 0) {
^^
)");
}
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.
@ -599,79 +471,6 @@ test:9:11 note: reading from read_write storage buffer 'rw' may result in a non-
)");
}
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.
std::string 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.
std::string 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:16:7 note: non-uniform function call argument causes subsequent control flow to be non-uniform
foo(rw);
^^
test:7:3 note: control flow depends on non-uniform value
if (i == 0) {
^^
test:7:7 note: reading from 'i' may result in a non-uniform value
if (i == 0) {
^
test:16:7 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
foo(rw);
^^
)");
}
////////////////////////////////////////////////////////////////////////////////
/// Test shader IO attributes.
////////////////////////////////////////////////////////////////////////////////
@ -1770,72 +1569,6 @@ fn foo() {
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.
std::string 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.
std::string 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:12:5 note: calling 'bar' may cause subsequent control flow to be non-uniform
bar();
^^^
test:5:3 note: control flow depends on non-uniform value
if (n == 42) {
^^
test:5:7 note: reading from read_write storage buffer 'n' may result in a non-uniform value
if (n == 42) {
^
)");
}
TEST_F(UniformityAnalysisTest, ForLoop_CallInside_UniformCondition) {
std::string src = R"(
@group(0) @binding(0) var<storage, read> n : i32;
@ -1877,84 +1610,6 @@ test:5:23 note: reading from read_write storage buffer 'n' may result in a non-u
)");
}
TEST_F(UniformityAnalysisTest, ForLoop_CallInside_InitializerCausesNonUniformFlow) {
std::string 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:13:16 note: calling 'bar' may cause subsequent control flow to be non-uniform
for (var i = bar(); i < 10; i = i + 1) {
^^^
test:5:3 note: control flow depends on non-uniform value
if (n == 42) {
^^
test:5:7 note: reading from read_write storage buffer 'n' may result in a non-uniform value
if (n == 42) {
^
)");
}
TEST_F(UniformityAnalysisTest, ForLoop_CallInside_ContinuingCausesNonUniformFlow) {
std::string 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:13:35 note: calling 'bar' may cause subsequent control flow to be non-uniform
for (var i = 0; i < 10; i = i + bar()) {
^^^
test:5:3 note: control flow depends on non-uniform value
if (n == 42) {
^^
test:5:7 note: reading from read_write storage buffer 'n' may result in a non-uniform value
if (n == 42) {
^
)");
}
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.
@ -3832,76 +3487,6 @@ fn foo() {
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.
std::string 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.
std::string 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();
^^^^^^^^^^^^^^^^
test:13:7 note: calling 'bar' may cause subsequent control flow to be non-uniform
bar();
^^^
test:5:3 note: control flow depends on non-uniform value
if (n == 42) {
^^
test:5:7 note: reading from read_write storage buffer 'n' may result in a non-uniform value
if (n == 42) {
^
)");
}
////////////////////////////////////////////////////////////////////////////////
/// Pointer tests.
////////////////////////////////////////////////////////////////////////////////
@ -7164,6 +6749,32 @@ fn foo() {
/// Miscellaneous statement and expression tests.
////////////////////////////////////////////////////////////////////////////////
TEST_F(UniformityAnalysisTest, FunctionReconvergesOnExit) {
// Call a function that has returns during non-uniform control flow, and test that the analysis
// reconverges when returning to the caller.
std::string 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, true);
}
TEST_F(UniformityAnalysisTest, FunctionRequiresUniformFlowAndCausesNonUniformFlow) {
// Test that a function that requires uniform flow and then causes non-uniform flow can be
// called without error.
@ -7328,176 +6939,12 @@ test:5:11 note: reading from read_write storage buffer 'rw' may result in a non-
)");
}
TEST_F(UniformityAnalysisTest, PhonyAssignment_LhsCausesNonUniformControlFlow) {
std::string 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:13:7 note: calling 'bar' may cause subsequent control flow to be non-uniform
_ = bar();
^^^
test:5:3 note: control flow depends on non-uniform value
if (nonuniform_var == 42) {
^^
test:5:7 note: reading from read_write storage buffer 'nonuniform_var' may result in a non-uniform value
if (nonuniform_var == 42) {
^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest, ShortCircuiting_NoReconvergeLHS) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
var<private> p : i32;
fn non_uniform_discard_func() -> bool {
if (non_uniform_global == 42) {
discard;
}
return false;
}
fn main() {
let b = non_uniform_discard_func() && false;
workgroupBarrier();
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:15:3 warning: 'workgroupBarrier' must only be called from uniform control flow
workgroupBarrier();
^^^^^^^^^^^^^^^^
test:14:11 note: calling 'non_uniform_discard_func' may cause subsequent control flow to be non-uniform
let b = non_uniform_discard_func() && false;
^^^^^^^^^^^^^^^^^^^^^^^^
test:7:3 note: control flow depends on non-uniform value
if (non_uniform_global == 42) {
^^
test:7:7 note: reading from read_write storage buffer 'non_uniform_global' may result in a non-uniform value
if (non_uniform_global == 42) {
^^^^^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest, ShortCircuiting_NoReconvergeRHS) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
var<private> p : i32;
fn non_uniform_discard_func() -> bool {
if (non_uniform_global == 42) {
discard;
}
return false;
}
fn main() {
let b = false && non_uniform_discard_func();
workgroupBarrier();
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:15:3 warning: 'workgroupBarrier' must only be called from uniform control flow
workgroupBarrier();
^^^^^^^^^^^^^^^^
test:14:20 note: calling 'non_uniform_discard_func' may cause subsequent control flow to be non-uniform
let b = false && non_uniform_discard_func();
^^^^^^^^^^^^^^^^^^^^^^^^
test:7:3 note: control flow depends on non-uniform value
if (non_uniform_global == 42) {
^^
test:7:7 note: reading from read_write storage buffer 'non_uniform_global' may result in a non-uniform value
if (non_uniform_global == 42) {
^^^^^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest, ShortCircuiting_NoReconvergeBoth) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
var<private> p : i32;
fn non_uniform_discard_func() -> bool {
if (non_uniform_global == 42) {
discard;
}
return false;
}
fn main() {
let b = non_uniform_discard_func() && non_uniform_discard_func();
workgroupBarrier();
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:15:3 warning: 'workgroupBarrier' must only be called from uniform control flow
workgroupBarrier();
^^^^^^^^^^^^^^^^
test:14:41 note: calling 'non_uniform_discard_func' may cause subsequent control flow to be non-uniform
let b = non_uniform_discard_func() && non_uniform_discard_func();
^^^^^^^^^^^^^^^^^^^^^^^^
test:7:3 note: control flow depends on non-uniform value
if (non_uniform_global == 42) {
^^
test:7:7 note: reading from read_write storage buffer 'non_uniform_global' may result in a non-uniform value
if (non_uniform_global == 42) {
^^^^^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest, ShortCircuiting_ReconvergeLHS) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
var<private> p : i32;
fn uniform_discard_func() -> bool {
if (true) {
discard;
}
return false;
}
fn main() {
let b = uniform_discard_func() && false;
let b = (non_uniform_global == 0) && false;
workgroupBarrier();
}
)";
@ -7509,17 +6956,8 @@ TEST_F(UniformityAnalysisTest, ShortCircuiting_ReconvergeRHS) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
var<private> p : i32;
fn uniform_discard_func() -> bool {
if (true) {
discard;
}
return false;
}
fn main() {
let b = false && uniform_discard_func();
let b = false && (non_uniform_global == 0);
workgroupBarrier();
}
)";
@ -7531,17 +6969,8 @@ TEST_F(UniformityAnalysisTest, ShortCircuiting_ReconvergeBoth) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> non_uniform_global : i32;
var<private> p : i32;
fn uniform_discard_func() -> bool {
if (true) {
discard;
}
return false;
}
fn main() {
let b = uniform_discard_func() && uniform_discard_func();
let b = (non_uniform_global != 0) && (non_uniform_global != 42);
workgroupBarrier();
}
)";
@ -7867,228 +7296,5 @@ test:17:7 note: return value of 'foo' may be non-uniform
)");
}
TEST_F(UniformityAnalysisTest, Error_SubsequentControlFlowMayBeNonUniform) {
// Make sure we correctly identify the function call as the source of non-uniform control flow
// and not the if statement with the uniform condition.
std::string src = R"(
@group(0) @binding(0) var<uniform> uniform_value : i32;
@group(0) @binding(1) var<storage, read_write> non_uniform_value : i32;
fn foo() -> i32 {
if (non_uniform_value == 0) {
return 5;
}
return 6;
}
fn main() {
foo();
if (uniform_value == 42) {
workgroupBarrier();
}
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:15:5 warning: 'workgroupBarrier' must only be called from uniform control flow
workgroupBarrier();
^^^^^^^^^^^^^^^^
test:13:3 note: calling 'foo' may cause subsequent control flow to be non-uniform
foo();
^^^
test:6:3 note: control flow depends on non-uniform value
if (non_uniform_value == 0) {
^^
test:6:7 note: reading from read_write storage buffer 'non_uniform_value' may result in a non-uniform value
if (non_uniform_value == 0) {
^^^^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest, Error_ParameterRequiredToBeUniformForSubsequentControlFlow) {
// Make sure we correctly identify the function call as the source of non-uniform control flow
// and not the if statement with the uniform condition.
std::string src = R"(
@group(0) @binding(0) var<uniform> uniform_value : i32;
@group(0) @binding(1) var<storage, read_write> non_uniform_value : i32;
fn foo(x : i32) -> i32 {
if (x == 0) {
return 5;
}
return 6;
}
fn main() {
foo(non_uniform_value);
if (uniform_value == 42) {
workgroupBarrier();
}
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:15:5 warning: 'workgroupBarrier' must only be called from uniform control flow
workgroupBarrier();
^^^^^^^^^^^^^^^^
test:13:7 note: non-uniform function call argument causes subsequent control flow to be non-uniform
foo(non_uniform_value);
^^^^^^^^^^^^^^^^^
test:6:3 note: control flow depends on non-uniform value
if (x == 0) {
^^
test:6:7 note: reading from 'x' may result in a non-uniform value
if (x == 0) {
^
test:13:7 note: reading from read_write storage buffer 'non_uniform_value' may result in a non-uniform value
foo(non_uniform_value);
^^^^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest,
Error_ParameterRequiredToBeUniformForSubsequentControlFlow_ViaPointer) {
// Make sure we correctly identify the function call as the source of non-uniform control flow
// and not the if statement with the uniform condition.
std::string src = R"(
@group(0) @binding(1) var<storage, read_write> non_uniform_value : vec4<f32>;
fn foo(limit : ptr<function, f32>) -> f32 {
var i : i32;
if (f32(i) > *limit) {
return 0.0;
}
return 1.0f;
}
fn main() {
var param : f32 = non_uniform_value.y;
let i = foo(&param);
let y = dpdx(vec3<f32>());
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:15:11 warning: 'dpdx' must only be called from uniform control flow
let y = dpdx(vec3<f32>());
^^^^
test:14:15 note: non-uniform function call argument causes subsequent control flow to be non-uniform
let i = foo(&param);
^
test:6:3 note: control flow depends on non-uniform value
if (f32(i) > *limit) {
^^
test:6:14 note: result of expression may be non-uniform
if (f32(i) > *limit) {
^
test:13:21 note: reading from read_write storage buffer 'non_uniform_value' may result in a non-uniform value
var param : f32 = non_uniform_value.y;
^^^^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest,
Error_ParameterRequiredToBeUniformForSubsequentControlFlow_ViaPointer_InLoop) {
// Make sure we correctly identify the function call as the source of non-uniform control flow
// and not the if statement with the uniform condition.
std::string src = R"(
@group(0) @binding(1) var<storage, read_write> non_uniform_value : vec4<f32>;
fn foo(limit : ptr<function, f32>) -> f32 {
var i : i32;
loop {
if (f32(i) > *limit) {
return 0.0;
}
}
}
fn main() {
var param : f32 = non_uniform_value.y;
let i = foo(&param);
let y = dpdx(vec3<f32>());
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:16:11 warning: 'dpdx' must only be called from uniform control flow
let y = dpdx(vec3<f32>());
^^^^
test:15:15 note: non-uniform function call argument causes subsequent control flow to be non-uniform
let i = foo(&param);
^
test:7:5 note: control flow depends on non-uniform value
if (f32(i) > *limit) {
^^
test:4:8 note: reading from 'limit' may result in a non-uniform value
fn foo(limit : ptr<function, f32>) -> f32 {
^^^^^
test:14:21 note: reading from read_write storage buffer 'non_uniform_value' may result in a non-uniform value
var param : f32 = non_uniform_value.y;
^^^^^^^^^^^^^^^^^
)");
}
TEST_F(UniformityAnalysisTest, Error_ShortCircuitingExprCausesNonUniformControlFlow) {
// Make sure we correctly identify the short-circuit as the source of non-uniform control flow
// and not the if statement with the uniform condition.
std::string src = R"(
@group(0) @binding(0) var<uniform> uniform_value : i32;
@group(0) @binding(1) var<storage, read_write> non_uniform_value : i32;
fn non_uniform_discard_func() -> bool {
if (non_uniform_value == 42) {
discard;
}
return false;
}
fn main() {
let b = non_uniform_discard_func() && true;
if (uniform_value == 42) {
workgroupBarrier();
}
}
)";
RunTest(src, false);
EXPECT_EQ(error_,
R"(test:15:5 warning: 'workgroupBarrier' must only be called from uniform control flow
workgroupBarrier();
^^^^^^^^^^^^^^^^
test:13:11 note: calling 'non_uniform_discard_func' may cause subsequent control flow to be non-uniform
let b = non_uniform_discard_func() && true;
^^^^^^^^^^^^^^^^^^^^^^^^
test:6:3 note: control flow depends on non-uniform value
if (non_uniform_value == 42) {
^^
test:6:7 note: reading from read_write storage buffer 'non_uniform_value' may result in a non-uniform value
if (non_uniform_value == 42) {
^^^^^^^^^^^^^^^^^
)");
}
} // namespace
} // namespace tint::resolver