// Copyright 2022 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <gtest/gtest.h>

#include "gmock/gmock.h"
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/wgsl/enums.h"
#include "src/tint/lang/wgsl/resolver/resolver.h"
#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"
#include "src/tint/utils/text/string_stream.h"

namespace tint::resolver {
namespace {

using namespace tint::core::fluent_types;     // NOLINT
using namespace tint::core::number_suffixes;  // NOLINT

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 {
    const core::AddressSpace address_space;  // The address space for the pointers.
    const bool aliased;                      // Whether the pointers alias or not.
};
class TwoPointers : public ResolverTestWithParam<TwoPointerConfig> {
  protected:
    void SetUp() override {
        Vector<const ast::Statement*, 4> body;
        if (GetParam().address_space == core::AddressSpace::kFunction) {
            body.Push(Decl(Var("v1", ty.i32())));
            body.Push(Decl(Var("v2", ty.i32())));
        } else {
            GlobalVar("v1", core::AddressSpace::kPrivate, ty.i32());
            GlobalVar("v2", core::AddressSpace::kPrivate, ty.i32());
        }
        body.Push(CallStmt(Call("target", AddressOf(Source{{12, 34}}, "v1"),
                                AddressOf(Source{{56, 78}}, GetParam().aliased ? "v1" : "v2"))));
        Func("caller", tint::Empty, ty.void_(), body);
    }

    void Run(Vector<const ast::Statement*, 4>&& body, const char* err = nullptr) {
        auto addrspace = GetParam().address_space;
        Func("target",
             Vector{
                 Param("p1", ty.ptr<i32>(addrspace)),
                 Param("p2", ty.ptr<i32>(addrspace)),
             },
             ty.void_(), std::move(body));
        if (GetParam().aliased && err) {
            EXPECT_FALSE(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 error: 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 error: 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 error: 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",
         Vector{
             Param("p1", ty.ptr<i32>(GetParam().address_space)),
             Param("p2", ty.ptr<i32>(GetParam().address_space)),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), Deref("p1")),
             Assign(Deref("p2"), 42_a),
         });
    Func("f1",
         Vector{
             Param("p1", ty.ptr<i32>(GetParam().address_space)),
             Param("p2", ty.ptr<i32>(GetParam().address_space)),
         },
         ty.void_(),
         Vector{
             CallStmt(Call("f2", "p1", "p2")),
         });
    Run(
        {
            CallStmt(Call("f1", "p1", "p2")),
        },
        R"(56:78 error: 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",
         Vector{
             Param("p1", ty.ptr<i32>(GetParam().address_space)),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), Deref("p1")),
         });
    Func("f2",
         Vector{
             Param("p2", ty.ptr<i32>(GetParam().address_space)),
         },
         ty.void_(),
         Vector{
             Assign(Deref("p2"), 42_a),
         });
    Run(
        {
            CallStmt(Call("f1", "p1")),
            CallStmt(Call("f2", "p2")),
        },
        R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
                         TwoPointers,
                         ::testing::Values(TwoPointerConfig{core::AddressSpace::kFunction, false},
                                           TwoPointerConfig{core::AddressSpace::kFunction, true},
                                           TwoPointerConfig{core::AddressSpace::kPrivate, false},
                                           TwoPointerConfig{core::AddressSpace::kPrivate, true}),
                         [](const ::testing::TestParamInfo<TwoPointers::ParamType>& p) {
                             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", core::AddressSpace::kPrivate, ty.i32());
        GlobalVar("global_2", core::AddressSpace::kPrivate, ty.i32());
        Func("caller", tint::Empty, ty.void_(),
             Vector{
                 CallStmt(Call("target",
                               AddressOf(Source{{12, 34}}, GetParam() ? "global_1" : "global_2"))),
             });
    }

    void Run(Vector<const ast::Statement*, 4>&& body, const char* err = nullptr) {
        Func("target",
             Vector{
                 Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
             },
             ty.void_(), std::move(body));
        if (GetParam() && err) {
            EXPECT_FALSE(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 error: 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 error: 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 error: 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",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             Assign(Deref("p1"), 42_a),
         });
    Func("f1",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), Deref("p1")),
             CallStmt(Call("f2", AddressOf(Source{{56, 78}}, "global_1"))),
         });
    Run(
        {
            CallStmt(Call("f1", "p1")),
        },
        R"(12:34 error: 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",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), Deref("p1")),
             Assign(Expr(Source{{56, 78}}, "global_1"), 42_a),
         });
    Func("f1",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             CallStmt(Call("f2", "p1")),
         });
    Run(
        {
            CallStmt(Call("f1", "p1")),
        },
        R"(12:34 error: 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",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), Deref("p1")),
         });
    Func("f1",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             Assign(Deref("p1"), 42_a),
             CallStmt(Call("f2", AddressOf(Source{{56, 78}}, "global_1"))),
         });
    Run(
        {
            CallStmt(Call("f1", "p1")),
        },
        R"(12:34 error: 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",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             Assign(Deref("p1"), 42_a),
             Assign(Phony(), Expr(Source{{56, 78}}, "global_1")),
         });
    Func("f1",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             CallStmt(Call("f2", "p1")),
         });
    Run(
        {
            CallStmt(Call("f1", "p1")),
        },
        R"(12:34 error: 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",
         Vector{
             Param("p1", ty.ptr<i32>(core::AddressSpace::kPrivate)),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), Deref("p1")),
         });
    Func("f2", tint::Empty, ty.void_(),
         Vector{
             Assign(Expr(Source{{56, 78}}, "global_1"), 42_a),
         });
    Run(
        {
            CallStmt(Call("f1", "p1")),
            CallStmt(Call("f2")),
        },
        R"(12:34 error: 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", tint::Empty, ty.void_(),
             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",
             Vector{
                 Param("p1", ty.ptr<i32>(core::AddressSpace::kFunction)),
                 Param("p2", ty.ptr<i32>(core::AddressSpace::kFunction)),
             },
             ty.void_(),
             Vector{
                 Assign(Deref("p1"), 42_a),
                 stmt,
             });
        if (GetParam() && err) {
            EXPECT_FALSE(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 error: 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 error: 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, core::BinaryOp::kAdd),
        R"(56:78 error: 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", core::AddressSpace::kPrivate, ty.i32());
    Run(CompoundAssign("global", Deref("p2"), core::BinaryOp::kAdd),
        R"(56:78 error: 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 error: 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 error: 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 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(Use, Read_FunctionCallArg) {
    // abs(*p2);
    Run(Assign(Phony(), Call("abs", Deref("p2"))), R"(56:78 error: 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 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(Use, Read_Convert) {
    // _ = f32(*p2);
    Run(Assign(Phony(), Call<f32>(Deref("p2"))),
        R"(56:78 error: 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", core::AddressSpace::kPrivate, ty.array<f32, 4>());
    Run(Assign(Phony(), IndexAccessor("data", Deref("p2"))),
        R"(56:78 error: 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 error: 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", core::AddressSpace::kFunction, Deref("p2"))),
        R"(56:78 error: 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",
         Vector{
             Param("p", ty.ptr<i32>(core::AddressSpace::kFunction)),
         },
         ty.i32(),
         Vector{
             Return(Deref("p")),
         });
    Run(Assign(Phony(), Call("foo", "p2")), R"(56:78 error: 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"), Vector{DefaultCase(Block())}),
        R"(56:78 error: 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", tint::Empty, ty.void_(),
             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",
             Vector{
                 Param("p1", ty.ptr<bool>(core::AddressSpace::kFunction)),
                 Param("p2", ty.ptr<bool>(core::AddressSpace::kFunction)),
             },
             ty.void_(),
             Vector{
                 Assign(Deref("p1"), true),
                 stmt,
             });
        if (GetParam() && err) {
            EXPECT_FALSE(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 error: 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 error: 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 error: 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 error: 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", Vector{Member("a", ty.i32())});
    Func("f2",
         Vector{
             Param("p1", ty.ptr<function>(ty.AsType("S"))),
             Param("p2", ty.ptr<function>(ty.AsType("S"))),
         },
         ty.void_(),
         Vector{
             Decl(Let("newp", AddressOf(MemberAccessor(Deref("p2"), "a")))),
             Assign(MemberAccessor(Deref("p1"), "a"), 42_a),
         });
    Func("f1", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("v", ty.AsType("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", Vector{Member("a", ty.i32())});
    Func("f2",
         Vector{
             Param("p1", ty.ptr<function>(ty.AsType("S"))),
             Param("p2", ty.ptr<function>(ty.AsType("S"))),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), MemberAccessor(Deref("p2"), "a")),
             Assign(Deref("p1"), Call("S")),
         });
    Func("f1", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("v", ty.AsType("S"))),
             CallStmt(
                 Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))),
         });
    EXPECT_FALSE(r()->Resolve()) << r()->error();
    EXPECT_EQ(r()->error(), R"(56:76 error: 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", Vector{Member("a", ty.i32())});
    Func("f2",
         Vector{
             Param("p1", ty.ptr<function>(ty.AsType("S"))),
             Param("p2", ty.ptr<function>(ty.AsType("S"))),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), Deref("p2")),
             Assign(MemberAccessor(Deref("p1"), "a"), 42_a),
         });
    Func("f1", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("v", ty.AsType("S"))),
             CallStmt(
                 Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))),
         });
    EXPECT_FALSE(r()->Resolve()) << r()->error();
    EXPECT_EQ(r()->error(), R"(56:76 error: 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", Vector{Member("a", ty.i32())});
    Func("f2",
         Vector{
             Param("p1", ty.ptr<function, vec4<f32>>()),
             Param("p2", ty.ptr<function, vec4<f32>>()),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), MemberAccessor(Deref("p2"), "zy")),
             Assign(Deref("p1"), Call<vec4<f32>>()),
         });
    Func("f1", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("v", ty.vec4<f32>())),
             CallStmt(
                 Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))),
         });
    EXPECT_FALSE(r()->Resolve()) << r()->error();
    EXPECT_EQ(r()->error(), R"(56:76 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_F(ResolverAliasAnalysisTest, Read_MultiComponentSwizzle_FromPointer) {
    // 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", Vector{Member("a", ty.i32())});
    Func("f2",
         Vector{
             Param("p1", ty.ptr<function, vec4<f32>>()),
             Param("p2", ty.ptr<function, vec4<f32>>()),
         },
         ty.void_(),
         Vector{
             Assign(Phony(), MemberAccessor("p2", "zy")),
             Assign(Deref("p1"), Call<vec4<f32>>()),
         });
    Func("f1", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("v", ty.vec4<f32>())),
             CallStmt(
                 Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))),
         });
    EXPECT_FALSE(r()->Resolve()) << r()->error();
    EXPECT_EQ(r()->error(), R"(56:76 error: 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",
         Vector{
             Param("p", ty.ptr<i32>(core::AddressSpace::kFunction)),
         },
         ty.void_(),
         Vector{
             Decl(Var("v", ty.i32())),
             Assign(Phony(), Deref("p")),
             Assign(Deref("p"), 42_a),
         });
    Func("f2", tint::Empty, ty.void_(),
         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", tint::Empty, ty.void_(),
         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",
         Vector{
             Param("p", ty.ptr<i32>(core::AddressSpace::kFunction)),
         },
         ty.void_(),
         Vector{
             Assign(Deref("p"), 42_a),
         });
    Func("f3",
         Vector{
             Param("p", ty.ptr<i32>(core::AddressSpace::kFunction)),
         },
         ty.void_(),
         Vector{
             Assign(Deref("p"), 42_a),
         });
    Func("f1", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("v", ty.i32())),
             CallStmt(Call("f2", AddressOf("v"))),
             CallStmt(Call("f3", AddressOf("v"))),
         });
    EXPECT_TRUE(r()->Resolve()) << r()->error();
}

////////////////////////////////////////////////////////////////////////////////
// Atomics
////////////////////////////////////////////////////////////////////////////////
class AtomicPointers
    : public ResolverTestWithParam<
          std::tuple<wgsl::BuiltinFn, wgsl::BuiltinFn, core::AddressSpace, bool /* aliased */>> {
  protected:
    static constexpr std::string_view kPass = "<PASS>";

    ast::Type Ptr() {
        auto address_space = std::get<2>(GetParam());
        if (address_space == storage) {
            return ty.ptr<storage, atomic<i32>, read_write>();
        }
        return ty.ptr<atomic<i32>>(address_space);
    }

    void SetUp() override {
        auto address_space = std::get<2>(GetParam());
        if (address_space == storage) {
            GlobalVar("v1", address_space, read_write, ty.Of<atomic<i32>>(),  //
                      Binding(0_a), Group(0_a));
            GlobalVar("v2", address_space, read_write, ty.Of<atomic<i32>>(),  //
                      Binding(1_a), Group(0_a));
        } else {
            GlobalVar("v1", address_space, ty.Of<atomic<i32>>());
            GlobalVar("v2", address_space, ty.Of<atomic<i32>>());
        }
    }

    bool IsWrite(wgsl::BuiltinFn fn) const {
        switch (fn) {
            case wgsl::BuiltinFn::kAtomicStore:
            case wgsl::BuiltinFn::kAtomicAdd:
            case wgsl::BuiltinFn::kAtomicSub:
            case wgsl::BuiltinFn::kAtomicMax:
            case wgsl::BuiltinFn::kAtomicMin:
            case wgsl::BuiltinFn::kAtomicAnd:
            case wgsl::BuiltinFn::kAtomicOr:
            case wgsl::BuiltinFn::kAtomicXor:
            case wgsl::BuiltinFn::kAtomicExchange:
            case wgsl::BuiltinFn::kAtomicCompareExchangeWeak:
                return true;
            default:
                return false;
        }
    }

    bool ShouldPass() const {
        auto [builtin_a, builtin_b, space, aliased] = GetParam();
        bool fail = aliased && (IsWrite(builtin_a) || IsWrite(builtin_b));
        return !fail;
    }

    const ast::Statement* CallBuiltin(wgsl::BuiltinFn fn, std::string_view ptr) {
        switch (fn) {
            case wgsl::BuiltinFn::kAtomicLoad:
                return CallStmt(Call(fn, ptr));
            case wgsl::BuiltinFn::kAtomicStore:
            case wgsl::BuiltinFn::kAtomicAdd:
            case wgsl::BuiltinFn::kAtomicSub:
            case wgsl::BuiltinFn::kAtomicMax:
            case wgsl::BuiltinFn::kAtomicMin:
            case wgsl::BuiltinFn::kAtomicAnd:
            case wgsl::BuiltinFn::kAtomicOr:
            case wgsl::BuiltinFn::kAtomicXor:
            case wgsl::BuiltinFn::kAtomicExchange:
                return CallStmt(Call(fn, ptr, 42_a));
            case wgsl::BuiltinFn::kAtomicCompareExchangeWeak:
                return CallStmt(Call(fn, ptr, 10_a, 42_a));
            default:
                TINT_UNIMPLEMENTED() << fn;
        }
    }

    std::string Run() {
        if (r()->Resolve()) {
            return std::string(kPass);
        }
        return r()->error();
    }
};

TEST_P(AtomicPointers, CallDirect) {
    // var<ADDRESS_SPACE> v1 : atomic<i32>;
    // var<ADDRESS_SPACE> v2 : atomic<i32>;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <builtin-a>(p1);
    //   <builtin-b>(p2);
    // }
    auto [builtin_a, builtin_b, space, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("callee", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_a, "p1"),
             CallBuiltin(builtin_b, "p2"),
         });

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(AtomicPointers, CallThroughChain) {
    // var<ADDRESS_SPACE> v1 : atomic<i32>;
    // var<ADDRESS_SPACE> v2 : atomic<i32>;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f2(p1 : PTR, p2 : PTR) {
    //    f1(p1, p2);
    // }
    //
    // fn f1(p1 : PTR, p2 : PTR) {
    //    callee(p1, p2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <builtin-a>(p1);
    //   <builtin-b>(p2);
    // }
    auto [builtin_a, builtin_b, space, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f2", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1", "p2")),
         });

    Func("f1", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("callee", "p1", "p2")),
         });

    Func("callee", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_a, "p1"),
             CallBuiltin(builtin_b, "p2"),
         });

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(AtomicPointers, ReadWriteAcrossDifferentFunctions) {
    // var<ADDRESS_SPACE> v1 : atomic<i32>;
    // var<ADDRESS_SPACE> v2 : atomic<i32>;
    //
    // fn caller() {
    //   f(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f(p1 : PTR, p2 : PTR) {
    //    f1(p1);
    //    f2(p2);
    // }
    //
    // fn f1(p : PTR) {
    //   <builtin-a>(p);
    // }
    //
    // fn f2(p : PTR) {
    //   <builtin-b>(p);
    // }
    auto [builtin_a, builtin_b, space, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("f",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1")),
             CallStmt(Call("f2", "p2")),
         });

    Func("f1", Vector{Param("p", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_a, "p"),
         });

    Func("f2", Vector{Param("p", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_b, "p"),
         });

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

std::array kAtomicFns{
    wgsl::BuiltinFn::kAtomicLoad,
    wgsl::BuiltinFn::kAtomicStore,
    wgsl::BuiltinFn::kAtomicAdd,
    wgsl::BuiltinFn::kAtomicSub,
    wgsl::BuiltinFn::kAtomicMax,
    wgsl::BuiltinFn::kAtomicMin,
    wgsl::BuiltinFn::kAtomicAnd,
    wgsl::BuiltinFn::kAtomicOr,
    wgsl::BuiltinFn::kAtomicXor,
    wgsl::BuiltinFn::kAtomicExchange,
    wgsl::BuiltinFn::kAtomicCompareExchangeWeak,
};

INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
                         AtomicPointers,
                         ::testing::Combine(::testing::ValuesIn(kAtomicFns),
                                            ::testing::ValuesIn(kAtomicFns),
                                            ::testing::Values(core::AddressSpace::kWorkgroup,
                                                              core::AddressSpace::kStorage),
                                            ::testing::Values(true, false)));

////////////////////////////////////////////////////////////////////////////////
// Atomics Min/Max
////////////////////////////////////////////////////////////////////////////////
class AtomicMinMax : public ResolverTestWithParam<
                         std::tuple<wgsl::BuiltinFn, wgsl::BuiltinFn, bool /* aliased */>> {
  protected:
    static constexpr std::string_view kPass = "<PASS>";

    ast::Type Ptr() { return ty.ptr<storage, atomic<vec2u>, read_write>(); }

    void SetUp() override {
        Enable(wgsl::Extension::kAtomicVec2UMinMax);

        GlobalVar("v1", core::AddressSpace::kStorage, read_write, ty.Of<atomic<vec2u>>(),
                  Binding(0_a), Group(0_a));
        GlobalVar("v2", core::AddressSpace::kStorage, read_write, ty.Of<atomic<vec2u>>(),
                  Binding(1_a), Group(0_a));
    }

    const ast::Statement* CallBuiltin(wgsl::BuiltinFn fn, std::string_view ptr) {
        return CallStmt(Call(fn, ptr, Call<vec2<u32>>(1_u, 1_u)));
    }

    std::string Run() {
        if (r()->Resolve()) {
            return std::string(kPass);
        }
        return r()->error();
    }
};

TEST_P(AtomicMinMax, CallDirect) {
    // var<storage> v1 : atomic<i32>;
    // var<storage> v2 : atomic<i32>;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <builtin-a>(p1);
    //   <builtin-b>(p2);
    // }
    auto [builtin_a, builtin_b, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("callee", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_a, "p1"),
             CallBuiltin(builtin_b, "p2"),
         });

    EXPECT_EQ(Run(), !aliased ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(AtomicMinMax, CallThroughChain) {
    // var<storage> v1 : atomic<i32>;
    // var<storage> v2 : atomic<i32>;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f2(p1 : PTR, p2 : PTR) {
    //    f1(p1, p2);
    // }
    //
    // fn f1(p1 : PTR, p2 : PTR) {
    //    callee(p1, p2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <builtin-a>(p1);
    //   <builtin-b>(p2);
    // }
    auto [builtin_a, builtin_b, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f2", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1", "p2")),
         });

    Func("f1", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("callee", "p1", "p2")),
         });

    Func("callee", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_a, "p1"),
             CallBuiltin(builtin_b, "p2"),
         });

    EXPECT_EQ(Run(), !aliased ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(AtomicMinMax, ReadWriteAcrossDifferentFunctions) {
    // var<storage> v1 : atomic<i32>;
    // var<storage> v2 : atomic<i32>;
    //
    // fn caller() {
    //   f(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f(p1 : PTR, p2 : PTR) {
    //    f1(p1);
    //    f2(p2);
    // }
    //
    // fn f1(p : PTR) {
    //   <builtin-a>(p);
    // }
    //
    // fn f2(p : PTR) {
    //   <builtin-b>(p);
    // }
    auto [builtin_a, builtin_b, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("f",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1")),
             CallStmt(Call("f2", "p2")),
         });

    Func("f1", Vector{Param("p", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_a, "p"),
         });

    Func("f2", Vector{Param("p", Ptr())}, ty.void_(),
         Vector{
             CallBuiltin(builtin_b, "p"),
         });

    EXPECT_EQ(Run(), !aliased ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

std::array kAtomicMinMaxFns{
    wgsl::BuiltinFn::kAtomicStoreMin,
    wgsl::BuiltinFn::kAtomicStoreMax,
};

INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
                         AtomicMinMax,
                         ::testing::Combine(::testing::ValuesIn(kAtomicMinMaxFns),
                                            ::testing::ValuesIn(kAtomicMinMaxFns),
                                            ::testing::Values(true, false)));

////////////////////////////////////////////////////////////////////////////////
// WorkgroupUniformLoad
////////////////////////////////////////////////////////////////////////////////
enum class WorkgroupUniformLoadAction {
    kRead,
    kWrite,
    kWorkgroupUniformLoad,
};

std::array kWorkgroupUniformLoadActions{
    WorkgroupUniformLoadAction::kRead,
    WorkgroupUniformLoadAction::kWrite,
    WorkgroupUniformLoadAction::kWorkgroupUniformLoad,
};

class WorkgroupUniformLoad
    : public ResolverTestWithParam<
          std::tuple<WorkgroupUniformLoadAction, WorkgroupUniformLoadAction, bool>> {
  protected:
    static constexpr std::string_view kPass = "<PASS>";

    void SetUp() override {
        GlobalVar("v1", workgroup, ty.i32());
        GlobalVar("v2", workgroup, ty.i32());
    }

    const ast::Statement* Do(WorkgroupUniformLoadAction action, std::string_view ptr) {
        switch (action) {
            case WorkgroupUniformLoadAction::kRead:
                return Assign(Phony(), Deref(ptr));
            case WorkgroupUniformLoadAction::kWrite:
                return Assign(Deref(ptr), 42_a);
            case WorkgroupUniformLoadAction::kWorkgroupUniformLoad:
                return Assign(Phony(), Call(wgsl::BuiltinFn::kWorkgroupUniformLoad, ptr));
        }
        return nullptr;
    }

    bool IsWrite(WorkgroupUniformLoadAction action) const {
        return action == WorkgroupUniformLoadAction::kWrite;
    }

    bool ShouldPass() const {
        auto [action_a, action_b, aliased] = GetParam();
        bool fail = aliased && (IsWrite(action_a) || IsWrite(action_b));
        return !fail;
    }

    std::string Run() {
        if (r()->Resolve()) {
            return std::string(kPass);
        }
        return r()->error();
    }
};

TEST_P(WorkgroupUniformLoad, CallDirect) {
    // var<workgroup> v1 : i32;
    // var<workgroup> v2 : i32;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <action-a>(p1);
    //   <action-b>(p2);
    // }
    auto [action_a, action_b, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("callee",
         Vector{Param("p1", ty.ptr<workgroup, i32>()), Param("p2", ty.ptr<workgroup, i32>())},
         ty.void_(),
         Vector{
             Do(action_a, "p1"),
             Do(action_b, "p2"),
         });

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(WorkgroupUniformLoad, CallThroughChain) {
    // var<workgroup> v1 : i32;
    // var<workgroup> v2 : i32;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f2(p1 : PTR, p2 : PTR) {
    //    f1(p1, p2);
    // }
    //
    // fn f1(p1 : PTR, p2 : PTR) {
    //    callee(p1, p2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <action-a>(p1);
    //   <action-b>(p2);
    // }
    auto [action_a, action_b, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f2", Vector{Param("p1", ty.ptr<workgroup, i32>()), Param("p2", ty.ptr<workgroup, i32>())},
         ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1", "p2")),
         });

    Func("f1", Vector{Param("p1", ty.ptr<workgroup, i32>()), Param("p2", ty.ptr<workgroup, i32>())},
         ty.void_(),
         Vector{
             CallStmt(Call("callee", "p1", "p2")),
         });

    Func("callee",
         Vector{Param("p1", ty.ptr<workgroup, i32>()), Param("p2", ty.ptr<workgroup, i32>())},
         ty.void_(),
         Vector{
             Do(action_a, "p1"),
             Do(action_b, "p2"),
         });

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(WorkgroupUniformLoad, ReadWriteAcrossDifferentFunctions) {
    // var<workgroup> v1 : i32;
    // var<workgroup> v2 : i32;
    //
    // fn caller() {
    //   f(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f(p1 : PTR, p2 : PTR) {
    //    f1(p1);
    //    f2(p2);
    // }
    //
    // fn f1(p : PTR) {
    //   <action-a>(p);
    // }
    //
    // fn f2(p : PTR) {
    //   <action-b>(p);
    // }
    auto [action_a, action_b, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("f",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f", Vector{Param("p1", ty.ptr<workgroup, i32>()), Param("p2", ty.ptr<workgroup, i32>())},
         ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1")),
             CallStmt(Call("f2", "p2")),
         });

    Func("f1", Vector{Param("p", ty.ptr<workgroup, i32>())}, ty.void_(), Vector{Do(action_a, "p")});

    Func("f2", Vector{Param("p", ty.ptr<workgroup, i32>())}, ty.void_(), Vector{Do(action_b, "p")});

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
                         WorkgroupUniformLoad,
                         ::testing::Combine(::testing::ValuesIn(kWorkgroupUniformLoadActions),
                                            ::testing::ValuesIn(kWorkgroupUniformLoadActions),
                                            ::testing::Values(true, false)));

////////////////////////////////////////////////////////////////////////////////
// Subgroup matrix builtins
////////////////////////////////////////////////////////////////////////////////
enum class SubgroupMatrixAction {
    kLoad,
    kStore,
};

constexpr std::array kSubgroupMatrixActions{
    SubgroupMatrixAction::kLoad,
    SubgroupMatrixAction::kStore,
};

class SubgroupMatrixTest : public ResolverTestWithParam<std::tuple<SubgroupMatrixAction,
                                                                   SubgroupMatrixAction,
                                                                   core::AddressSpace,
                                                                   bool /* aliased */>> {
  protected:
    static constexpr std::string_view kPass = "<PASS>";

    ast::Type Ptr() {
        auto address_space = std::get<2>(GetParam());
        if (address_space == storage) {
            return ty.ptr<storage, array<f32, 1024>, read_write>();
        } else {
            return ty.ptr<array<f32, 1024>>(address_space);
        }
    }

    void SetUp() override {
        Enable(wgsl::Extension::kChromiumExperimentalSubgroupMatrix);
        auto address_space = std::get<2>(GetParam());
        if (address_space == storage) {
            GlobalVar("v1", address_space, read_write, ty.array<f32, 1024>(),  //
                      Binding(0_a), Group(0_a));
            GlobalVar("v2", address_space, read_write, ty.array<f32, 1024>(),  //
                      Binding(1_a), Group(0_a));
        } else {
            GlobalVar("v1", address_space, ty.array<f32, 1024>());
            GlobalVar("v2", address_space, ty.array<f32, 1024>());
        }
    }

    const ast::Statement* Do(SubgroupMatrixAction action, std::string_view ptr) {
        switch (action) {
            case SubgroupMatrixAction::kLoad:
                return Assign(Phony(),
                              Call(Ident(wgsl::BuiltinFn::kSubgroupMatrixLoad,
                                         ty.subgroup_matrix(core::SubgroupMatrixKind::kResult,
                                                            ty.f32(), 8, 8)),
                                   ptr, 0_u, false, 8_u));
            case SubgroupMatrixAction::kStore:
                return CallStmt(Call(
                    wgsl::BuiltinFn::kSubgroupMatrixStore, ptr, 0_u,
                    Call(ty.subgroup_matrix(core::SubgroupMatrixKind::kResult, ty.f32(), 8, 8)),
                    false, 8_u));
        }
        return nullptr;
    }

    bool IsWrite(SubgroupMatrixAction action) const {
        return action == SubgroupMatrixAction::kStore;
    }

    bool ShouldPass() const {
        auto [action_a, action_b, addrspace, aliased] = GetParam();
        bool fail = aliased && (IsWrite(action_a) || IsWrite(action_b));
        return !fail;
    }

    std::string Run() {
        if (r()->Resolve()) {
            return std::string(kPass);
        }
        return r()->error();
    }
};

TEST_P(SubgroupMatrixTest, CallDirect) {
    // var<ADDRSPACE> v1 : array<f32, 1024>;
    // var<ADDRSPACE> v2 : array<f32, 1024>;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <action-a>(p1);
    //   <action-b>(p2);
    // }
    auto [action_a, action_b, addrspace, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("callee", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             Do(action_a, "p1"),
             Do(action_b, "p2"),
         });

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(SubgroupMatrixTest, CallThroughChain) {
    // var<ADDRSPACE> v1 : array<f32, 1024>;
    // var<ADDRSPACE> v2 : array<f32, 1024>;
    //
    // fn caller() {
    //    callee(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f2(p1 : PTR, p2 : PTR) {
    //    f1(p1, p2);
    // }
    //
    // fn f1(p1 : PTR, p2 : PTR) {
    //    callee(p1, p2);
    // }
    //
    // fn callee(p1 : PTR, p2 : PTR) {
    //   <action-a>(p1);
    //   <action-b>(p2);
    // }
    auto [action_a, action_b, addrspace, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("callee",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f2", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1", "p2")),
         });

    Func("f1", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("callee", "p1", "p2")),
         });

    Func("callee", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             Do(action_a, "p1"),
             Do(action_b, "p2"),
         });

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

TEST_P(SubgroupMatrixTest, ReadWriteAcrossDifferentFunctions) {
    // var<ADDRSPACE> v1 : array<f32, 1024>;
    // var<ADDRSPACE> v2 : array<f32, 1024>;
    //
    // fn caller() {
    //   f(&v1, aliased ? &v1 : &v2);
    // }
    //
    // fn f(p1 : PTR, p2 : PTR) {
    //    f1(p1);
    //    f2(p2);
    // }
    //
    // fn f1(p : PTR) {
    //   <action-a>(p);
    // }
    //
    // fn f2(p : PTR) {
    //   <action-b>(p);
    // }
    auto [action_a, action_b, addrspace, aliased] = GetParam();

    Func("caller", tint::Empty, ty.void_(),
         Vector{
             CallStmt(Call("f",  //
                           AddressOf(Source{{12, 34}}, "v1"),
                           AddressOf(Source{{56, 78}}, aliased ? "v1" : "v2"))),
         });

    Func("f", Vector{Param("p1", Ptr()), Param("p2", Ptr())}, ty.void_(),
         Vector{
             CallStmt(Call("f1", "p1")),
             CallStmt(Call("f2", "p2")),
         });

    Func("f1", Vector{Param("p", Ptr())}, ty.void_(), Vector{Do(action_a, "p")});

    Func("f2", Vector{Param("p", Ptr())}, ty.void_(), Vector{Do(action_b, "p")});

    EXPECT_EQ(Run(), ShouldPass() ? kPass : R"(56:78 error: invalid aliased pointer argument
12:34 note: aliases with another argument passed here)");
}

INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest,
                         SubgroupMatrixTest,
                         ::testing::Combine(::testing::ValuesIn(kSubgroupMatrixActions),
                                            ::testing::ValuesIn(kSubgroupMatrixActions),
                                            ::testing::Values(core::AddressSpace::kWorkgroup,
                                                              core::AddressSpace::kStorage),
                                            ::testing::Values(true, false)));

}  // namespace
}  // namespace tint::resolver
