| // Copyright 2023 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 "src/tint/lang/core/ir/transform/direct_variable_access.h" |
| |
| #include <utility> |
| |
| #include "src/tint/lang/core/ir/transform/helper_test.h" |
| #include "src/tint/lang/core/type/array.h" |
| #include "src/tint/lang/core/type/matrix.h" |
| #include "src/tint/lang/core/type/pointer.h" |
| #include "src/tint/lang/core/type/struct.h" |
| |
| namespace tint::core::ir::transform { |
| namespace { |
| |
| using namespace tint::core::fluent_types; // NOLINT |
| using namespace tint::core::number_suffixes; // NOLINT |
| |
| namespace { |
| |
| static constexpr DirectVariableAccessOptions kTransformPrivate = { |
| /* transform_private */ true, |
| /* transform_function */ false, |
| }; |
| |
| static constexpr DirectVariableAccessOptions kTransformFunction = { |
| /* transform_private */ false, |
| /* transform_function */ true, |
| }; |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // remove uncalled |
| //////////////////////////////////////////////////////////////////////////////// |
| namespace remove_uncalled { |
| |
| using IR_DirectVariableAccessTest_RemoveUncalled = TransformTest; |
| |
| TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrUniform) { |
| b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); }); |
| |
| auto* u = b.Function("u", ty.i32()); |
| auto* p = b.FunctionParam("p", ty.ptr<uniform, i32>()); |
| u->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(u->Block(), [&] { b.Return(u, b.Load(p)); }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| %u = func(%pre:i32, %p:ptr<uniform, i32, read>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrStorage) { |
| b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); }); |
| |
| auto* s = b.Function("s", ty.i32()); |
| auto* p = b.FunctionParam("p", ty.ptr<storage, i32, read>()); |
| s->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(s->Block(), [&] { b.Return(s, b.Load(p)); }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| %s = func(%pre:i32, %p:ptr<storage, i32, read>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrWorkgroup) { |
| b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); }); |
| |
| auto* w = b.Function("w", ty.i32()); |
| auto* p = b.FunctionParam("p", ty.ptr<workgroup, i32>()); |
| w->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(w->Block(), [&] { b.Return(w, b.Load(p)); }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| %w = func(%pre:i32, %p:ptr<workgroup, i32, read_write>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrPrivate_Disabled) { |
| b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); }); |
| |
| auto* f = b.Function("f", ty.i32()); |
| auto* p = b.FunctionParam("p", ty.ptr<private_, i32>()); |
| f->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| %f = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = src; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrPrivate_Enabled) { |
| b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); }); |
| |
| auto* f = b.Function("f", ty.i32()); |
| auto* p = b.FunctionParam("p", ty.ptr<private_, i32>()); |
| f->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| %f = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| )"; |
| Run(DirectVariableAccess, kTransformPrivate); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrFunction_Disabled) { |
| b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); }); |
| |
| auto* f = b.Function("f", ty.i32()); |
| auto* p = b.FunctionParam("p", ty.ptr<function, i32>()); |
| f->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| %f = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = src; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrFunction_Enabled) { |
| b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); }); |
| |
| auto* f = b.Function("f", ty.i32()); |
| auto* p = b.FunctionParam("p", ty.ptr<function, i32>()); |
| f->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| %f = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %keep_me:ptr<private, i32, read_write> = var, 42i |
| } |
| |
| )"; |
| |
| Run(DirectVariableAccess, kTransformFunction); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| } // namespace remove_uncalled |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // pointer chains |
| //////////////////////////////////////////////////////////////////////////////// |
| namespace pointer_chains_tests { |
| |
| using IR_DirectVariableAccessTest_PtrChains = TransformTest; |
| |
| TEST_F(IR_DirectVariableAccessTest_PtrChains, ConstantIndices) { |
| Var* U = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| U = b.Var<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>("U"); |
| U->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.vec4<i32>()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, vec4<i32>>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* p0 = b.Let("p0", U); |
| auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>>(), p0, 1_i); |
| b.ir.SetName(p1, "p1"); |
| auto* p2 = b.Access(ty.ptr<uniform, array<vec4<i32>, 8>>(), p1, 2_i); |
| b.ir.SetName(p2, "p2"); |
| auto* p3 = b.Access(ty.ptr<uniform, vec4<i32>>(), p2, 3_i); |
| b.ir.SetName(p3, "p3"); |
| b.Call(ty.vec4<i32>(), fn_a, 10_i, p3, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* fn_c = b.Function("c", ty.void_()); |
| auto* fn_c_p = b.FunctionParam("p", ty.ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>()); |
| fn_c->SetParams({fn_c_p}); |
| b.Append(fn_c->Block(), [&] { |
| auto* p0 = b.Let("p0", fn_c_p); |
| auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>>(), p0, 1_i); |
| b.ir.SetName(p1, "p1"); |
| auto* p2 = b.Access(ty.ptr<uniform, array<vec4<i32>, 8>>(), p1, 2_i); |
| b.ir.SetName(p2, "p2"); |
| auto* p3 = b.Access(ty.ptr<uniform, vec4<i32>>(), p2, 3_i); |
| b.ir.SetName(p3, "p3"); |
| b.Call(ty.vec4<i32>(), fn_a, 10_i, p3, 20_i); |
| b.Return(fn_c); |
| }); |
| |
| auto* fn_d = b.Function("d", ty.void_()); |
| b.Append(fn_d->Block(), [&] { |
| b.Call(ty.void_(), fn_c, U); |
| b.Return(fn_d); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p:ptr<uniform, vec4<i32>, read>, %post:i32):vec4<i32> { |
| $B2: { |
| %6:vec4<i32> = load %p |
| ret %6 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %p0:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %U |
| %p1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0, 1i |
| %p2:ptr<uniform, array<vec4<i32>, 8>, read> = access %p1, 2i |
| %p3:ptr<uniform, vec4<i32>, read> = access %p2, 3i |
| %12:vec4<i32> = call %a, 10i, %p3, 20i |
| ret |
| } |
| } |
| %c = func(%p_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>):void { # %p_1: 'p' |
| $B4: { |
| %p0_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %p_1 # %p0_1: 'p0' |
| %p1_1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0_1, 1i # %p1_1: 'p1' |
| %p2_1:ptr<uniform, array<vec4<i32>, 8>, read> = access %p1_1, 2i # %p2_1: 'p2' |
| %p3_1:ptr<uniform, vec4<i32>, read> = access %p2_1, 3i # %p3_1: 'p3' |
| %19:vec4<i32> = call %a, 10i, %p3_1, 20i |
| ret |
| } |
| } |
| %d = func():void { |
| $B5: { |
| %21:void = call %c, %U |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = |
| R"( |
| $B1: { # root |
| %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p_indices:array<u32, 3>, %post:i32):vec4<i32> { |
| $B2: { |
| %6:u32 = access %p_indices, 0u |
| %7:u32 = access %p_indices, 1u |
| %8:u32 = access %p_indices, 2u |
| %9:ptr<uniform, vec4<i32>, read> = access %U, %6, %7, %8 |
| %10:vec4<i32> = load %9 |
| ret %10 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %12:u32 = convert 3i |
| %13:u32 = convert 2i |
| %14:u32 = convert 1i |
| %15:array<u32, 3> = construct %14, %13, %12 |
| %16:vec4<i32> = call %a, 10i, %15, 20i |
| ret |
| } |
| } |
| %c = func():void { |
| $B4: { |
| %18:u32 = convert 3i |
| %19:u32 = convert 2i |
| %20:u32 = convert 1i |
| %21:array<u32, 3> = construct %20, %19, %18 |
| %22:vec4<i32> = call %a, 10i, %21, 20i |
| ret |
| } |
| } |
| %d = func():void { |
| $B5: { |
| %24:void = call %c |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_PtrChains, DynamicIndices) { |
| Var* U = nullptr; |
| Var* i = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| U = b.Var<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>("U"); |
| U->SetBindingPoint(0, 0); |
| i = b.Var<private_, i32>("i"); |
| }); |
| |
| auto* fn_first = b.Function("first", ty.i32()); |
| auto* fn_second = b.Function("second", ty.i32()); |
| auto* fn_third = b.Function("third", ty.i32()); |
| for (auto fn : {fn_first, fn_second, fn_third}) { |
| b.Append(fn->Block(), [&] { |
| b.Store(i, b.Add(ty.i32(), b.Load(i), 1_i)); |
| b.Return(fn, b.Load(i)); |
| }); |
| } |
| |
| auto* fn_a = b.Function("a", ty.vec4<i32>()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, vec4<i32>>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* p0 = b.Let("p0", U); |
| auto* first = b.Call(fn_first); |
| auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>>(), p0, first); |
| b.ir.SetName(p1, "p1"); |
| auto* second = b.Call(fn_second); |
| auto* third = b.Call(fn_third); |
| auto* p2 = b.Access(ty.ptr<uniform, vec4<i32>>(), p1, second, third); |
| b.ir.SetName(p2, "p2"); |
| b.Call(ty.vec4<i32>(), fn_a, 10_i, p2, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* fn_c = b.Function("c", ty.void_()); |
| auto* fn_c_p = b.FunctionParam("p", ty.ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>()); |
| fn_c->SetParams({fn_c_p}); |
| b.Append(fn_c->Block(), [&] { |
| auto* p0 = b.Let("p0", fn_c_p); |
| auto* first = b.Call(fn_first); |
| auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>>(), p0, first); |
| b.ir.SetName(p1, "p1"); |
| auto* second = b.Call(fn_second); |
| auto* third = b.Call(fn_third); |
| auto* p2 = b.Access(ty.ptr<uniform, vec4<i32>>(), p1, second, third); |
| b.ir.SetName(p2, "p2"); |
| b.Call(ty.vec4<i32>(), fn_a, 10_i, p2, 20_i); |
| b.Return(fn_c); |
| }); |
| |
| auto* fn_d = b.Function("d", ty.void_()); |
| b.Append(fn_d->Block(), [&] { |
| b.Call(ty.void_(), fn_c, U); |
| b.Return(fn_d); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0) |
| %i:ptr<private, i32, read_write> = var |
| } |
| |
| %first = func():i32 { |
| $B2: { |
| %4:i32 = load %i |
| %5:i32 = add %4, 1i |
| store %i, %5 |
| %6:i32 = load %i |
| ret %6 |
| } |
| } |
| %second = func():i32 { |
| $B3: { |
| %8:i32 = load %i |
| %9:i32 = add %8, 1i |
| store %i, %9 |
| %10:i32 = load %i |
| ret %10 |
| } |
| } |
| %third = func():i32 { |
| $B4: { |
| %12:i32 = load %i |
| %13:i32 = add %12, 1i |
| store %i, %13 |
| %14:i32 = load %i |
| ret %14 |
| } |
| } |
| %a = func(%pre:i32, %p:ptr<uniform, vec4<i32>, read>, %post:i32):vec4<i32> { |
| $B5: { |
| %19:vec4<i32> = load %p |
| ret %19 |
| } |
| } |
| %b = func():void { |
| $B6: { |
| %p0:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %U |
| %22:i32 = call %first |
| %p1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0, %22 |
| %24:i32 = call %second |
| %25:i32 = call %third |
| %p2:ptr<uniform, vec4<i32>, read> = access %p1, %24, %25 |
| %27:vec4<i32> = call %a, 10i, %p2, 20i |
| ret |
| } |
| } |
| %c = func(%p_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>):void { # %p_1: 'p' |
| $B7: { |
| %p0_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %p_1 # %p0_1: 'p0' |
| %31:i32 = call %first |
| %p1_1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0_1, %31 # %p1_1: 'p1' |
| %33:i32 = call %second |
| %34:i32 = call %third |
| %p2_1:ptr<uniform, vec4<i32>, read> = access %p1_1, %33, %34 # %p2_1: 'p2' |
| %36:vec4<i32> = call %a, 10i, %p2_1, 20i |
| ret |
| } |
| } |
| %d = func():void { |
| $B8: { |
| %38:void = call %c, %U |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0) |
| %i:ptr<private, i32, read_write> = var |
| } |
| |
| %first = func():i32 { |
| $B2: { |
| %4:i32 = load %i |
| %5:i32 = add %4, 1i |
| store %i, %5 |
| %6:i32 = load %i |
| ret %6 |
| } |
| } |
| %second = func():i32 { |
| $B3: { |
| %8:i32 = load %i |
| %9:i32 = add %8, 1i |
| store %i, %9 |
| %10:i32 = load %i |
| ret %10 |
| } |
| } |
| %third = func():i32 { |
| $B4: { |
| %12:i32 = load %i |
| %13:i32 = add %12, 1i |
| store %i, %13 |
| %14:i32 = load %i |
| ret %14 |
| } |
| } |
| %a = func(%pre:i32, %p_indices:array<u32, 3>, %post:i32):vec4<i32> { |
| $B5: { |
| %19:u32 = access %p_indices, 0u |
| %20:u32 = access %p_indices, 1u |
| %21:u32 = access %p_indices, 2u |
| %22:ptr<uniform, vec4<i32>, read> = access %U, %19, %20, %21 |
| %23:vec4<i32> = load %22 |
| ret %23 |
| } |
| } |
| %b = func():void { |
| $B6: { |
| %25:i32 = call %first |
| %26:i32 = call %second |
| %27:i32 = call %third |
| %28:u32 = convert %26 |
| %29:u32 = convert %27 |
| %30:u32 = convert %25 |
| %31:array<u32, 3> = construct %30, %28, %29 |
| %32:vec4<i32> = call %a, 10i, %31, 20i |
| ret |
| } |
| } |
| %c = func():void { |
| $B7: { |
| %34:i32 = call %first |
| %35:i32 = call %second |
| %36:i32 = call %third |
| %37:u32 = convert %35 |
| %38:u32 = convert %36 |
| %39:u32 = convert %34 |
| %40:array<u32, 3> = construct %39, %37, %38 |
| %41:vec4<i32> = call %a, 10i, %40, 20i |
| ret |
| } |
| } |
| %d = func():void { |
| $B8: { |
| %43:void = call %c |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| } // namespace pointer_chains_tests |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 'uniform' address space |
| //////////////////////////////////////////////////////////////////////////////// |
| namespace uniform_as_tests { |
| |
| using IR_DirectVariableAccessTest_UniformAS = TransformTest; |
| |
| TEST_F(IR_DirectVariableAccessTest_UniformAS, Param_ptr_i32_read) { |
| Var* U = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| U = b.Var<uniform, i32>("U"); |
| U->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.i32()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, i32>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| b.Call(fn_a, 10_i, U, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %U:ptr<uniform, i32, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p:ptr<uniform, i32, read>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %8:i32 = call %a, 10i, %U, 20i |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %U:ptr<uniform, i32, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %post:i32):i32 { |
| $B2: { |
| %5:ptr<uniform, i32, read> = access %U |
| %6:i32 = load %5 |
| ret %6 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %8:i32 = call %a, 10i, 20i |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_UniformAS, Param_ptr_vec4i32_Via_array_DynamicRead) { |
| Var* U = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| U = b.Var<uniform, array<vec4<i32>, 8>>("U"); |
| U->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.vec4<i32>()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, vec4<i32>>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* I = b.Let("I", 3_i); |
| auto* access = b.Access(ty.ptr<uniform, vec4<i32>>(), U, I); |
| b.Call(fn_a, 10_i, access, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %U:ptr<uniform, array<vec4<i32>, 8>, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p:ptr<uniform, vec4<i32>, read>, %post:i32):vec4<i32> { |
| $B2: { |
| %6:vec4<i32> = load %p |
| ret %6 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %I:i32 = let 3i |
| %9:ptr<uniform, vec4<i32>, read> = access %U, %I |
| %10:vec4<i32> = call %a, 10i, %9, 20i |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %U:ptr<uniform, array<vec4<i32>, 8>, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):vec4<i32> { |
| $B2: { |
| %6:u32 = access %p_indices, 0u |
| %7:ptr<uniform, vec4<i32>, read> = access %U, %6 |
| %8:vec4<i32> = load %7 |
| ret %8 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %I:i32 = let 3i |
| %11:u32 = convert %I |
| %12:array<u32, 1> = construct %11 |
| %13:vec4<i32> = call %a, 10i, %12, 20i |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_UniformAS, CallChaining) { |
| auto* Inner = |
| ty.Struct(mod.symbols.New("Inner"), { |
| {mod.symbols.Register("mat"), ty.mat3x4<f32>()}, |
| }); |
| auto* Outer = |
| ty.Struct(mod.symbols.New("Outer"), { |
| {mod.symbols.Register("arr"), ty.array(Inner, 4)}, |
| {mod.symbols.Register("mat"), ty.mat3x4<f32>()}, |
| }); |
| Var* U = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| U = b.Var("U", ty.ptr<uniform>(Outer)); |
| U->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_0 = b.Function("f0", ty.f32()); |
| auto* fn_0_p = b.FunctionParam("p", ty.ptr<uniform, vec4<f32>>()); |
| fn_0->SetParams({fn_0_p}); |
| b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); }); |
| |
| auto* fn_1 = b.Function("f1", ty.f32()); |
| auto* fn_1_p = b.FunctionParam("p", ty.ptr<uniform, mat3x4<f32>>()); |
| fn_1->SetParams({fn_1_p}); |
| b.Append(fn_1->Block(), [&] { |
| auto* res = b.Var<function, f32>("res"); |
| { |
| // res += f0(&(*p)[1]); |
| auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<uniform, vec4<f32>>(), fn_1_p, 1_i)); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // let p_vec = &(*p)[1]; |
| // res += f0(p_vec); |
| auto* p_vec = b.Access(ty.ptr<uniform, vec4<f32>>(), fn_1_p, 1_i); |
| b.ir.SetName(p_vec, "p_vec"); |
| auto* call_0 = b.Call(fn_0, p_vec); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // res += f0(&U.arr[2].mat[1]); |
| auto* access = b.Access(ty.ptr<uniform, vec4<f32>>(), U, 0_u, 2_i, 0_u, 1_i); |
| auto* call_0 = b.Call(fn_0, access); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // let p_vec = &U.arr[2].mat[1]; |
| // res += f0(p_vec); |
| auto* p_vec = b.Access(ty.ptr<uniform, vec4<f32>>(), U, 0_u, 2_i, 0_u, 1_i); |
| b.ir.SetName(p_vec, "p_vec"); |
| auto* call_0 = b.Call(fn_0, p_vec); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| |
| b.Return(fn_1, b.Load(res)); |
| }); |
| |
| auto* fn_2 = b.Function("f2", ty.f32()); |
| auto* fn_2_p = b.FunctionParam("p", ty.ptr<uniform>(Inner)); |
| fn_2->SetParams({fn_2_p}); |
| b.Append(fn_2->Block(), [&] { |
| auto* p_mat = b.Access(ty.ptr<uniform, mat3x4<f32>>(), fn_2_p, 0_u); |
| b.ir.SetName(p_mat, "p_mat"); |
| b.Return(fn_2, b.Call(fn_1, p_mat)); |
| }); |
| |
| auto* fn_3 = b.Function("f3", ty.f32()); |
| auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<uniform>(ty.array(Inner, 4))); |
| auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<uniform, mat3x4<f32>>()); |
| fn_3->SetParams({fn_3_p0, fn_3_p1}); |
| b.Append(fn_3->Block(), [&] { |
| auto* p0_inner = b.Access(ty.ptr<uniform>(Inner), fn_3_p0, 3_i); |
| b.ir.SetName(p0_inner, "p0_inner"); |
| auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner); |
| auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1); |
| b.Return(fn_3, b.Add(ty.f32(), call_0, call_1)); |
| }); |
| |
| auto* fn_4 = b.Function("f4", ty.f32()); |
| auto* fn_4_p = b.FunctionParam("p", ty.ptr<uniform>(Outer)); |
| fn_4->SetParams({fn_4_p}); |
| b.Append(fn_4->Block(), [&] { |
| auto* access_0 = b.Access(ty.ptr<uniform>(ty.array(Inner, 4)), fn_4_p, 0_u); |
| auto* access_1 = b.Access(ty.ptr<uniform, mat3x4<f32>>(), U, 1_u); |
| b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1)); |
| }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| b.Call(ty.f32(), fn_4, U); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| Inner = struct @align(16) { |
| mat:mat3x4<f32> @offset(0) |
| } |
| |
| Outer = struct @align(16) { |
| arr:array<Inner, 4> @offset(0) |
| mat:mat3x4<f32> @offset(192) |
| } |
| |
| $B1: { # root |
| %U:ptr<uniform, Outer, read> = var @binding_point(0, 0) |
| } |
| |
| %f0 = func(%p:ptr<uniform, vec4<f32>, read>):f32 { |
| $B2: { |
| %4:f32 = load_vector_element %p, 0u |
| ret %4 |
| } |
| } |
| %f1 = func(%p_1:ptr<uniform, mat3x4<f32>, read>):f32 { # %p_1: 'p' |
| $B3: { |
| %res:ptr<function, f32, read_write> = var |
| %8:ptr<uniform, vec4<f32>, read> = access %p_1, 1i |
| %9:f32 = call %f0, %8 |
| %10:f32 = load %res |
| %11:f32 = add %10, %9 |
| store %res, %11 |
| %p_vec:ptr<uniform, vec4<f32>, read> = access %p_1, 1i |
| %13:f32 = call %f0, %p_vec |
| %14:f32 = load %res |
| %15:f32 = add %14, %13 |
| store %res, %15 |
| %16:ptr<uniform, vec4<f32>, read> = access %U, 0u, 2i, 0u, 1i |
| %17:f32 = call %f0, %16 |
| %18:f32 = load %res |
| %19:f32 = add %18, %17 |
| store %res, %19 |
| %p_vec_1:ptr<uniform, vec4<f32>, read> = access %U, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec' |
| %21:f32 = call %f0, %p_vec_1 |
| %22:f32 = load %res |
| %23:f32 = add %22, %21 |
| store %res, %23 |
| %24:f32 = load %res |
| ret %24 |
| } |
| } |
| %f2 = func(%p_2:ptr<uniform, Inner, read>):f32 { # %p_2: 'p' |
| $B4: { |
| %p_mat:ptr<uniform, mat3x4<f32>, read> = access %p_2, 0u |
| %28:f32 = call %f1, %p_mat |
| ret %28 |
| } |
| } |
| %f3 = func(%p0:ptr<uniform, array<Inner, 4>, read>, %p1:ptr<uniform, mat3x4<f32>, read>):f32 { |
| $B5: { |
| %p0_inner:ptr<uniform, Inner, read> = access %p0, 3i |
| %33:f32 = call %f2, %p0_inner |
| %34:f32 = call %f1, %p1 |
| %35:f32 = add %33, %34 |
| ret %35 |
| } |
| } |
| %f4 = func(%p_3:ptr<uniform, Outer, read>):f32 { # %p_3: 'p' |
| $B6: { |
| %38:ptr<uniform, array<Inner, 4>, read> = access %p_3, 0u |
| %39:ptr<uniform, mat3x4<f32>, read> = access %U, 1u |
| %40:f32 = call %f3, %38, %39 |
| ret %40 |
| } |
| } |
| %b = func():void { |
| $B7: { |
| %42:f32 = call %f4, %U |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Inner = struct @align(16) { |
| mat:mat3x4<f32> @offset(0) |
| } |
| |
| Outer = struct @align(16) { |
| arr:array<Inner, 4> @offset(0) |
| mat:mat3x4<f32> @offset(192) |
| } |
| |
| $B1: { # root |
| %U:ptr<uniform, Outer, read> = var @binding_point(0, 0) |
| } |
| |
| %f0 = func(%p_indices:array<u32, 1>):f32 { |
| $B2: { |
| %4:u32 = access %p_indices, 0u |
| %5:ptr<uniform, vec4<f32>, read> = access %U, 1u, %4 |
| %6:f32 = load_vector_element %5, 0u |
| ret %6 |
| } |
| } |
| %f0_1 = func(%p_indices_1:array<u32, 2>):f32 { # %f0_1: 'f0', %p_indices_1: 'p_indices' |
| $B3: { |
| %9:u32 = access %p_indices_1, 0u |
| %10:u32 = access %p_indices_1, 1u |
| %11:ptr<uniform, vec4<f32>, read> = access %U, 0u, %9, 0u, %10 |
| %12:f32 = load_vector_element %11, 0u |
| ret %12 |
| } |
| } |
| %f1 = func():f32 { |
| $B4: { |
| %res:ptr<function, f32, read_write> = var |
| %15:u32 = convert 1i |
| %16:array<u32, 1> = construct %15 |
| %17:f32 = call %f0, %16 |
| %18:f32 = load %res |
| %19:f32 = add %18, %17 |
| store %res, %19 |
| %20:u32 = convert 1i |
| %21:array<u32, 1> = construct %20 |
| %22:f32 = call %f0, %21 |
| %23:f32 = load %res |
| %24:f32 = add %23, %22 |
| store %res, %24 |
| %25:u32 = convert 2i |
| %26:u32 = convert 1i |
| %27:array<u32, 2> = construct %25, %26 |
| %28:f32 = call %f0_1, %27 |
| %29:f32 = load %res |
| %30:f32 = add %29, %28 |
| store %res, %30 |
| %31:u32 = convert 2i |
| %32:u32 = convert 1i |
| %33:array<u32, 2> = construct %31, %32 |
| %34:f32 = call %f0_1, %33 |
| %35:f32 = load %res |
| %36:f32 = add %35, %34 |
| store %res, %36 |
| %37:f32 = load %res |
| ret %37 |
| } |
| } |
| %f1_1 = func(%p_indices_2:array<u32, 1>):f32 { # %f1_1: 'f1', %p_indices_2: 'p_indices' |
| $B5: { |
| %40:u32 = access %p_indices_2, 0u |
| %res_1:ptr<function, f32, read_write> = var # %res_1: 'res' |
| %42:u32 = convert 1i |
| %43:array<u32, 2> = construct %40, %42 |
| %44:f32 = call %f0_1, %43 |
| %45:f32 = load %res_1 |
| %46:f32 = add %45, %44 |
| store %res_1, %46 |
| %47:u32 = convert 1i |
| %48:array<u32, 2> = construct %40, %47 |
| %49:f32 = call %f0_1, %48 |
| %50:f32 = load %res_1 |
| %51:f32 = add %50, %49 |
| store %res_1, %51 |
| %52:u32 = convert 2i |
| %53:u32 = convert 1i |
| %54:array<u32, 2> = construct %52, %53 |
| %55:f32 = call %f0_1, %54 |
| %56:f32 = load %res_1 |
| %57:f32 = add %56, %55 |
| store %res_1, %57 |
| %58:u32 = convert 2i |
| %59:u32 = convert 1i |
| %60:array<u32, 2> = construct %58, %59 |
| %61:f32 = call %f0_1, %60 |
| %62:f32 = load %res_1 |
| %63:f32 = add %62, %61 |
| store %res_1, %63 |
| %64:f32 = load %res_1 |
| ret %64 |
| } |
| } |
| %f2 = func(%p_indices_3:array<u32, 1>):f32 { # %p_indices_3: 'p_indices' |
| $B6: { |
| %67:u32 = access %p_indices_3, 0u |
| %68:array<u32, 1> = construct %67 |
| %69:f32 = call %f1_1, %68 |
| ret %69 |
| } |
| } |
| %f3 = func():f32 { |
| $B7: { |
| %71:u32 = convert 3i |
| %72:array<u32, 1> = construct %71 |
| %73:f32 = call %f2, %72 |
| %74:f32 = call %f1 |
| %75:f32 = add %73, %74 |
| ret %75 |
| } |
| } |
| %f4 = func():f32 { |
| $B8: { |
| %77:f32 = call %f3 |
| ret %77 |
| } |
| } |
| %b = func():void { |
| $B9: { |
| %79:f32 = call %f4 |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_UniformAS, CallChaining2) { |
| auto* T3 = ty.vec4<i32>(); |
| auto* T2 = ty.array(T3, 5); |
| auto* T1 = ty.array(T2, 5); |
| auto* T = ty.array(T1, 5); |
| |
| Var* input = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| input = b.Var("U", ty.ptr<uniform>(T)); |
| input->SetBindingPoint(0, 0); |
| }); |
| |
| auto* f2 = b.Function("f2", T3); |
| { |
| auto* p = b.FunctionParam("p", ty.ptr<uniform>(T2)); |
| f2->SetParams({p}); |
| b.Append(f2->Block(), |
| [&] { b.Return(f2, b.Load(b.Access<ptr<uniform, vec4<i32>>>(p, 3_u))); }); |
| } |
| |
| auto* f1 = b.Function("f1", T3); |
| { |
| auto* p = b.FunctionParam("p", ty.ptr<uniform>(T1)); |
| f1->SetParams({p}); |
| b.Append(f1->Block(), |
| [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<uniform>(T2), p, 2_u))); }); |
| } |
| |
| auto* f0 = b.Function("f0", T3); |
| { |
| auto* p = b.FunctionParam("p", ty.ptr<uniform>(T)); |
| f0->SetParams({p}); |
| b.Append(f0->Block(), |
| [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<uniform>(T1), p, 1_u))); }); |
| } |
| |
| auto* main = b.Function("main", ty.void_()); |
| b.Append(main->Block(), [&] { |
| b.Call(f0, input); |
| b.Return(main); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %U:ptr<uniform, array<array<array<vec4<i32>, 5>, 5>, 5>, read> = var @binding_point(0, 0) |
| } |
| |
| %f2 = func(%p:ptr<uniform, array<vec4<i32>, 5>, read>):vec4<i32> { |
| $B2: { |
| %4:ptr<uniform, vec4<i32>, read> = access %p, 3u |
| %5:vec4<i32> = load %4 |
| ret %5 |
| } |
| } |
| %f1 = func(%p_1:ptr<uniform, array<array<vec4<i32>, 5>, 5>, read>):vec4<i32> { # %p_1: 'p' |
| $B3: { |
| %8:ptr<uniform, array<vec4<i32>, 5>, read> = access %p_1, 2u |
| %9:vec4<i32> = call %f2, %8 |
| ret %9 |
| } |
| } |
| %f0 = func(%p_2:ptr<uniform, array<array<array<vec4<i32>, 5>, 5>, 5>, read>):vec4<i32> { # %p_2: 'p' |
| $B4: { |
| %12:ptr<uniform, array<array<vec4<i32>, 5>, 5>, read> = access %p_2, 1u |
| %13:vec4<i32> = call %f1, %12 |
| ret %13 |
| } |
| } |
| %main = func():void { |
| $B5: { |
| %15:vec4<i32> = call %f0, %U |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %U:ptr<uniform, array<array<array<vec4<i32>, 5>, 5>, 5>, read> = var @binding_point(0, 0) |
| } |
| |
| %f2 = func(%p_indices:array<u32, 2>):vec4<i32> { |
| $B2: { |
| %4:u32 = access %p_indices, 0u |
| %5:u32 = access %p_indices, 1u |
| %6:ptr<uniform, array<vec4<i32>, 5>, read> = access %U, %4, %5 |
| %7:ptr<uniform, vec4<i32>, read> = access %6, 3u |
| %8:vec4<i32> = load %7 |
| ret %8 |
| } |
| } |
| %f1 = func(%p_indices_1:array<u32, 1>):vec4<i32> { # %p_indices_1: 'p_indices' |
| $B3: { |
| %11:u32 = access %p_indices_1, 0u |
| %12:array<u32, 2> = construct %11, 2u |
| %13:vec4<i32> = call %f2, %12 |
| ret %13 |
| } |
| } |
| %f0 = func():vec4<i32> { |
| $B4: { |
| %15:array<u32, 1> = construct 1u |
| %16:vec4<i32> = call %f1, %15 |
| ret %16 |
| } |
| } |
| %main = func():void { |
| $B5: { |
| %18:vec4<i32> = call %f0 |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| } // namespace uniform_as_tests |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 'storage' address space |
| //////////////////////////////////////////////////////////////////////////////// |
| namespace storage_as_tests { |
| |
| using IR_DirectVariableAccessTest_StorageAS = TransformTest; |
| |
| TEST_F(IR_DirectVariableAccessTest_StorageAS, Param_ptr_i32_Via_struct_read) { |
| auto* str_ = ty.Struct(mod.symbols.New("str"), { |
| {mod.symbols.Register("i"), ty.i32()}, |
| }); |
| |
| Var* S = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| S = b.Var("S", ty.ptr<storage, read>(str_)); |
| S->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.i32()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<storage, i32, read>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* access = b.Access(ty.ptr<storage, i32, read>(), S, 0_u); |
| b.Call(fn_a, 10_i, access, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| str = struct @align(4) { |
| i:i32 @offset(0) |
| } |
| |
| $B1: { # root |
| %S:ptr<storage, str, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p:ptr<storage, i32, read>, %post:i32):i32 { |
| $B2: { |
| %6:i32 = load %p |
| ret %6 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %8:ptr<storage, i32, read> = access %S, 0u |
| %9:i32 = call %a, 10i, %8, 20i |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| str = struct @align(4) { |
| i:i32 @offset(0) |
| } |
| |
| $B1: { # root |
| %S:ptr<storage, str, read> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %post:i32):i32 { |
| $B2: { |
| %5:ptr<storage, i32, read> = access %S, 0u |
| %6:i32 = load %5 |
| ret %6 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %8:i32 = call %a, 10i, 20i |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_StorageAS, Param_ptr_arr_i32_Via_struct_write) { |
| auto* str_ = |
| ty.Struct(mod.symbols.New("str"), { |
| {mod.symbols.Register("arr"), ty.array<i32, 4>()}, |
| }); |
| |
| Var* S = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| S = b.Var("S", ty.ptr<storage>(str_)); |
| S->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.void_()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<storage, array<i32, 4>>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { |
| b.Store(fn_a_p, b.Splat<array<i32, 4>>(0_i)); |
| b.Return(fn_a); |
| }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* access = b.Access(ty.ptr<storage, array<i32, 4>>(), S, 0_u); |
| b.Call(fn_a, 10_i, access, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| str = struct @align(4) { |
| arr:array<i32, 4> @offset(0) |
| } |
| |
| $B1: { # root |
| %S:ptr<storage, str, read_write> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p:ptr<storage, array<i32, 4>, read_write>, %post:i32):void { |
| $B2: { |
| store %p, array<i32, 4>(0i) |
| ret |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %7:ptr<storage, array<i32, 4>, read_write> = access %S, 0u |
| %8:void = call %a, 10i, %7, 20i |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| str = struct @align(4) { |
| arr:array<i32, 4> @offset(0) |
| } |
| |
| $B1: { # root |
| %S:ptr<storage, str, read_write> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %post:i32):void { |
| $B2: { |
| %5:ptr<storage, array<i32, 4>, read_write> = access %S, 0u |
| store %5, array<i32, 4>(0i) |
| ret |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %7:void = call %a, 10i, 20i |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_StorageAS, Param_ptr_vec4i32_Via_array_DynamicWrite) { |
| Var* S = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| S = b.Var<storage, array<vec4<i32>, 8>>("S"); |
| S->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.void_()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<storage, vec4<i32>>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { |
| b.Store(fn_a_p, b.Splat<vec4<i32>>(0_i)); |
| b.Return(fn_a); |
| }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* I = b.Let("I", 3_i); |
| auto* access = b.Access(ty.ptr<storage, vec4<i32>>(), S, I); |
| b.Call(fn_a, 10_i, access, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %S:ptr<storage, array<vec4<i32>, 8>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p:ptr<storage, vec4<i32>, read_write>, %post:i32):void { |
| $B2: { |
| store %p, vec4<i32>(0i) |
| ret |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %I:i32 = let 3i |
| %8:ptr<storage, vec4<i32>, read_write> = access %S, %I |
| %9:void = call %a, 10i, %8, 20i |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %S:ptr<storage, array<vec4<i32>, 8>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %a = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):void { |
| $B2: { |
| %6:u32 = access %p_indices, 0u |
| %7:ptr<storage, vec4<i32>, read_write> = access %S, %6 |
| store %7, vec4<i32>(0i) |
| ret |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %I:i32 = let 3i |
| %10:u32 = convert %I |
| %11:array<u32, 1> = construct %10 |
| %12:void = call %a, 10i, %11, 20i |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_StorageAS, CallChaining) { |
| auto* Inner = |
| ty.Struct(mod.symbols.New("Inner"), { |
| {mod.symbols.Register("mat"), ty.mat3x4<f32>()}, |
| }); |
| auto* Outer = |
| ty.Struct(mod.symbols.New("Outer"), { |
| {mod.symbols.Register("arr"), ty.array(Inner, 4)}, |
| {mod.symbols.Register("mat"), ty.mat3x4<f32>()}, |
| }); |
| Var* S = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| S = b.Var("S", ty.ptr<storage, read>(Outer)); |
| S->SetBindingPoint(0, 0); |
| }); |
| |
| auto* fn_0 = b.Function("f0", ty.f32()); |
| auto* fn_0_p = b.FunctionParam("p", ty.ptr<storage, vec4<f32>, read>()); |
| fn_0->SetParams({fn_0_p}); |
| b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); }); |
| |
| auto* fn_1 = b.Function("f1", ty.f32()); |
| auto* fn_1_p = b.FunctionParam("p", ty.ptr<storage, mat3x4<f32>, read>()); |
| fn_1->SetParams({fn_1_p}); |
| b.Append(fn_1->Block(), [&] { |
| auto* res = b.Var<function, f32>("res"); |
| { |
| // res += f0(&(*p)[1]); |
| auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<storage, vec4<f32>, read>(), fn_1_p, 1_i)); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // let p_vec = &(*p)[1]; |
| // res += f0(p_vec); |
| auto* p_vec = b.Access(ty.ptr<storage, vec4<f32>, read>(), fn_1_p, 1_i); |
| b.ir.SetName(p_vec, "p_vec"); |
| auto* call_0 = b.Call(fn_0, p_vec); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // res += f0(&U.arr[2].mat[1]); |
| auto* access = b.Access(ty.ptr<storage, vec4<f32>, read>(), S, 0_u, 2_i, 0_u, 1_i); |
| auto* call_0 = b.Call(fn_0, access); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // let p_vec = &U.arr[2].mat[1]; |
| // res += f0(p_vec); |
| auto* p_vec = b.Access(ty.ptr<storage, vec4<f32>, read>(), S, 0_u, 2_i, 0_u, 1_i); |
| b.ir.SetName(p_vec, "p_vec"); |
| auto* call_0 = b.Call(fn_0, p_vec); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| |
| b.Return(fn_1, b.Load(res)); |
| }); |
| |
| auto* fn_2 = b.Function("f2", ty.f32()); |
| auto* fn_2_p = b.FunctionParam("p", ty.ptr<storage, read>(Inner)); |
| fn_2->SetParams({fn_2_p}); |
| b.Append(fn_2->Block(), [&] { |
| auto* p_mat = b.Access(ty.ptr<storage, mat3x4<f32>, read>(), fn_2_p, 0_u); |
| b.ir.SetName(p_mat, "p_mat"); |
| b.Return(fn_2, b.Call(fn_1, p_mat)); |
| }); |
| |
| auto* fn_3 = b.Function("f3", ty.f32()); |
| auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<storage, read>(ty.array(Inner, 4))); |
| auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<storage, mat3x4<f32>, read>()); |
| fn_3->SetParams({fn_3_p0, fn_3_p1}); |
| b.Append(fn_3->Block(), [&] { |
| auto* p0_inner = b.Access(ty.ptr<storage, read>(Inner), fn_3_p0, 3_i); |
| b.ir.SetName(p0_inner, "p0_inner"); |
| auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner); |
| auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1); |
| b.Return(fn_3, b.Add(ty.f32(), call_0, call_1)); |
| }); |
| |
| auto* fn_4 = b.Function("f4", ty.f32()); |
| auto* fn_4_p = b.FunctionParam("p", ty.ptr<storage, read>(Outer)); |
| fn_4->SetParams({fn_4_p}); |
| b.Append(fn_4->Block(), [&] { |
| auto* access_0 = b.Access(ty.ptr<storage, read>(ty.array(Inner, 4)), fn_4_p, 0_u); |
| auto* access_1 = b.Access(ty.ptr<storage, mat3x4<f32>, read>(), S, 1_u); |
| b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1)); |
| }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| b.Call(ty.f32(), fn_4, S); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| Inner = struct @align(16) { |
| mat:mat3x4<f32> @offset(0) |
| } |
| |
| Outer = struct @align(16) { |
| arr:array<Inner, 4> @offset(0) |
| mat:mat3x4<f32> @offset(192) |
| } |
| |
| $B1: { # root |
| %S:ptr<storage, Outer, read> = var @binding_point(0, 0) |
| } |
| |
| %f0 = func(%p:ptr<storage, vec4<f32>, read>):f32 { |
| $B2: { |
| %4:f32 = load_vector_element %p, 0u |
| ret %4 |
| } |
| } |
| %f1 = func(%p_1:ptr<storage, mat3x4<f32>, read>):f32 { # %p_1: 'p' |
| $B3: { |
| %res:ptr<function, f32, read_write> = var |
| %8:ptr<storage, vec4<f32>, read> = access %p_1, 1i |
| %9:f32 = call %f0, %8 |
| %10:f32 = load %res |
| %11:f32 = add %10, %9 |
| store %res, %11 |
| %p_vec:ptr<storage, vec4<f32>, read> = access %p_1, 1i |
| %13:f32 = call %f0, %p_vec |
| %14:f32 = load %res |
| %15:f32 = add %14, %13 |
| store %res, %15 |
| %16:ptr<storage, vec4<f32>, read> = access %S, 0u, 2i, 0u, 1i |
| %17:f32 = call %f0, %16 |
| %18:f32 = load %res |
| %19:f32 = add %18, %17 |
| store %res, %19 |
| %p_vec_1:ptr<storage, vec4<f32>, read> = access %S, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec' |
| %21:f32 = call %f0, %p_vec_1 |
| %22:f32 = load %res |
| %23:f32 = add %22, %21 |
| store %res, %23 |
| %24:f32 = load %res |
| ret %24 |
| } |
| } |
| %f2 = func(%p_2:ptr<storage, Inner, read>):f32 { # %p_2: 'p' |
| $B4: { |
| %p_mat:ptr<storage, mat3x4<f32>, read> = access %p_2, 0u |
| %28:f32 = call %f1, %p_mat |
| ret %28 |
| } |
| } |
| %f3 = func(%p0:ptr<storage, array<Inner, 4>, read>, %p1:ptr<storage, mat3x4<f32>, read>):f32 { |
| $B5: { |
| %p0_inner:ptr<storage, Inner, read> = access %p0, 3i |
| %33:f32 = call %f2, %p0_inner |
| %34:f32 = call %f1, %p1 |
| %35:f32 = add %33, %34 |
| ret %35 |
| } |
| } |
| %f4 = func(%p_3:ptr<storage, Outer, read>):f32 { # %p_3: 'p' |
| $B6: { |
| %38:ptr<storage, array<Inner, 4>, read> = access %p_3, 0u |
| %39:ptr<storage, mat3x4<f32>, read> = access %S, 1u |
| %40:f32 = call %f3, %38, %39 |
| ret %40 |
| } |
| } |
| %b = func():void { |
| $B7: { |
| %42:f32 = call %f4, %S |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Inner = struct @align(16) { |
| mat:mat3x4<f32> @offset(0) |
| } |
| |
| Outer = struct @align(16) { |
| arr:array<Inner, 4> @offset(0) |
| mat:mat3x4<f32> @offset(192) |
| } |
| |
| $B1: { # root |
| %S:ptr<storage, Outer, read> = var @binding_point(0, 0) |
| } |
| |
| %f0 = func(%p_indices:array<u32, 1>):f32 { |
| $B2: { |
| %4:u32 = access %p_indices, 0u |
| %5:ptr<storage, vec4<f32>, read> = access %S, 1u, %4 |
| %6:f32 = load_vector_element %5, 0u |
| ret %6 |
| } |
| } |
| %f0_1 = func(%p_indices_1:array<u32, 2>):f32 { # %f0_1: 'f0', %p_indices_1: 'p_indices' |
| $B3: { |
| %9:u32 = access %p_indices_1, 0u |
| %10:u32 = access %p_indices_1, 1u |
| %11:ptr<storage, vec4<f32>, read> = access %S, 0u, %9, 0u, %10 |
| %12:f32 = load_vector_element %11, 0u |
| ret %12 |
| } |
| } |
| %f1 = func():f32 { |
| $B4: { |
| %res:ptr<function, f32, read_write> = var |
| %15:u32 = convert 1i |
| %16:array<u32, 1> = construct %15 |
| %17:f32 = call %f0, %16 |
| %18:f32 = load %res |
| %19:f32 = add %18, %17 |
| store %res, %19 |
| %20:u32 = convert 1i |
| %21:array<u32, 1> = construct %20 |
| %22:f32 = call %f0, %21 |
| %23:f32 = load %res |
| %24:f32 = add %23, %22 |
| store %res, %24 |
| %25:u32 = convert 2i |
| %26:u32 = convert 1i |
| %27:array<u32, 2> = construct %25, %26 |
| %28:f32 = call %f0_1, %27 |
| %29:f32 = load %res |
| %30:f32 = add %29, %28 |
| store %res, %30 |
| %31:u32 = convert 2i |
| %32:u32 = convert 1i |
| %33:array<u32, 2> = construct %31, %32 |
| %34:f32 = call %f0_1, %33 |
| %35:f32 = load %res |
| %36:f32 = add %35, %34 |
| store %res, %36 |
| %37:f32 = load %res |
| ret %37 |
| } |
| } |
| %f1_1 = func(%p_indices_2:array<u32, 1>):f32 { # %f1_1: 'f1', %p_indices_2: 'p_indices' |
| $B5: { |
| %40:u32 = access %p_indices_2, 0u |
| %res_1:ptr<function, f32, read_write> = var # %res_1: 'res' |
| %42:u32 = convert 1i |
| %43:array<u32, 2> = construct %40, %42 |
| %44:f32 = call %f0_1, %43 |
| %45:f32 = load %res_1 |
| %46:f32 = add %45, %44 |
| store %res_1, %46 |
| %47:u32 = convert 1i |
| %48:array<u32, 2> = construct %40, %47 |
| %49:f32 = call %f0_1, %48 |
| %50:f32 = load %res_1 |
| %51:f32 = add %50, %49 |
| store %res_1, %51 |
| %52:u32 = convert 2i |
| %53:u32 = convert 1i |
| %54:array<u32, 2> = construct %52, %53 |
| %55:f32 = call %f0_1, %54 |
| %56:f32 = load %res_1 |
| %57:f32 = add %56, %55 |
| store %res_1, %57 |
| %58:u32 = convert 2i |
| %59:u32 = convert 1i |
| %60:array<u32, 2> = construct %58, %59 |
| %61:f32 = call %f0_1, %60 |
| %62:f32 = load %res_1 |
| %63:f32 = add %62, %61 |
| store %res_1, %63 |
| %64:f32 = load %res_1 |
| ret %64 |
| } |
| } |
| %f2 = func(%p_indices_3:array<u32, 1>):f32 { # %p_indices_3: 'p_indices' |
| $B6: { |
| %67:u32 = access %p_indices_3, 0u |
| %68:array<u32, 1> = construct %67 |
| %69:f32 = call %f1_1, %68 |
| ret %69 |
| } |
| } |
| %f3 = func():f32 { |
| $B7: { |
| %71:u32 = convert 3i |
| %72:array<u32, 1> = construct %71 |
| %73:f32 = call %f2, %72 |
| %74:f32 = call %f1 |
| %75:f32 = add %73, %74 |
| ret %75 |
| } |
| } |
| %f4 = func():f32 { |
| $B8: { |
| %77:f32 = call %f3 |
| ret %77 |
| } |
| } |
| %b = func():void { |
| $B9: { |
| %79:f32 = call %f4 |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_StorageAS, CallChaining2) { |
| auto* T3 = ty.vec4<i32>(); |
| auto* T2 = ty.array(T3, 5); |
| auto* T1 = ty.array(T2, 5); |
| auto* T = ty.array(T1, 5); |
| |
| Var* input = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| input = b.Var("U", ty.ptr<storage>(T)); |
| input->SetBindingPoint(0, 0); |
| }); |
| |
| auto* f2 = b.Function("f2", T3); |
| { |
| auto* p = b.FunctionParam("p", ty.ptr<storage>(T2)); |
| f2->SetParams({p}); |
| b.Append(f2->Block(), |
| [&] { b.Return(f2, b.Load(b.Access<ptr<storage, vec4<i32>>>(p, 3_u))); }); |
| } |
| |
| auto* f1 = b.Function("f1", T3); |
| { |
| auto* p = b.FunctionParam("p", ty.ptr<storage>(T1)); |
| f1->SetParams({p}); |
| b.Append(f1->Block(), |
| [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<storage>(T2), p, 2_u))); }); |
| } |
| |
| auto* f0 = b.Function("f0", T3); |
| { |
| auto* p = b.FunctionParam("p", ty.ptr<storage>(T)); |
| f0->SetParams({p}); |
| b.Append(f0->Block(), |
| [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<storage>(T1), p, 1_u))); }); |
| } |
| |
| auto* main = b.Function("main", ty.void_()); |
| b.Append(main->Block(), [&] { |
| b.Call(f0, input); |
| b.Return(main); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %U:ptr<storage, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %f2 = func(%p:ptr<storage, array<vec4<i32>, 5>, read_write>):vec4<i32> { |
| $B2: { |
| %4:ptr<storage, vec4<i32>, read_write> = access %p, 3u |
| %5:vec4<i32> = load %4 |
| ret %5 |
| } |
| } |
| %f1 = func(%p_1:ptr<storage, array<array<vec4<i32>, 5>, 5>, read_write>):vec4<i32> { # %p_1: 'p' |
| $B3: { |
| %8:ptr<storage, array<vec4<i32>, 5>, read_write> = access %p_1, 2u |
| %9:vec4<i32> = call %f2, %8 |
| ret %9 |
| } |
| } |
| %f0 = func(%p_2:ptr<storage, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> { # %p_2: 'p' |
| $B4: { |
| %12:ptr<storage, array<array<vec4<i32>, 5>, 5>, read_write> = access %p_2, 1u |
| %13:vec4<i32> = call %f1, %12 |
| ret %13 |
| } |
| } |
| %main = func():void { |
| $B5: { |
| %15:vec4<i32> = call %f0, %U |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %U:ptr<storage, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %f2 = func(%p_indices:array<u32, 2>):vec4<i32> { |
| $B2: { |
| %4:u32 = access %p_indices, 0u |
| %5:u32 = access %p_indices, 1u |
| %6:ptr<storage, array<vec4<i32>, 5>, read_write> = access %U, %4, %5 |
| %7:ptr<storage, vec4<i32>, read_write> = access %6, 3u |
| %8:vec4<i32> = load %7 |
| ret %8 |
| } |
| } |
| %f1 = func(%p_indices_1:array<u32, 1>):vec4<i32> { # %p_indices_1: 'p_indices' |
| $B3: { |
| %11:u32 = access %p_indices_1, 0u |
| %12:array<u32, 2> = construct %11, 2u |
| %13:vec4<i32> = call %f2, %12 |
| ret %13 |
| } |
| } |
| %f0 = func():vec4<i32> { |
| $B4: { |
| %15:array<u32, 1> = construct 1u |
| %16:vec4<i32> = call %f1, %15 |
| ret %16 |
| } |
| } |
| %main = func():void { |
| $B5: { |
| %18:vec4<i32> = call %f0 |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| } // namespace storage_as_tests |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 'workgroup' address space |
| //////////////////////////////////////////////////////////////////////////////// |
| namespace workgroup_as_tests { |
| |
| using IR_DirectVariableAccessTest_WorkgroupAS = TransformTest; |
| |
| TEST_F(IR_DirectVariableAccessTest_WorkgroupAS, Param_ptr_vec4i32_Via_array_StaticRead) { |
| Var* W = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| W = b.Var("W", ty.ptr<workgroup, array<vec4<i32>, 8>>()); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.vec4<i32>()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<workgroup, vec4<i32>>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* access = b.Access(ty.ptr<workgroup, vec4<i32>>(), W, 3_i); |
| b.Call(fn_a, 10_i, access, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var |
| } |
| |
| %a = func(%pre:i32, %p:ptr<workgroup, vec4<i32>, read_write>, %post:i32):vec4<i32> { |
| $B2: { |
| %6:vec4<i32> = load %p |
| ret %6 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %8:ptr<workgroup, vec4<i32>, read_write> = access %W, 3i |
| %9:vec4<i32> = call %a, 10i, %8, 20i |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var |
| } |
| |
| %a = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):vec4<i32> { |
| $B2: { |
| %6:u32 = access %p_indices, 0u |
| %7:ptr<workgroup, vec4<i32>, read_write> = access %W, %6 |
| %8:vec4<i32> = load %7 |
| ret %8 |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %10:u32 = convert 3i |
| %11:array<u32, 1> = construct %10 |
| %12:vec4<i32> = call %a, 10i, %11, 20i |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_WorkgroupAS, Param_ptr_vec4i32_Via_array_StaticWrite) { |
| Var* W = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| W = b.Var<workgroup, array<vec4<i32>, 8>>("W"); |
| }); |
| |
| auto* fn_a = b.Function("a", ty.void_()); |
| auto* fn_a_p = b.FunctionParam("p", ty.ptr<workgroup, vec4<i32>>()); |
| fn_a->SetParams({ |
| b.FunctionParam("pre", ty.i32()), |
| fn_a_p, |
| b.FunctionParam("post", ty.i32()), |
| }); |
| b.Append(fn_a->Block(), [&] { |
| b.Store(fn_a_p, b.Splat<vec4<i32>>(0_i)); |
| b.Return(fn_a); |
| }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| auto* access = b.Access(ty.ptr<workgroup, vec4<i32>>(), W, 3_i); |
| b.Call(fn_a, 10_i, access, 20_i); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var |
| } |
| |
| %a = func(%pre:i32, %p:ptr<workgroup, vec4<i32>, read_write>, %post:i32):void { |
| $B2: { |
| store %p, vec4<i32>(0i) |
| ret |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %7:ptr<workgroup, vec4<i32>, read_write> = access %W, 3i |
| %8:void = call %a, 10i, %7, 20i |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| $B1: { # root |
| %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var |
| } |
| |
| %a = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):void { |
| $B2: { |
| %6:u32 = access %p_indices, 0u |
| %7:ptr<workgroup, vec4<i32>, read_write> = access %W, %6 |
| store %7, vec4<i32>(0i) |
| ret |
| } |
| } |
| %b = func():void { |
| $B3: { |
| %9:u32 = convert 3i |
| %10:array<u32, 1> = construct %9 |
| %11:void = call %a, 10i, %10, 20i |
| ret |
| } |
| } |
| )"; |
| |
| Run(DirectVariableAccess, DirectVariableAccessOptions{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DirectVariableAccessTest_WorkgroupAS, CallChaining) { |
| auto* Inner = |
| ty.Struct(mod.symbols.New("Inner"), { |
| {mod.symbols.Register("mat"), ty.mat3x4<f32>()}, |
| }); |
| auto* Outer = |
| ty.Struct(mod.symbols.New("Outer"), { |
| {mod.symbols.Register("arr"), ty.array(Inner, 4)}, |
| {mod.symbols.Register("mat"), ty.mat3x4<f32>()}, |
| }); |
| Var* W = nullptr; |
| b.Append(b.ir.root_block, |
| [&] { // |
| W = b.Var("W", ty.ptr<workgroup>(Outer)); |
| }); |
| |
| auto* fn_0 = b.Function("f0", ty.f32()); |
| auto* fn_0_p = b.FunctionParam("p", ty.ptr<workgroup, vec4<f32>>()); |
| fn_0->SetParams({fn_0_p}); |
| b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); }); |
| |
| auto* fn_1 = b.Function("f1", ty.f32()); |
| auto* fn_1_p = b.FunctionParam("p", ty.ptr<workgroup, mat3x4<f32>>()); |
| fn_1->SetParams({fn_1_p}); |
| b.Append(fn_1->Block(), [&] { |
| auto* res = b.Var<function, f32>("res"); |
| { |
| // res += f0(&(*p)[1]); |
| auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<workgroup, vec4<f32>>(), fn_1_p, 1_i)); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // let p_vec = &(*p)[1]; |
| // res += f0(p_vec); |
| auto* p_vec = b.Access(ty.ptr<workgroup, vec4<f32>>(), fn_1_p, 1_i); |
| b.ir.SetName(p_vec, "p_vec"); |
| auto* call_0 = b.Call(fn_0, p_vec); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // res += f0(&U.arr[2].mat[1]); |
| auto* access = b.Access(ty.ptr<workgroup, vec4<f32>>(), W, 0_u, 2_i, 0_u, 1_i); |
| auto* call_0 = b.Call(fn_0, access); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| { |
| // let p_vec = &U.arr[2].mat[1]; |
| // res += f0(p_vec); |
| auto* p_vec = b.Access(ty.ptr<workgroup, vec4<f32>>(), W, 0_u, 2_i, 0_u, 1_i); |
| b.ir.SetName(p_vec, "p_vec"); |
| auto* call_0 = b.Call(fn_0, p_vec); |
| b.Store(res, b.Add(ty.f32(), b.Load(res), call_0)); |
| } |
| |
| b.Return(fn_1, b.Load(res)); |
| }); |
| |
| auto* fn_2 = b.Function("f2", ty.f32()); |
| auto* fn_2_p = b.FunctionParam("p", ty.ptr<workgroup>(Inner)); |
| fn_2->SetParams({fn_2_p}); |
| b.Append(fn_2->Block(), [&] { |
| auto* p_mat = b.Access(ty.ptr<workgroup, mat3x4<f32>>(), fn_2_p, 0_u); |
| b.ir.SetName(p_mat, "p_mat"); |
| b.Return(fn_2, b.Call(fn_1, p_mat)); |
| }); |
| |
| auto* fn_3 = b.Function("f3", ty.f32()); |
| auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<workgroup>(ty.array(Inner, 4))); |
| auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<workgroup, mat3x4<f32>>()); |
| fn_3->SetParams({fn_3_p0, fn_3_p1}); |
| b.Append(fn_3->Block(), [&] { |
| auto* p0_inner = b.Access(ty.ptr<workgroup>(Inner), fn_3_p0, 3_i); |
| b.ir.SetName(p0_inner, "p0_inner"); |
| auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner); |
| auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1); |
| b.Return(fn_3, b.Add(ty.f32(), call_0, call_1)); |
| }); |
| |
| auto* fn_4 = b.Function("f4", ty.f32()); |
| auto* fn_4_p = b.FunctionParam("p", ty.ptr<workgroup>(Outer)); |
| fn_4->SetParams({fn_4_p}); |
| b.Append(fn_4->Block(), [&] { |
| auto* access_0 = b.Access(ty.ptr<workgroup>(ty.array(Inner, 4)), fn_4_p, 0_u); |
| auto* access_1 = b.Access(ty.ptr<workgroup, mat3x4<f32>>(), W, 1_u); |
| b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1)); |
| }); |
| |
| auto* fn_b = b.Function("b", ty.void_()); |
| b.Append(fn_b->Block(), [&] { |
| b.Call(ty.f32(), fn_4, W); |
| b.Return(fn_b); |
| }); |
| |
| auto* src = R"( |
| Inner = struct @align(16) { |
| mat:mat3x4<f32> @offset(0) |
| } |
| |
| Outer = struct @align(16) { |
| arr:array<Inner, 4> @offset(0) |
| mat:mat3x4<f32> @offset(192) |
| } |
| |
| $B1: { # root |
| %W:ptr<workgroup, Outer, read_write> = var |
| } |
| |
| %f0 = func(%p:ptr<workgroup, vec4<f32>, read_write>):f32 { |
| $B2: { |
| %4:f32 = load_vector_element %p, 0u |
| ret %4 |
| } |
| } |
| %f1 = func(%p_1:ptr<workgroup, mat3x4<f32>, read_write>):f32 { # %p_1: 'p' |
| $B3: { |
| %res:ptr<function, f32, read_write> = var |
| %8:ptr<workgroup, vec4<f32>, read_write> = access %p_1, 1i |
| %9:f32 = call %f0, %8 |
| %10:f32 = load %res |
| %11:f32 = add %10, %9 |
| store %res, %11 |
| %p_vec:ptr<workgroup, vec4<f32>, read_write> = access %p_1, 1i |
| %13:f32 = call %f0, %p_vec |
| %14:f32 = load %res |
| %15:f32 = add %14, %13 |
| store %res, %15 |
| %16:ptr<workgroup, vec4<f32>, read_write> = access %W, 0u, 2i, 0u, 1i |
| %17:f32 = call %f0, %16 |
| %18:f32 = load %res |
| %19:f32 = add %18, %17 |
| store %res, %19 |
| %p_vec_1:ptr<workgroup, vec4<f32>, read_write> = access %W, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec' |
| %21:f32 = call %f0, %p_vec_1 |
| %22:f32 = load %res |
| %23:f32 = add %22, %21 |
| store %res, %23 |
| %24:f32 = load %res |
| ret %24 |
| } |
| } |
| %f2 = func(%p_2:ptr<workgroup, Inner, read_write>):f32 { # %p_2: 'p' |
| $B4: { |
| %p_mat:ptr<workgroup, mat3x4<f32>, read_write> = access %p_2, 0u |
| %28:f32 = call %f1, %p_mat |
| ret %28 |
| } |
| } |
| %f3 = func(%p0:ptr<workgroup, array<Inner, 4>, read_write>, %p1:ptr<workgroup, mat3x4<f32>, read_write>):f32 { |
| $B5: { |
| %p0_inner:ptr<workgroup, Inner, read_write> = access %p0, 3i |
| %33:f32 = call %f2, %p0_inner |
| %34:f32 = call %f1, %p1 |
| %35:f32 = add %33, %34 |
| ret %35 |
| } |
| } |
| %f4 = func(%p_3:ptr<workgroup, Outer, read_write>):f32 { # %p_3: 'p' |
| $B6: { |
| %38:ptr<workgroup, array<Inner, 4>, read_write> = access %p_3, 0u |
| %39:ptr<workgroup, mat3x4<f32>, read_write> = access %W, 1u |
| %40:f32 = call %f3, %38, %39 |
| ret %40 |
| } |
| } |
| %b = func():void { |
| $B7: { |
| %42:f32 = call %f4, %W |
| ret |
| } |
| } |
| )"; |
| |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Inner = struct @align(16) { |
| mat:mat3x4<f32> @offset(0) |
| } |
| |
| Outer = struct @align(16) { |
| arr:array<Inner, 4> @offset(0) |
| mat:mat3x4<f32> @offset(192) |
| } |
| |
| $B1: { # root |
| %W:ptr<workgroup, Outer, read_write> = var |
| } |
| |
| %f0 = func(%p_indices:array<u32, 1>):f32 { |
| $B2: { |
| %4:u32 = access %p_indices, 0u |
| %5:ptr<workgroup, vec4<f32>, read_write> = access %W, 1u, %4 |
| %6:f32 = load_vector_element %5, 0u |
| ret %6 |
| } |
| } |
| %f0_1 = func(%p_indices_1:array<u32, 2>):f32 { # %f0_1: 'f0', %p_indices_1: 'p_indices' |
| $B3: { |
| %9:u32 = access %p_indices_1, 0u |
| %10:u32 = access %p_indices_1, 1u |
| %11:ptr<workgroup, vec4<f32>, read_write> = access %W, 0u, %9, 0u, %10 |
| %12:f32 = load_vector_element %11, 0u |
| ret %12 |
| } |
| } |
| %f1 = func():f32 { |
| $B4: { |
| %res:ptr<function, f32, read_write> = var |
| %15:u32 = convert 1i |
| %16:array<u32, 1> = construct %15 |
| %17:f32 = call %f0, %16 |
| %18:f32 = load %res |
| %19:f32 = add %18, %17 |
| store %res, %19 |
| %20:u32 = convert 1i |
| %21:array<u32, 1> = construct %20 |
| %22:f32 = call %f0, %21 |
| %23:f32 = load %res |
| %24:f32 = add %23, %22 |
| store %res, %24 |
| %25:u32 = convert 2i |
| %26:u32 = convert 1i |
| %27:array<u32, 2> = construct %25, %26 |
| %28:f32 = call %f0_1, %27 |
| %29:f32 = load %res |
| %30:f32 = add %29, %28 |
| store %res, %30 |
| %31:u32 = convert 2i |
| %32:u32 = convert 1i |
| %33:array<u32, 2> = construct %31, %32 |
| %34:f32 = call %f0_1, %33 |
| %35:f32 = load %res |
| %36:f32 = add %35, %34 |
| store %res, %36 |
| %37:f32 = load %res |
| ret %37 |
| } |
| } |
| %f1_1 = func(%p_indices_2:array<u32, 1>):f32 { # %f1_1: 'f1', %p_indices_2: 'p_indices' |
| $B5: { |
| %40:u32 = access %p_indices_2, 0u |
| %res_1:ptr<function, f32, read_write> = var # %res_1: 'res' |
| %42:u32 = convert 1i |
| %43:array<u32, 2> = construct %40, %42 |
| %44:f32 = call %f0_1, %43 |
| %45:f32 = load %res_1 |
| %46:f32 = add %45, %44 |
| store %res_1, %46 |
| %47:u32 = convert 1i |
| %48:array<u32, 2> = construct %40, %47 |
| %49:f32 = call %f0_1, %48 |
| %50:f32 = load %res_1 |
| %51:f32 = add %50, %49 |
| store %res_1, %51 |
| %52:u32 = convert 2i |
| %53:u32 = convert 1i |
| %54:array<u32, 2> = construct %52, %53 |
| %55:f32 = call %f0_1, %54 |
| %56:f32 = load %res_1 |
| %57:f32 = add %56, %55 |
| store %res_1, %57 |
| %58:u32 = convert 2i |
| %59:u32 = convert 1i |
| %60:array<u32, 2> = construct %58, %59 |
| %61:f32 = call %f0_1, %60 |
| %62:f32 = load %res_1 |
| %63:f32 = add %62, %61 |
| store %res_1, %63 |
| %64:f32 = load %res_1 |
| ret %64 |
| } |
| } |
| %f2 = func(%p_indices_3:array<u32, 1>):f32 { # %p_indices_3: 'p_indices' |
| $B6: { |
| %67:u32 = access %p_indices_3, 0u |
| %68:array<u32, 1> = construct %67 |
| %69:f32 = call %f1_1, %68 |
| ret %69 |
| } |
| } |
| %f3 = func():f32 { |
| $B7: { |
| %71:u32 = convert 3i |
| %72:array<u32, 1> = construct %71 |
| %73:f32 = call %f2, %72 |
| %74:f32 = call %f1 |
| %75:f32 = add %73, %74 |
| ret %75 |
| } |
| } |
| %f4 = func():f32 { |
| $B8: { |
| %77:f32 = call %f3 |
| ret %77 |
| } |
| } |
| %b = func():void { |
| $B9: { |
| %79:f32 = call %f4 |
| ret |
| } |
| } |
| )"; |
| |