blob: fd89e61a64f5490937bfccc75d7e26d937a0581f [file] [log] [blame]
// 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
}
}
)";