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:
parent
3e1bc0a6da
commit
cc85ed6dd1
|
@ -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,
|
||||
|
|
|
@ -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(¶m);
|
||||
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(¶m);
|
||||
^
|
||||
|
||||
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(¶m);
|
||||
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(¶m);
|
||||
^
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue