blob: db78d64b832c3deecc15e3196716a91c5822bcdd [file] [log] [blame] [edit]
// 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 {
type::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 == type::AddressSpace::kFunction) {
body.Push(Decl(Var("v1", ty.i32())));
body.Push(Decl(Var("v2", ty.i32())));
} else {
GlobalVar("v1", type::AddressSpace::kPrivate, ty.i32());
GlobalVar("v2", type::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{type::AddressSpace::kFunction, false},
TwoPointerConfig{type::AddressSpace::kFunction, true},
TwoPointerConfig{type::AddressSpace::kPrivate, false},
TwoPointerConfig{type::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", type::AddressSpace::kPrivate, ty.i32());
GlobalVar("global_2", type::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>(type::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>(type::AddressSpace::kPrivate)),
},
ty.void_(),
utils::Vector{
Assign(Deref("p1"), 42_a),
});
Func("f1",
utils::Vector<const ast::Parameter*, 4>{
Param("p1", ty.pointer<i32>(type::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>(type::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>(type::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>(type::AddressSpace::kPrivate)),
},
ty.void_(),
utils::Vector{
Assign(Phony(), Deref("p1")),
});
Func("f1",
utils::Vector<const ast::Parameter*, 4>{
Param("p1", ty.pointer<i32>(type::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>(type::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>(type::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>(type::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>(type::AddressSpace::kFunction)),
Param("p2", ty.pointer<i32>(type::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", type::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", type::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", type::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>(type::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>(type::AddressSpace::kFunction)),
Param("p2", ty.pointer<bool>(type::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"), type::AddressSpace::kFunction)),
Param("p2", ty.pointer(ty.type_name("S"), type::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"), type::AddressSpace::kFunction)),
Param("p2", ty.pointer(ty.type_name("S"), type::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"), type::AddressSpace::kFunction)),
Param("p2", ty.pointer(ty.type_name("S"), type::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, Read_MultiComponentSwizzle) {
// fn f2(p1 : ptr<function, vec4<f32>, p2 : ptr<function, vec4<f32>) {
// _ = (*p2).zy;
// *p1 = vec4<f32>();
// }
// fn f1() {
// var v : vec4<f32>;
// f2(&v, &v);
// }
Structure("S", utils::Vector{Member("a", ty.i32())});
Func("f2",
utils::Vector{
Param("p1", ty.pointer(ty.vec4<f32>(), type::AddressSpace::kFunction)),
Param("p2", ty.pointer(ty.vec4<f32>(), type::AddressSpace::kFunction)),
},
ty.void_(),
utils::Vector{
Assign(Phony(), MemberAccessor(Deref("p2"), "zy")),
Assign(Deref("p1"), Construct(ty.vec4<f32>())),
});
Func("f1", utils::Empty, ty.void_(),
utils::Vector{
Decl(Var("v", ty.vec4<f32>())),
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>(type::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>(type::AddressSpace::kFunction)),
},
ty.void_(),
utils::Vector{
Assign(Deref("p"), 42_a),
});
Func("f3",
utils::Vector{
Param("p", ty.pointer<i32>(type::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