tint: Implement pointer alias analysis
Track reads and writes to pointer parameters for each function in the Resolver, as well as accesses to module-scope variables. At function call sites, check the root identifiers of each pointer argument to determine if problematic aliasing occurs. The MSL backend passes pointers to sub-objects to functions when handling workgroup storage variables, which triggers the alias analysis. Add a validation override for this scenario. Bug: tint:1675 Change-Id: I81a40d1309df65521cc5ad39764d6a09a260f51e Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/110167 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Ben Clayton <bclayton@google.com> Commit-Queue: James Price <jrprice@google.com>
This commit is contained in:
parent
d6f9a8a253
commit
4d65fc91bb
|
@ -5,6 +5,8 @@
|
|||
### Deprecated Features
|
||||
|
||||
* The `sig` member of the return type of `frexp()` has been renamed to `fract`. [tint:1757](crbug.com/tint/1757)
|
||||
* Calling a function with multiple pointer arguments that alias each other is now a warning, and
|
||||
will become an error in a future release. [tint:1675](crbug.com/tint/1675)
|
||||
|
||||
## Changes for M109
|
||||
|
||||
|
|
|
@ -1116,6 +1116,7 @@ if (tint_build_unittests) {
|
|||
sources = [
|
||||
"resolver/address_space_layout_validation_test.cc",
|
||||
"resolver/address_space_validation_test.cc",
|
||||
"resolver/alias_analysis_test.cc",
|
||||
"resolver/array_accessor_test.cc",
|
||||
"resolver/assignment_validation_test.cc",
|
||||
"resolver/atomics_test.cc",
|
||||
|
|
|
@ -840,6 +840,7 @@ if(TINT_BUILD_TESTS)
|
|||
program_builder_test.cc
|
||||
program_test.cc
|
||||
reflection_test.cc
|
||||
resolver/alias_analysis_test.cc
|
||||
resolver/array_accessor_test.cc
|
||||
resolver/assignment_validation_test.cc
|
||||
resolver/atomics_test.cc
|
||||
|
|
|
@ -43,6 +43,8 @@ std::string DisableValidationAttribute::InternalName() const {
|
|||
return "disable_validation__ignore_stride";
|
||||
case DisabledValidation::kIgnoreInvalidPointerArgument:
|
||||
return "disable_validation__ignore_invalid_pointer_argument";
|
||||
case DisabledValidation::kIgnorePointerAliasing:
|
||||
return "disable_validation__ignore_pointer_aliasing";
|
||||
}
|
||||
return "<invalid>";
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ enum class DisabledValidation {
|
|||
/// When applied to a pointer function parameter, the validator will not require a function call
|
||||
/// argument passed for that parameter to have a certain form.
|
||||
kIgnoreInvalidPointerArgument,
|
||||
/// When applied to a function declaration, the validator will not complain if multiple
|
||||
/// pointer arguments alias when that function is called.
|
||||
kIgnorePointerAliasing,
|
||||
};
|
||||
|
||||
/// An internal attribute used to tell the validator to ignore specific
|
||||
|
|
|
@ -0,0 +1,897 @@
|
|||
// 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/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
using namespace tint::number_suffixes; // NOLINT
|
||||
|
||||
namespace tint::resolver {
|
||||
namespace {
|
||||
|
||||
struct ResolverAliasAnalysisTest : public resolver::TestHelper, public testing::Test {};
|
||||
|
||||
// Base test harness for tests that pass two pointers to a function.
|
||||
//
|
||||
// fn target(p1 : ptr<function, i32>, p2 : ptr<function, i32>) {
|
||||
// <test statements>
|
||||
// }
|
||||
// fn caller() {
|
||||
// var v1 : i32;
|
||||
// var v2 : i32;
|
||||
// target(&v1, aliased ? &v1 : &v2);
|
||||
// }
|
||||
struct TwoPointerConfig {
|
||||
ast::AddressSpace address_space; // The address space for the pointers.
|
||||
bool aliased; // Whether the pointers alias or not.
|
||||
};
|
||||
class TwoPointers : public ResolverTestWithParam<TwoPointerConfig> {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
utils::Vector<const ast::Statement*, 4> body;
|
||||
if (GetParam().address_space == ast::AddressSpace::kFunction) {
|
||||
body.Push(Decl(Var("v1", ty.i32())));
|
||||
body.Push(Decl(Var("v2", ty.i32())));
|
||||
} else {
|
||||
GlobalVar("v1", ast::AddressSpace::kPrivate, ty.i32());
|
||||
GlobalVar("v2", ast::AddressSpace::kPrivate, ty.i32());
|
||||
}
|
||||
body.Push(CallStmt(Call("target", AddressOf(Source{{12, 34}}, "v1"),
|
||||
AddressOf(Source{{56, 78}}, GetParam().aliased ? "v1" : "v2"))));
|
||||
Func("caller", utils::Empty, ty.void_(), body);
|
||||
}
|
||||
|
||||
void Run(utils::Vector<const ast::Statement*, 4>&& body, const char* err = nullptr) {
|
||||
auto addrspace = GetParam().address_space;
|
||||
Func("target",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<i32>(addrspace)),
|
||||
Param("p2", ty.pointer<i32>(addrspace)),
|
||||
},
|
||||
ty.void_(), std::move(body));
|
||||
if (GetParam().aliased && err) {
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), err);
|
||||
} else {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(TwoPointers, ReadRead) {
|
||||
// _ = *p1;
|
||||
// _ = *p2;
|
||||
Run({
|
||||
Assign(Phony(), Deref("p1")),
|
||||
Assign(Phony(), Deref("p2")),
|
||||
});
|
||||
}
|
||||
|
||||
TEST_P(TwoPointers, ReadWrite) {
|
||||
// _ = *p1;
|
||||
// *p2 = 42;
|
||||
Run(
|
||||
{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
Assign(Deref("p2"), 42_a),
|
||||
},
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(TwoPointers, WriteRead) {
|
||||
// *p1 = 42;
|
||||
// _ = *p2;
|
||||
Run(
|
||||
{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
Assign(Phony(), Deref("p2")),
|
||||
},
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(TwoPointers, WriteWrite) {
|
||||
// *p1 = 42;
|
||||
// *p2 = 42;
|
||||
Run(
|
||||
{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
Assign(Deref("p2"), 42_a),
|
||||
},
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(TwoPointers, ReadWriteThroughChain) {
|
||||
// fn f2(p1 : ptr<function, i32>, p2 : ptr<function, i32>) {
|
||||
// _ = *p1;
|
||||
// *p2 = 42;
|
||||
// }
|
||||
// fn f1(p1 : ptr<function, i32>, p2 : ptr<function, i32>) {
|
||||
// f2(p1, p2);
|
||||
// }
|
||||
//
|
||||
// f1(p1, p2);
|
||||
Func("f2",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<i32>(GetParam().address_space)),
|
||||
Param("p2", ty.pointer<i32>(GetParam().address_space)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
Assign(Deref("p2"), 42_a),
|
||||
});
|
||||
Func("f1",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<i32>(GetParam().address_space)),
|
||||
Param("p2", ty.pointer<i32>(GetParam().address_space)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
CallStmt(Call("f2", "p1", "p2")),
|
||||
});
|
||||
Run(
|
||||
{
|
||||
CallStmt(Call("f1", "p1", "p2")),
|
||||
},
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(TwoPointers, ReadWriteAcrossDifferentFunctions) {
|
||||
// fn f1(p1 : ptr<function, i32>) {
|
||||
// _ = *p1;
|
||||
// }
|
||||
// fn f2(p2 : ptr<function, i32>) {
|
||||
// *p2 = 42;
|
||||
// }
|
||||
//
|
||||
// f1(p1);
|
||||
// f2(p2);
|
||||
Func("f1",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(GetParam().address_space)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
});
|
||||
Func("f2",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p2", ty.pointer<i32>(GetParam().address_space)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p2"), 42_a),
|
||||
});
|
||||
Run(
|
||||
{
|
||||
CallStmt(Call("f1", "p1")),
|
||||
CallStmt(Call("f2", "p2")),
|
||||
},
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
|
||||
TwoPointers,
|
||||
::testing::Values(TwoPointerConfig{ast::AddressSpace::kFunction, false},
|
||||
TwoPointerConfig{ast::AddressSpace::kFunction, true},
|
||||
TwoPointerConfig{ast::AddressSpace::kPrivate, false},
|
||||
TwoPointerConfig{ast::AddressSpace::kPrivate, true}),
|
||||
[](const ::testing::TestParamInfo<TwoPointers::ParamType>& p) {
|
||||
std::stringstream ss;
|
||||
ss << (p.param.aliased ? "Aliased" : "Unaliased") << "_"
|
||||
<< p.param.address_space;
|
||||
return ss.str();
|
||||
});
|
||||
|
||||
// Base test harness for tests that pass a pointer to a function that references a module-scope var.
|
||||
//
|
||||
// var<private> global_1 : i32;
|
||||
// var<private> global_2 : i32;
|
||||
// fn target(p1 : ptr<private, i32>) {
|
||||
// <test statements>
|
||||
// }
|
||||
// fn caller() {
|
||||
// target(aliased ? &global_1 : &global_2);
|
||||
// }
|
||||
class OnePointerOneModuleScope : public ResolverTestWithParam<bool> {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
GlobalVar("global_1", ast::AddressSpace::kPrivate, ty.i32());
|
||||
GlobalVar("global_2", ast::AddressSpace::kPrivate, ty.i32());
|
||||
Func("caller", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
CallStmt(Call("target",
|
||||
AddressOf(Source{{12, 34}}, GetParam() ? "global_1" : "global_2"))),
|
||||
});
|
||||
}
|
||||
|
||||
void Run(utils::Vector<const ast::Statement*, 4>&& body, const char* err = nullptr) {
|
||||
Func("target",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(), std::move(body));
|
||||
if (GetParam() && err) {
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), err);
|
||||
} else {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, ReadRead) {
|
||||
// _ = *p1;
|
||||
// _ = global_1;
|
||||
Run({
|
||||
Assign(Phony(), Deref("p1")),
|
||||
Assign(Phony(), "global_1"),
|
||||
});
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, ReadWrite) {
|
||||
// _ = *p1;
|
||||
// global_1 = 42;
|
||||
Run(
|
||||
{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
Assign(Expr(Source{{56, 78}}, "global_1"), 42_a),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable write in 'target')");
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, WriteRead) {
|
||||
// *p1 = 42;
|
||||
// _ = global_1;
|
||||
Run(
|
||||
{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
Assign(Phony(), Expr(Source{{56, 78}}, "global_1")),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable read in 'target')");
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, WriteWrite) {
|
||||
// *p1 = 42;
|
||||
// global_1 = 42;
|
||||
Run(
|
||||
{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
Assign(Expr(Source{{56, 78}}, "global_1"), 42_a),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable write in 'target')");
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, ReadWriteThroughChain_GlobalViaArg) {
|
||||
// fn f2(p1 : ptr<private, i32>) {
|
||||
// *p1 = 42;
|
||||
// }
|
||||
// fn f1(p1 : ptr<private, i32>) {
|
||||
// _ = *p1;
|
||||
// f2(&global_1);
|
||||
// }
|
||||
//
|
||||
// f1(p1);
|
||||
Func("f2",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
});
|
||||
Func("f1",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
CallStmt(Call("f2", AddressOf(Source{{56, 78}}, "global_1"))),
|
||||
});
|
||||
Run(
|
||||
{
|
||||
CallStmt(Call("f1", "p1")),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable write in 'f1')");
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, ReadWriteThroughChain_Both) {
|
||||
// fn f2(p1 : ptr<private, i32>) {
|
||||
// _ = *p1;
|
||||
// global_1 = 42;
|
||||
// }
|
||||
// fn f1(p1 : ptr<private, i32>) {
|
||||
// f2(p1);
|
||||
// }
|
||||
//
|
||||
// f1(p1);
|
||||
Func("f2",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
Assign(Expr(Source{{56, 78}}, "global_1"), 42_a),
|
||||
});
|
||||
Func("f1",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
CallStmt(Call("f2", "p1")),
|
||||
});
|
||||
Run(
|
||||
{
|
||||
CallStmt(Call("f1", "p1")),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable write in 'f2')");
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, WriteReadThroughChain_GlobalViaArg) {
|
||||
// fn f2(p1 : ptr<private, i32>) {
|
||||
// _ = *p1;
|
||||
// }
|
||||
// fn f1(p1 : ptr<private, i32>) {
|
||||
// *p1 = 42;
|
||||
// f2(&global_1);
|
||||
// }
|
||||
//
|
||||
// f1(p1);
|
||||
Func("f2",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
});
|
||||
Func("f1",
|
||||
utils::Vector<const ast::Parameter*, 4>{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
CallStmt(Call("f2", AddressOf(Source{{56, 78}}, "global_1"))),
|
||||
});
|
||||
Run(
|
||||
{
|
||||
CallStmt(Call("f1", "p1")),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable read in 'f1')");
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, WriteReadThroughChain_Both) {
|
||||
// fn f2(p1 : ptr<private, i32>) {
|
||||
// *p1 = 42;
|
||||
// _ = global_1;
|
||||
// }
|
||||
// fn f1(p1 : ptr<private, i32>) {
|
||||
// f2(p1);
|
||||
// }
|
||||
//
|
||||
// f1(p1);
|
||||
Func("f2",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
Assign(Phony(), Expr(Source{{56, 78}}, "global_1")),
|
||||
});
|
||||
Func("f1",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
CallStmt(Call("f2", "p1")),
|
||||
});
|
||||
Run(
|
||||
{
|
||||
CallStmt(Call("f1", "p1")),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable read in 'f2')");
|
||||
}
|
||||
|
||||
TEST_P(OnePointerOneModuleScope, ReadWriteAcrossDifferentFunctions) {
|
||||
// fn f1(p1 : ptr<private, i32>) {
|
||||
// _ = *p1;
|
||||
// }
|
||||
// fn f2() {
|
||||
// global_1 = 42;
|
||||
// }
|
||||
//
|
||||
// f1(p1);
|
||||
// f2();
|
||||
Func("f1",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kPrivate)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), Deref("p1")),
|
||||
});
|
||||
Func("f2", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Expr(Source{{56, 78}}, "global_1"), 42_a),
|
||||
});
|
||||
Run(
|
||||
{
|
||||
CallStmt(Call("f1", "p1")),
|
||||
CallStmt(Call("f2")),
|
||||
},
|
||||
R"(12:34 warning: invalid aliased pointer argument
|
||||
56:78 note: aliases with module-scope variable write in 'f2')");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
|
||||
OnePointerOneModuleScope,
|
||||
::testing::Values(false, true),
|
||||
[](const ::testing::TestParamInfo<bool>& p) {
|
||||
return p.param ? "Aliased" : "Unaliased";
|
||||
});
|
||||
|
||||
// Base test harness for tests that use a potentially aliased pointer in a variety of expressions.
|
||||
//
|
||||
// fn target(p1 : ptr<function, i32>, p2 : ptr<function, i32>) {
|
||||
// *p1 = 42;
|
||||
// <test statements>
|
||||
// }
|
||||
// fn caller() {
|
||||
// var v1 : i32;
|
||||
// var v2 : i32;
|
||||
// target(&v1, aliased ? &v1 : &v2);
|
||||
// }
|
||||
class Use : public ResolverTestWithParam<bool> {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
Func("caller", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v1", ty.i32())),
|
||||
Decl(Var("v2", ty.i32())),
|
||||
CallStmt(Call("target", AddressOf(Source{{12, 34}}, "v1"),
|
||||
AddressOf(Source{{56, 78}}, GetParam() ? "v1" : "v2"))),
|
||||
});
|
||||
}
|
||||
|
||||
void Run(const ast::Statement* stmt, const char* err = nullptr) {
|
||||
Func("target",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<i32>(ast::AddressSpace::kFunction)),
|
||||
Param("p2", ty.pointer<i32>(ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p1"), 42_a),
|
||||
stmt,
|
||||
});
|
||||
if (GetParam() && err) {
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), err);
|
||||
} else {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(Use, NoAccess) {
|
||||
// Expect no errors even when aliasing occurs.
|
||||
Run(Assign(Phony(), 42_a));
|
||||
}
|
||||
|
||||
TEST_P(Use, Write_Increment) {
|
||||
// (*p2)++;
|
||||
Run(Increment(Deref("p2")), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Write_Decrement) {
|
||||
// (*p2)--;
|
||||
Run(Decrement(Deref("p2")), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Write_CompoundAssignment_LHS) {
|
||||
// *p2 += 42;
|
||||
Run(CompoundAssign(Deref("p2"), 42_a, ast::BinaryOp::kAdd),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_CompoundAssignment_RHS) {
|
||||
// var<private> global : i32;
|
||||
// global += *p2;
|
||||
GlobalVar("global", ast::AddressSpace::kPrivate, ty.i32());
|
||||
Run(CompoundAssign("global", Deref("p2"), ast::BinaryOp::kAdd),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_BinaryOp_LHS) {
|
||||
// _ = (*p2) + 1;
|
||||
Run(Assign(Phony(), Add(Deref("p2"), 1_a)), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_BinaryOp_RHS) {
|
||||
// _ = 1 + (*p2);
|
||||
Run(Assign(Phony(), Add(1_a, Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_UnaryMinus) {
|
||||
// _ = -(*p2);
|
||||
Run(Assign(Phony(), Negation(Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_FunctionCallArg) {
|
||||
// abs(*p2);
|
||||
Run(CallStmt(Call("abs", Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_Bitcast) {
|
||||
// _ = bitcast<f32>(*p2);
|
||||
Run(Assign(Phony(), Bitcast<f32>(Deref("p2"))),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_Convert) {
|
||||
// _ = f32(*p2);
|
||||
Run(Assign(Phony(), Construct<f32>(Deref("p2"))),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_IndexAccessor) {
|
||||
// var<private> data : array<f32, 4>;
|
||||
// _ = data[*p2];
|
||||
GlobalVar("data", ast::AddressSpace::kPrivate, ty.array<f32, 4>());
|
||||
Run(Assign(Phony(), IndexAccessor("data", Deref("p2"))),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_LetInitializer) {
|
||||
// let x = *p2;
|
||||
Run(Decl(Let("x", Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_VarInitializer) {
|
||||
// var x = *p2;
|
||||
Run(Decl(Var("x", ast::AddressSpace::kFunction, Deref("p2"))),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_ReturnValue) {
|
||||
// fn foo(p : ptr<function, i32>) -> i32 { return *p; }
|
||||
// foo(p2);
|
||||
Func("foo",
|
||||
utils::Vector{
|
||||
Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.i32(),
|
||||
utils::Vector{
|
||||
Return(Deref("p")),
|
||||
});
|
||||
Run(Assign(Phony(), Call("foo", "p2")), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, Read_Switch) {
|
||||
// Switch (*p2) { default {} }
|
||||
Run(Switch(Deref("p2"), utils::Vector{DefaultCase(Block())}),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(Use, NoAccess_AddressOf_Deref) {
|
||||
// Should not invoke the load-rule, and therefore expect no errors even when aliasing occurs.
|
||||
// let newp = &(*p2);
|
||||
Run(Decl(Let("newp", AddressOf(Deref("p2")))));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
|
||||
Use,
|
||||
::testing::Values(false, true),
|
||||
[](const ::testing::TestParamInfo<bool>& p) {
|
||||
return p.param ? "Aliased" : "Unaliased";
|
||||
});
|
||||
|
||||
// Base test harness for tests that use a potentially aliased pointer in a variety of expressions.
|
||||
// As above, but using the bool type to test expressions that invoke that load-rule for booleans.
|
||||
//
|
||||
// fn target(p1 : ptr<function, bool>, p2 : ptr<function, bool>) {
|
||||
// *p1 = true;
|
||||
// <test statements>
|
||||
// }
|
||||
// fn caller() {
|
||||
// var v1 : bool;
|
||||
// var v2 : bool;
|
||||
// target(&v1, aliased ? &v1 : &v2);
|
||||
// }
|
||||
class UseBool : public ResolverTestWithParam<bool> {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
Func("caller", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v1", ty.bool_())),
|
||||
Decl(Var("v2", ty.bool_())),
|
||||
CallStmt(Call("target", AddressOf(Source{{12, 34}}, "v1"),
|
||||
AddressOf(Source{{56, 78}}, GetParam() ? "v1" : "v2"))),
|
||||
});
|
||||
}
|
||||
|
||||
void Run(const ast::Statement* stmt, const char* err = nullptr) {
|
||||
Func("target",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer<bool>(ast::AddressSpace::kFunction)),
|
||||
Param("p2", ty.pointer<bool>(ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p1"), true),
|
||||
stmt,
|
||||
});
|
||||
if (GetParam() && err) {
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), err);
|
||||
} else {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(UseBool, Read_IfCond) {
|
||||
// if (*p2) {}
|
||||
Run(If(Deref("p2"), Block()), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(UseBool, Read_WhileCond) {
|
||||
// while (*p2) {}
|
||||
Run(While(Deref("p2"), Block()), R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(UseBool, Read_ForCond) {
|
||||
// for (; *p2; ) {}
|
||||
Run(For(nullptr, Deref("p2"), nullptr, Block()),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_P(UseBool, Read_BreakIf) {
|
||||
// loop { continuing { break if (*p2); } }
|
||||
Run(Loop(Block(), Block(BreakIf(Deref("p2")))),
|
||||
R"(56:78 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
|
||||
UseBool,
|
||||
::testing::Values(false, true),
|
||||
[](const ::testing::TestParamInfo<bool>& p) {
|
||||
return p.param ? "Aliased" : "Unaliased";
|
||||
});
|
||||
|
||||
TEST_F(ResolverAliasAnalysisTest, NoAccess_MemberAccessor) {
|
||||
// Should not invoke the load-rule, and therefore expect no errors even when aliasing occurs.
|
||||
//
|
||||
// struct S { a : i32 }
|
||||
// fn f2(p1 : ptr<function, S>, p2 : ptr<function, S>) {
|
||||
// let newp = &((*p2).a);
|
||||
// (*p1).a = 42;
|
||||
// }
|
||||
// fn f1() {
|
||||
// var v : S;
|
||||
// f2(&v, &v);
|
||||
// }
|
||||
Structure("S", utils::Vector{Member("a", ty.i32())});
|
||||
Func("f2",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)),
|
||||
Param("p2", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Let("newp", AddressOf(MemberAccessor(Deref("p2"), "a")))),
|
||||
Assign(MemberAccessor(Deref("p1"), "a"), 42_a),
|
||||
});
|
||||
Func("f1", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v", ty.type_name("S"))),
|
||||
CallStmt(Call("f2", AddressOf("v"), AddressOf("v"))),
|
||||
});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAliasAnalysisTest, Read_MemberAccessor) {
|
||||
// struct S { a : i32 }
|
||||
// fn f2(p1 : ptr<function, S>, p2 : ptr<function, S>) {
|
||||
// _ = (*p2).a;
|
||||
// *p1 = S();
|
||||
// }
|
||||
// fn f1() {
|
||||
// var v : S;
|
||||
// f2(&v, &v);
|
||||
// }
|
||||
Structure("S", utils::Vector{Member("a", ty.i32())});
|
||||
Func("f2",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)),
|
||||
Param("p2", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), MemberAccessor(Deref("p2"), "a")),
|
||||
Assign(Deref("p1"), Construct(ty.type_name("S"))),
|
||||
});
|
||||
Func("f1", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v", ty.type_name("S"))),
|
||||
CallStmt(
|
||||
Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))),
|
||||
});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), R"(56:76 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAliasAnalysisTest, Write_MemberAccessor) {
|
||||
// struct S { a : i32 }
|
||||
// fn f2(p1 : ptr<function, S>, p2 : ptr<function, S>) {
|
||||
// _ = *p2;
|
||||
// (*p1).a = 42;
|
||||
// }
|
||||
// fn f1() {
|
||||
// var v : S;
|
||||
// f2(&v, &v);
|
||||
// }
|
||||
Structure("S", utils::Vector{Member("a", ty.i32())});
|
||||
Func("f2",
|
||||
utils::Vector{
|
||||
Param("p1", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)),
|
||||
Param("p2", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Phony(), Deref("p2")),
|
||||
Assign(MemberAccessor(Deref("p1"), "a"), 42_a),
|
||||
});
|
||||
Func("f1", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v", ty.type_name("S"))),
|
||||
CallStmt(
|
||||
Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))),
|
||||
});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), R"(56:76 warning: invalid aliased pointer argument
|
||||
12:34 note: aliases with another argument passed here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAliasAnalysisTest, SinglePointerReadWrite) {
|
||||
// Test that we can both read and write from a single pointer parameter.
|
||||
//
|
||||
// fn f1(p : ptr<function, i32>) {
|
||||
// _ = *p;
|
||||
// *p = 42;
|
||||
// }
|
||||
// fn f2() {
|
||||
// var v : i32;
|
||||
// f1(&v);
|
||||
// }
|
||||
Func("f1",
|
||||
utils::Vector{
|
||||
Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v", ty.i32())),
|
||||
Assign(Phony(), Deref("p")),
|
||||
Assign(Deref("p"), 42_a),
|
||||
});
|
||||
Func("f2", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v", ty.i32())),
|
||||
CallStmt(Call("f1", AddressOf("v"))),
|
||||
});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAliasAnalysisTest, AliasingInsideFunction) {
|
||||
// Test that we can use two aliased pointers inside the same function they are created in.
|
||||
//
|
||||
// fn f1() {
|
||||
// var v : i32;
|
||||
// let p1 = &v;
|
||||
// let p2 = &v;
|
||||
// *p1 = 42;
|
||||
// *p2 = 42;
|
||||
// }
|
||||
Func("f1", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v", ty.i32())),
|
||||
Decl(Let("p1", AddressOf("v"))),
|
||||
Decl(Let("p2", AddressOf("v"))),
|
||||
Assign(Deref("p1"), 42_a),
|
||||
Assign(Deref("p2"), 42_a),
|
||||
});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAliasAnalysisTest, NonOverlappingCalls) {
|
||||
// Test that we pass the same pointer to multiple non-overlapping function calls.
|
||||
//
|
||||
// fn f2(p : ptr<function, i32>) {
|
||||
// *p = 42;
|
||||
// }
|
||||
// fn f3(p : ptr<function, i32>) {
|
||||
// *p = 42;
|
||||
// }
|
||||
// fn f1() {
|
||||
// var v : i32;
|
||||
// f2(&v);
|
||||
// f3(&v);
|
||||
// }
|
||||
Func("f2",
|
||||
utils::Vector{
|
||||
Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p"), 42_a),
|
||||
});
|
||||
Func("f3",
|
||||
utils::Vector{
|
||||
Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
|
||||
},
|
||||
ty.void_(),
|
||||
utils::Vector{
|
||||
Assign(Deref("p"), 42_a),
|
||||
});
|
||||
Func("f1", utils::Empty, ty.void_(),
|
||||
utils::Vector{
|
||||
Decl(Var("v", ty.i32())),
|
||||
CallStmt(Call("f2", AddressOf("v"))),
|
||||
CallStmt(Call("f3", AddressOf("v"))),
|
||||
});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tint::resolver
|
|
@ -372,6 +372,8 @@ sem::Variable* Resolver::Let(const ast::Let* v, bool is_global) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
RegisterLoadIfNeeded(rhs);
|
||||
|
||||
// If the variable has no declared type, infer it from the RHS
|
||||
if (!ty) {
|
||||
ty = rhs->Type()->UnwrapRef(); // Implicit load of RHS
|
||||
|
@ -578,6 +580,8 @@ sem::Variable* Resolver::Var(const ast::Var* var, bool is_global) {
|
|||
if (!storage_ty) {
|
||||
storage_ty = rhs->Type()->UnwrapRef(); // Implicit load of RHS
|
||||
}
|
||||
|
||||
RegisterLoadIfNeeded(rhs);
|
||||
}
|
||||
|
||||
if (!storage_ty) {
|
||||
|
@ -1288,6 +1292,8 @@ sem::IfStatement* Resolver::IfStatement(const ast::IfStatement* stmt) {
|
|||
sem->Behaviors() = cond->Behaviors();
|
||||
sem->Behaviors().Remove(sem::Behavior::kNext);
|
||||
|
||||
RegisterLoadIfNeeded(cond);
|
||||
|
||||
Mark(stmt->body);
|
||||
auto* body = builder_->create<sem::BlockStatement>(stmt->body, current_compound_statement_,
|
||||
current_function_);
|
||||
|
@ -1381,6 +1387,8 @@ sem::ForLoopStatement* Resolver::ForLoopStatement(const ast::ForLoopStatement* s
|
|||
}
|
||||
sem->SetCondition(cond);
|
||||
behaviors.Add(cond->Behaviors());
|
||||
|
||||
RegisterLoadIfNeeded(cond);
|
||||
}
|
||||
|
||||
if (auto* continuing = stmt->continuing) {
|
||||
|
@ -1425,6 +1433,8 @@ sem::WhileStatement* Resolver::WhileStatement(const ast::WhileStatement* stmt) {
|
|||
sem->SetCondition(cond);
|
||||
behaviors.Add(cond->Behaviors());
|
||||
|
||||
RegisterLoadIfNeeded(cond);
|
||||
|
||||
Mark(stmt->body);
|
||||
|
||||
auto* body = builder_->create<sem::LoopBlockStatement>(
|
||||
|
@ -1526,6 +1536,145 @@ sem::Expression* Resolver::Expression(const ast::Expression* root) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Resolver::RegisterLoadIfNeeded(const sem::Expression* expr) {
|
||||
if (!expr) {
|
||||
return;
|
||||
}
|
||||
if (!expr->Type()->Is<sem::Reference>()) {
|
||||
return;
|
||||
}
|
||||
if (!current_function_) {
|
||||
// There is currently no situation where the Load Rule can be invoked outside of a function.
|
||||
return;
|
||||
}
|
||||
auto& info = alias_analysis_infos_[current_function_];
|
||||
Switch(
|
||||
expr->RootIdentifier(),
|
||||
[&](const sem::GlobalVariable* global) {
|
||||
info.module_scope_reads.insert({global, expr});
|
||||
},
|
||||
[&](const sem::Parameter* param) { info.parameter_reads.insert(param); });
|
||||
}
|
||||
|
||||
void Resolver::RegisterStore(const sem::Expression* expr) {
|
||||
auto& info = alias_analysis_infos_[current_function_];
|
||||
Switch(
|
||||
expr->RootIdentifier(),
|
||||
[&](const sem::GlobalVariable* global) {
|
||||
info.module_scope_writes.insert({global, expr});
|
||||
},
|
||||
[&](const sem::Parameter* param) { info.parameter_writes.insert(param); });
|
||||
}
|
||||
|
||||
bool Resolver::AliasAnalysis(const sem::Call* call) {
|
||||
auto* target = call->Target()->As<sem::Function>();
|
||||
if (!target) {
|
||||
return true;
|
||||
}
|
||||
if (validator_.IsValidationDisabled(target->Declaration()->attributes,
|
||||
ast::DisabledValidation::kIgnorePointerAliasing)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper to generate an aliasing error diagnostic.
|
||||
struct Alias {
|
||||
const sem::Expression* expr; // the "other expression"
|
||||
enum { Argument, ModuleScope } type; // the type of the "other" expression
|
||||
std::string access; // the access performed for the "other" expression
|
||||
};
|
||||
auto make_error = [&](const sem::Expression* arg, Alias&& var) {
|
||||
// TODO(crbug.com/tint/1675): Switch to error and return false after deprecation period.
|
||||
AddWarning("invalid aliased pointer argument", arg->Declaration()->source);
|
||||
switch (var.type) {
|
||||
case Alias::Argument:
|
||||
AddNote("aliases with another argument passed here",
|
||||
var.expr->Declaration()->source);
|
||||
break;
|
||||
case Alias::ModuleScope: {
|
||||
auto* func = var.expr->Stmt()->Function();
|
||||
auto func_name = builder_->Symbols().NameFor(func->Declaration()->symbol);
|
||||
AddNote(
|
||||
"aliases with module-scope variable " + var.access + " in '" + func_name + "'",
|
||||
var.expr->Declaration()->source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto& args = call->Arguments();
|
||||
auto& target_info = alias_analysis_infos_[target];
|
||||
auto& caller_info = alias_analysis_infos_[current_function_];
|
||||
|
||||
// Track the set of root identifiers that are read and written by arguments passed in this call.
|
||||
std::unordered_map<const sem::Variable*, const sem::Expression*> arg_reads;
|
||||
std::unordered_map<const sem::Variable*, const sem::Expression*> arg_writes;
|
||||
for (size_t i = 0; i < args.Length(); i++) {
|
||||
auto* arg = args[i];
|
||||
if (!arg->Type()->Is<sem::Pointer>()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* root = arg->RootIdentifier();
|
||||
if (target_info.parameter_writes.count(target->Parameters()[i])) {
|
||||
// Arguments that are written to can alias with any other argument or module-scope
|
||||
// variable access.
|
||||
if (arg_writes.count(root)) {
|
||||
return make_error(arg, {arg_writes.at(root), Alias::Argument, "write"});
|
||||
}
|
||||
if (arg_reads.count(root)) {
|
||||
return make_error(arg, {arg_reads.at(root), Alias::Argument, "read"});
|
||||
}
|
||||
if (target_info.module_scope_reads.count(root)) {
|
||||
return make_error(
|
||||
arg, {target_info.module_scope_reads.at(root), Alias::ModuleScope, "read"});
|
||||
}
|
||||
if (target_info.module_scope_writes.count(root)) {
|
||||
return make_error(
|
||||
arg, {target_info.module_scope_writes.at(root), Alias::ModuleScope, "write"});
|
||||
}
|
||||
arg_writes.insert({root, arg});
|
||||
|
||||
// Propagate the write access to the caller.
|
||||
Switch(
|
||||
root,
|
||||
[&](const sem::GlobalVariable* global) {
|
||||
caller_info.module_scope_writes.insert({global, arg});
|
||||
},
|
||||
[&](const sem::Parameter* param) { caller_info.parameter_writes.insert(param); });
|
||||
} else if (target_info.parameter_reads.count(target->Parameters()[i])) {
|
||||
// Arguments that are read from can alias with arguments or module-scope variables that
|
||||
// are written to.
|
||||
if (arg_writes.count(root)) {
|
||||
return make_error(arg, {arg_writes.at(root), Alias::Argument, "write"});
|
||||
}
|
||||
if (target_info.module_scope_writes.count(root)) {
|
||||
return make_error(
|
||||
arg, {target_info.module_scope_writes.at(root), Alias::ModuleScope, "write"});
|
||||
}
|
||||
arg_reads.insert({root, arg});
|
||||
|
||||
// Propagate the read access to the caller.
|
||||
Switch(
|
||||
root,
|
||||
[&](const sem::GlobalVariable* global) {
|
||||
caller_info.module_scope_reads.insert({global, arg});
|
||||
},
|
||||
[&](const sem::Parameter* param) { caller_info.parameter_reads.insert(param); });
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate module-scope variable uses to the caller.
|
||||
for (auto read : target_info.module_scope_reads) {
|
||||
caller_info.module_scope_reads.insert({read.first, read.second});
|
||||
}
|
||||
for (auto write : target_info.module_scope_writes) {
|
||||
caller_info.module_scope_writes.insert({write.first, write.second});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const sem::Type* Resolver::ConcreteType(const sem::Type* ty,
|
||||
const sem::Type* target_ty,
|
||||
const Source& source) {
|
||||
|
@ -1668,6 +1817,7 @@ sem::Expression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* exp
|
|||
// vec2(1, 2)[runtime-index]
|
||||
obj = Materialize(obj);
|
||||
}
|
||||
RegisterLoadIfNeeded(idx);
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1725,6 +1875,8 @@ sem::Expression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
RegisterLoadIfNeeded(inner);
|
||||
|
||||
const sem::Constant* val = nullptr;
|
||||
if (auto r = const_eval_.Bitcast(ty, inner)) {
|
||||
val = r.Get();
|
||||
|
@ -1764,6 +1916,8 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
|
|||
args.Push(arg);
|
||||
args_stage = sem::EarliestStage(args_stage, arg->Stage());
|
||||
arg_behaviors.Add(arg->Behaviors());
|
||||
|
||||
RegisterLoadIfNeeded(arg);
|
||||
}
|
||||
arg_behaviors.Remove(sem::Behavior::kNext);
|
||||
|
||||
|
@ -2205,6 +2359,10 @@ sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
|
|||
current_function_->AddTransitivelyReferencedGlobal(var);
|
||||
}
|
||||
|
||||
if (!AliasAnalysis(call)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Note: Validation *must* be performed before calling this method.
|
||||
CollectTextureSamplerPairs(target, call->Arguments());
|
||||
}
|
||||
|
@ -2520,6 +2678,9 @@ sem::Expression* Resolver::Binary(const ast::BinaryExpression* expr) {
|
|||
}
|
||||
}
|
||||
|
||||
RegisterLoadIfNeeded(lhs);
|
||||
RegisterLoadIfNeeded(rhs);
|
||||
|
||||
const sem::Constant* value = nullptr;
|
||||
if (stage == sem::EvaluationStage::kConstant) {
|
||||
if (op.const_eval_fn) {
|
||||
|
@ -2627,6 +2788,7 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
|
|||
stage = sem::EvaluationStage::kRuntime;
|
||||
}
|
||||
}
|
||||
RegisterLoadIfNeeded(expr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -3076,6 +3238,8 @@ sem::Statement* Resolver::ReturnStatement(const ast::ReturnStatement* stmt) {
|
|||
}
|
||||
behaviors.Add(expr->Behaviors() - sem::Behavior::kNext);
|
||||
value_ty = expr->Type()->UnwrapRef();
|
||||
|
||||
RegisterLoadIfNeeded(expr);
|
||||
} else {
|
||||
value_ty = builder_->create<sem::Void>();
|
||||
}
|
||||
|
@ -3099,6 +3263,8 @@ sem::SwitchStatement* Resolver::SwitchStatement(const ast::SwitchStatement* stmt
|
|||
}
|
||||
behaviors = cond->Behaviors() - sem::Behavior::kNext;
|
||||
|
||||
RegisterLoadIfNeeded(cond);
|
||||
|
||||
auto* cond_ty = cond->Type()->UnwrapRef();
|
||||
|
||||
// Determine the common type across all selectors and the switch expression
|
||||
|
@ -3202,12 +3368,18 @@ sem::Statement* Resolver::AssignmentStatement(const ast::AssignmentStatement* st
|
|||
}
|
||||
}
|
||||
|
||||
RegisterLoadIfNeeded(rhs);
|
||||
|
||||
auto& behaviors = sem->Behaviors();
|
||||
behaviors = rhs->Behaviors();
|
||||
if (!is_phony_assignment) {
|
||||
behaviors.Add(lhs->Behaviors());
|
||||
}
|
||||
|
||||
if (!is_phony_assignment) {
|
||||
RegisterStore(lhs);
|
||||
}
|
||||
|
||||
return validator_.Assignment(stmt, sem_.TypeOf(stmt->rhs));
|
||||
});
|
||||
}
|
||||
|
@ -3234,6 +3406,8 @@ sem::Statement* Resolver::BreakIfStatement(const ast::BreakIfStatement* stmt) {
|
|||
sem->Behaviors() = cond->Behaviors();
|
||||
sem->Behaviors().Add(sem::Behavior::kBreak);
|
||||
|
||||
RegisterLoadIfNeeded(cond);
|
||||
|
||||
return validator_.BreakIfStatement(sem, current_statement_);
|
||||
});
|
||||
}
|
||||
|
@ -3265,6 +3439,9 @@ sem::Statement* Resolver::CompoundAssignmentStatement(
|
|||
return false;
|
||||
}
|
||||
|
||||
RegisterLoadIfNeeded(rhs);
|
||||
RegisterStore(lhs);
|
||||
|
||||
sem->Behaviors() = rhs->Behaviors() + lhs->Behaviors();
|
||||
|
||||
auto* lhs_ty = lhs->Type()->UnwrapRef();
|
||||
|
@ -3317,6 +3494,9 @@ sem::Statement* Resolver::IncrementDecrementStatement(
|
|||
}
|
||||
sem->Behaviors() = lhs->Behaviors();
|
||||
|
||||
RegisterLoadIfNeeded(lhs);
|
||||
RegisterStore(lhs);
|
||||
|
||||
return validator_.IncrementDecrementStatement(stmt);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
@ -153,6 +155,18 @@ class Resolver {
|
|||
sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
|
||||
sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
|
||||
|
||||
/// Register a memory load from an expression, to track accesses to root identifiers in order to
|
||||
/// perform alias analysis.
|
||||
void RegisterLoadIfNeeded(const sem::Expression* expr);
|
||||
|
||||
/// Register a memory store to an expression, to track accesses to root identifiers in order to
|
||||
/// perform alias analysis.
|
||||
void RegisterStore(const sem::Expression* expr);
|
||||
|
||||
/// Perform pointer alias analysis for `call`.
|
||||
/// @returns true is the call arguments are free from aliasing issues, false otherwise.
|
||||
bool AliasAnalysis(const sem::Call* call);
|
||||
|
||||
/// If `expr` is not of an abstract-numeric type, then Materialize() will just return `expr`.
|
||||
/// If `expr` is of an abstract-numeric type:
|
||||
/// * Materialize will create and return a sem::Materialize node wrapping `expr`.
|
||||
|
@ -423,6 +437,19 @@ class Resolver {
|
|||
const char* constraint = nullptr;
|
||||
};
|
||||
|
||||
/// AliasAnalysisInfo captures the memory accesses performed by a given function for the purpose
|
||||
/// of determining if any two arguments alias at any callsite.
|
||||
struct AliasAnalysisInfo {
|
||||
/// The set of module-scope variables that are written to, and where that write occurs.
|
||||
std::unordered_map<const sem::Variable*, const sem::Expression*> module_scope_writes;
|
||||
/// The set of module-scope variables that are read from, and where that read occurs.
|
||||
std::unordered_map<const sem::Variable*, const sem::Expression*> module_scope_reads;
|
||||
/// The set of function parameters that are written to.
|
||||
std::unordered_set<const sem::Variable*> parameter_writes;
|
||||
/// The set of function parameters that are read from.
|
||||
std::unordered_set<const sem::Variable*> parameter_reads;
|
||||
};
|
||||
|
||||
ProgramBuilder* const builder_;
|
||||
diag::List& diagnostics_;
|
||||
ConstEval const_eval_;
|
||||
|
@ -435,6 +462,7 @@ class Resolver {
|
|||
utils::Hashmap<const sem::Type*, const Source*, 8> atomic_composite_info_;
|
||||
utils::Bitset<0> marked_;
|
||||
ExprEvalStageConstraint expr_eval_stage_constraint_;
|
||||
std::unordered_map<const sem::Function*, AliasAnalysisInfo> alias_analysis_infos_;
|
||||
utils::Hashmap<OverrideId, const sem::Variable*, 8> override_ids_;
|
||||
utils::Hashmap<ArrayInitializerSig, sem::CallTarget*, 8> array_inits_;
|
||||
utils::Hashmap<StructInitializerSig, sem::CallTarget*, 8> struct_inits_;
|
||||
|
|
|
@ -354,6 +354,7 @@ struct ModuleScopeVarToEntryPointParam::State {
|
|||
for (auto* func_ast : functions_to_process) {
|
||||
auto* func_sem = ctx.src->Sem().Get(func_ast);
|
||||
bool is_entry_point = func_ast->IsEntryPoint();
|
||||
bool needs_pointer_aliasing = false;
|
||||
|
||||
// Map module-scope variables onto their replacement.
|
||||
struct NewVar {
|
||||
|
@ -424,6 +425,9 @@ struct ModuleScopeVarToEntryPointParam::State {
|
|||
is_wrapped);
|
||||
} else {
|
||||
ProcessVariableInUserFunction(func_ast, var, new_var_symbol, is_pointer);
|
||||
if (var->AddressSpace() == ast::AddressSpace::kWorkgroup) {
|
||||
needs_pointer_aliasing = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Record the replacement symbol.
|
||||
|
@ -434,6 +438,12 @@ struct ModuleScopeVarToEntryPointParam::State {
|
|||
ReplaceUsesInFunction(func_ast, var, new_var_symbol, is_pointer, is_wrapped);
|
||||
}
|
||||
|
||||
// Allow pointer aliasing if needed.
|
||||
if (needs_pointer_aliasing) {
|
||||
ctx.InsertBack(func_ast->attributes,
|
||||
ctx.dst->Disable(ast::DisabledValidation::kIgnorePointerAliasing));
|
||||
}
|
||||
|
||||
if (!workgroup_parameter_members.IsEmpty()) {
|
||||
// Create the workgroup memory parameter.
|
||||
// The parameter is a struct that contains members for each workgroup variable.
|
||||
|
|
|
@ -125,12 +125,14 @@ fn zoo(@internal(disable_validation__ignore_address_space) @internal(disable_val
|
|||
*(tint_symbol) = (*(tint_symbol) * 2.0);
|
||||
}
|
||||
|
||||
@internal(disable_validation__ignore_pointer_aliasing)
|
||||
fn bar(a : f32, b : f32, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<private, f32>, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<workgroup, f32>) {
|
||||
*(tint_symbol_1) = a;
|
||||
*(tint_symbol_2) = b;
|
||||
zoo(tint_symbol_1);
|
||||
}
|
||||
|
||||
@internal(disable_validation__ignore_pointer_aliasing)
|
||||
fn foo(a : f32, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<private, f32>, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_4 : ptr<workgroup, f32>) {
|
||||
let b : f32 = 2.0;
|
||||
bar(a, b, tint_symbol_3, tint_symbol_4);
|
||||
|
@ -188,6 +190,7 @@ fn main() {
|
|||
foo(1.0, &(tint_symbol_5), &(tint_symbol_6));
|
||||
}
|
||||
|
||||
@internal(disable_validation__ignore_pointer_aliasing)
|
||||
fn foo(a : f32, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_3 : ptr<private, f32>, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_4 : ptr<workgroup, f32>) {
|
||||
let b : f32 = 2.0;
|
||||
bar(a, b, tint_symbol_3, tint_symbol_4);
|
||||
|
@ -197,6 +200,7 @@ fn foo(a : f32, @internal(disable_validation__ignore_address_space) @internal(di
|
|||
fn no_uses() {
|
||||
}
|
||||
|
||||
@internal(disable_validation__ignore_pointer_aliasing)
|
||||
fn bar(a : f32, b : f32, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_1 : ptr<private, f32>, @internal(disable_validation__ignore_address_space) @internal(disable_validation__ignore_invalid_pointer_argument) tint_symbol_2 : ptr<workgroup, f32>) {
|
||||
*(tint_symbol_1) = a;
|
||||
*(tint_symbol_2) = b;
|
||||
|
|
Loading…
Reference in New Issue