blob: 5476656c7708bc8498ce8a4e202b24937a477023 [file] [log] [blame]
// Copyright 2025 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/msl/writer/raise/argument_buffers.h"
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/core/ir/transform/helper_test.h"
#include "src/tint/lang/core/type/binding_array.h"
#include "src/tint/lang/core/type/sampled_texture.h"
namespace tint::msl::writer::raise {
namespace {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
class MslWriter_ArgumentBuffersTest : public core::ir::transform::TransformTest {
public:
void SetUp() override {
capabilities.Add(core::ir::Capability::kAllowPointersAndHandlesInStructures,
core::ir::Capability::kAllowPrivateVarsInFunctions,
core::ir::Capability::kAllowAnyLetType);
}
};
TEST_F(MslWriter_ArgumentBuffersTest, NoArgumentBuffers) {
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* var = b.Var<function, i32>("v");
b.Load(var);
b.Return(func);
});
auto* src = R"(
%foo = @fragment func():void {
$B1: {
%v:ptr<function, i32, read_write> = var undef
%3:i32 = load %v
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, Private_NoChange) {
auto* var_a = b.Var("a", ty.ptr<private_, i32>());
auto* var_b = b.Var("b", ty.ptr<private_, i32>());
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_a, b.Add<i32>(load_a, load_b));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<private, i32, read_write> = var undef
%b:ptr<private, i32, read_write> = var undef
}
%foo = @fragment func():void {
$B2: {
%4:i32 = load %a
%5:i32 = load %b
%6:i32 = add %4, %5
store %a, %6
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%a:ptr<private, i32, read_write> = var undef
%b:ptr<private, i32, read_write> = var undef
}
%foo = @fragment func():void {
$B2: {
%4:i32 = load %a
%5:i32 = load %b
%6:i32 = add %4, %5
store %a, %6
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, Workgroup_NoChange) {
auto* var_a = b.Var("a", ty.ptr<workgroup, i32>());
auto* var_b = b.Var("b", ty.ptr<workgroup, i32>());
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_a, b.Add<i32>(load_a, load_b));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<workgroup, i32, read_write> = var undef
%b:ptr<workgroup, i32, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%4:i32 = load %a
%5:i32 = load %b
%6:i32 = add %4, %5
store %a, %6
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%a:ptr<workgroup, i32, read_write> = var undef
%b:ptr<workgroup, i32, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%4:i32 = load %a
%5:i32 = load %b
%6:i32 = add %4, %5
store %a, %6
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, Storage) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, u32, core::Access::kReadWrite>());
auto* var_c = b.Var("c", ty.ptr<storage, f32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
var_c->SetBindingPoint(3, 2);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
mod.root_block->Append(var_c);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
auto* load_c = b.Load(var_c);
auto* conv_b = b.Convert(ty.i32(), load_b);
auto* conv_c = b.Convert(ty.i32(), load_c);
auto* add = b.Add<i32>(b.Add<i32>(load_a, conv_b), conv_c);
auto* conv = b.Convert(ty.u32(), add);
b.Store(var_b, conv);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, u32, read_write> = var undef @binding_point(3, 4)
%c:ptr<storage, f32, read_write> = var undef @binding_point(3, 2)
}
%foo = @fragment func():void {
$B2: {
%5:i32 = load %a
%6:u32 = load %b
%7:f32 = load %c
%8:i32 = convert %6
%9:i32 = convert %7
%10:i32 = add %5, %8
%11:i32 = add %10, %9
%12:u32 = convert %11
store %b, %12
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
c:ptr<storage, f32, read_write> @offset(0), @binding_point(0, 2)
b:ptr<storage, u32, read_write> @offset(0), @binding_point(0, 4)
}
%foo = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B1: {
%4:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%5:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%6:ptr<storage, i32, read> = access %5, 0u
%7:i32 = load %6
%8:ptr<storage, u32, read_write> = access %4, 1u
%9:u32 = load %8
%10:ptr<storage, f32, read_write> = access %4, 0u
%11:f32 = load %10
%12:i32 = convert %9
%13:i32 = convert %11
%14:i32 = add %7, %12
%15:i32 = add %14, %13
%16:u32 = convert %15
%17:ptr<storage, u32, read_write> = access %4, 1u
store %17, %16
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, Uniform) {
auto* var_a = b.Var("a", ty.ptr<uniform, i32>());
auto* var_b = b.Var("b", ty.ptr<uniform, i32>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Add<i32>(load_a, load_b);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<uniform, i32, read> = var undef @binding_point(1, 2)
%b:ptr<uniform, i32, read> = var undef @binding_point(3, 4)
}
%foo = @fragment func():void {
$B2: {
%4:i32 = load %a
%5:i32 = load %b
%6:i32 = add %4, %5
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<uniform, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<uniform, i32, read> @offset(0), @binding_point(0, 4)
}
%foo = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B1: {
%4:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%5:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%6:ptr<uniform, i32, read> = access %5, 0u
%7:i32 = load %6
%8:ptr<uniform, i32, read> = access %4, 0u
%9:i32 = load %8
%10:i32 = add %7, %9
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, HandleTypes) {
auto* t = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
auto* var_t = b.Var("t", ty.ptr<handle>(t));
auto* var_s = b.Var("s", ty.ptr<handle>(ty.sampler()));
var_t->SetBindingPoint(1, 2);
var_s->SetBindingPoint(3, 4);
mod.root_block->Append(var_t);
mod.root_block->Append(var_s);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_t = b.Load(var_t);
auto* load_s = b.Load(var_s);
b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s, b.Splat<vec2<f32>>(0_f));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%t:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(1, 2)
%s:ptr<handle, sampler, read> = var undef @binding_point(3, 4)
}
%foo = @fragment func():void {
$B2: {
%4:texture_2d<f32> = load %t
%5:sampler = load %s
%6:vec4<f32> = textureSample %4, %5, vec2<f32>(0.0f)
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
t:texture_2d<f32> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
s:sampler @offset(0), @binding_point(0, 4)
}
%foo = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B1: {
%4:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%5:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%6:texture_2d<f32> = access %5, 0u
%7:sampler = access %4, 0u
%8:vec4<f32> = textureSample %6, %7, vec2<f32>(0.0f)
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, HandleTypes_BindingArray) {
auto* texture_type = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
auto* var_t = b.Var("t", ty.ptr<handle>(ty.binding_array(texture_type, 3u)));
auto* var_s = b.Var("s", ty.ptr<handle>(ty.binding_array(ty.sampler(), 3u)));
var_t->SetBindingPoint(1, 2);
var_s->SetBindingPoint(3, 4);
mod.root_block->Append(var_t);
mod.root_block->Append(var_s);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_t = b.Load(b.Access(ty.ptr<handle>(texture_type), var_t, 0_i));
auto* load_s = b.Load(b.Access(ty.ptr<handle>(ty.sampler()), var_s, 0_i));
b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s, b.Splat<vec2<f32>>(0_f));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%t:ptr<handle, binding_array<texture_2d<f32>, 3>, read> = var undef @binding_point(1, 2)
%s:ptr<handle, binding_array<sampler, 3>, read> = var undef @binding_point(3, 4)
}
%foo = @fragment func():void {
$B2: {
%4:ptr<handle, texture_2d<f32>, read> = access %t, 0i
%5:texture_2d<f32> = load %4
%6:ptr<handle, sampler, read> = access %s, 0i
%7:sampler = load %6
%8:vec4<f32> = textureSample %5, %7, vec2<f32>(0.0f)
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
t:binding_array<texture_2d<f32>, 3> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
s:binding_array<sampler, 3> @offset(0), @binding_point(0, 4)
}
%foo = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B1: {
%4:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%5:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%6:binding_array<texture_2d<f32>, 3> = access %5, 0u
%7:texture_2d<f32> = access %6, 0i
%8:binding_array<sampler, 3> = access %4, 0u
%9:sampler = access %8, 0i
%10:vec4<f32> = textureSample %7, %9, vec2<f32>(0.0f)
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, MultipleAddressSpaces) {
auto* var_a = b.Var("a", ty.ptr<uniform, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
auto* var_c = b.Var("c", ty.ptr<private_, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
mod.root_block->Append(var_c);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
auto* load_c = b.Load(var_c);
b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, load_c)));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<uniform, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
%c:ptr<private, i32, read_write> = var undef
}
%foo = @fragment func():void {
$B2: {
%5:i32 = load %a
%6:i32 = load %b
%7:i32 = load %c
%8:i32 = add %6, %7
%9:i32 = add %5, %8
store %b, %9
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<uniform, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
$B1: { # root
%c:ptr<private, i32, read_write> = var undef
}
%foo = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B2: {
%5:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%6:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%7:ptr<uniform, i32, read> = access %6, 0u
%8:i32 = load %7
%9:ptr<storage, i32, read_write> = access %5, 0u
%10:i32 = load %9
%11:i32 = load %c
%12:i32 = add %10, %11
%13:i32 = add %8, %12
%14:ptr<storage, i32, read_write> = access %5, 0u
store %14, %13
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, EntryPointHasExistingParameters) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
auto* param = b.FunctionParam<i32>("param");
param->SetLocation(1_u);
param->SetInterpolation(core::Interpolation{core::InterpolationType::kFlat});
func->SetParams({param});
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, param)));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
}
%foo = @fragment func(%param:i32 [@location(1), @interpolate(flat)]):void {
$B2: {
%5:i32 = load %a
%6:i32 = load %b
%7:i32 = add %6, %param
%8:i32 = add %5, %7
store %b, %8
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
%foo = @fragment func(%param:i32 [@location(1), @interpolate(flat)], %tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B1: {
%5:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%6:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%7:ptr<storage, i32, read> = access %6, 0u
%8:i32 = load %7
%9:ptr<storage, i32, read_write> = access %5, 0u
%10:i32 = load %9
%11:i32 = add %10, %param
%12:i32 = add %8, %11
%13:ptr<storage, i32, read_write> = access %5, 0u
store %13, %12
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionThatUsesVars_NoArgs) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, load_b));
b.Return(foo);
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
b.Call(foo);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
}
%foo = func():void {
$B2: {
%4:i32 = load %a
%5:i32 = load %b
%6:i32 = add %4, %5
store %b, %6
ret
}
}
%main = @fragment func():void {
$B3: {
%8:void = call %foo
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
%foo = func(%a:ptr<storage, i32, read>, %b:ptr<storage, i32, read_write>):void {
$B1: {
%4:i32 = load %a
%5:i32 = load %b
%6:i32 = add %4, %5
store %b, %6
ret
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B2: {
%10:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%11:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%12:ptr<storage, i32, read> = access %11, 0u
%13:ptr<storage, i32, read_write> = access %10, 0u
%14:void = call %foo, %12, %13
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionThatUsesVars_WithExistingParameters) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* foo = b.Function("foo", ty.void_());
auto* param = b.FunctionParam<i32>("param");
foo->SetParams({param});
b.Append(foo->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, param)));
b.Return(foo);
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
b.Call(foo, 42_i);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
}
%foo = func(%param:i32):void {
$B2: {
%5:i32 = load %a
%6:i32 = load %b
%7:i32 = add %6, %param
%8:i32 = add %5, %7
store %b, %8
ret
}
}
%main = @fragment func():void {
$B3: {
%10:void = call %foo, 42i
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
%foo = func(%param:i32, %a:ptr<storage, i32, read>, %b:ptr<storage, i32, read_write>):void {
$B1: {
%5:i32 = load %a
%6:i32 = load %b
%7:i32 = add %6, %param
%8:i32 = add %5, %7
store %b, %8
ret
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B2: {
%12:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%13:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%14:ptr<storage, i32, read> = access %13, 0u
%15:ptr<storage, i32, read_write> = access %12, 0u
%16:void = call %foo, 42i, %14, %15
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionThatUsesVars_HandleTypes) {
auto* t = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
auto* var_t = b.Var("t", ty.ptr<handle>(t));
auto* var_s = b.Var("s", ty.ptr<handle>(ty.sampler()));
var_t->SetBindingPoint(1, 2);
var_s->SetBindingPoint(3, 4);
mod.root_block->Append(var_t);
mod.root_block->Append(var_s);
auto* foo = b.Function("foo", ty.vec4<f32>());
auto* param = b.FunctionParam<i32>("param");
foo->SetParams({param});
b.Append(foo->Block(), [&] {
auto* load_t = b.Load(var_t);
auto* load_s = b.Load(var_s);
auto* result = b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s,
b.Splat<vec2<f32>>(0_f));
b.Return(foo, result);
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
b.Call(foo, 42_i);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%t:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(1, 2)
%s:ptr<handle, sampler, read> = var undef @binding_point(3, 4)
}
%foo = func(%param:i32):vec4<f32> {
$B2: {
%5:texture_2d<f32> = load %t
%6:sampler = load %s
%7:vec4<f32> = textureSample %5, %6, vec2<f32>(0.0f)
ret %7
}
}
%main = @fragment func():void {
$B3: {
%9:vec4<f32> = call %foo, 42i
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
t:texture_2d<f32> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
s:sampler @offset(0), @binding_point(0, 4)
}
%foo = func(%param:i32, %t:texture_2d<f32>, %s:sampler):vec4<f32> {
$B1: {
%5:vec4<f32> = textureSample %t, %s, vec2<f32>(0.0f)
ret %5
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B2: {
%9:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%10:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%11:texture_2d<f32> = access %10, 0u
%12:sampler = access %9, 0u
%13:vec4<f32> = call %foo, 42i, %11, %12
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionThatUsesVars_HandleTypes_BindingArray) {
auto* texture_type = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
auto* var_t = b.Var("t", ty.ptr<handle>(ty.binding_array(texture_type, 3u)));
auto* var_s = b.Var("s", ty.ptr<handle>(ty.binding_array(ty.sampler(), 3u)));
var_t->SetBindingPoint(1, 2);
var_s->SetBindingPoint(3, 4);
mod.root_block->Append(var_t);
mod.root_block->Append(var_s);
auto* foo = b.Function("foo", ty.vec4<f32>());
auto* param = b.FunctionParam<i32>("param");
foo->SetParams({param});
b.Append(foo->Block(), [&] {
auto* load_t = b.Load(b.Access(ty.ptr<handle>(texture_type), var_t, 0_i));
auto* load_s = b.Load(b.Access(ty.ptr<handle>(ty.sampler()), var_s, 0_i));
auto* result = b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s,
b.Splat<vec2<f32>>(0_f));
b.Return(foo, result);
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
b.Call(foo, 42_i);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%t:ptr<handle, binding_array<texture_2d<f32>, 3>, read> = var undef @binding_point(1, 2)
%s:ptr<handle, binding_array<sampler, 3>, read> = var undef @binding_point(3, 4)
}
%foo = func(%param:i32):vec4<f32> {
$B2: {
%5:ptr<handle, texture_2d<f32>, read> = access %t, 0i
%6:texture_2d<f32> = load %5
%7:ptr<handle, sampler, read> = access %s, 0i
%8:sampler = load %7
%9:vec4<f32> = textureSample %6, %8, vec2<f32>(0.0f)
ret %9
}
}
%main = @fragment func():void {
$B3: {
%11:vec4<f32> = call %foo, 42i
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
t:binding_array<texture_2d<f32>, 3> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
s:binding_array<sampler, 3> @offset(0), @binding_point(0, 4)
}
%foo = func(%param:i32, %t:binding_array<texture_2d<f32>, 3>, %s:binding_array<sampler, 3>):vec4<f32> {
$B1: {
%5:texture_2d<f32> = access %t, 0i
%6:sampler = access %s, 0i
%7:vec4<f32> = textureSample %5, %6, vec2<f32>(0.0f)
ret %7
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B2: {
%11:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%12:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%13:binding_array<texture_2d<f32>, 3> = access %12, 0u
%14:binding_array<sampler, 3> = access %11, 0u
%15:vec4<f32> = call %foo, 42i, %13, %14
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionThatUsesVars_OutOfOrder) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, load_b));
b.Return(foo);
});
b.Append(func->Block(), [&] {
b.Call(foo);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
}
%main = @fragment func():void {
$B2: {
%4:void = call %foo
ret
}
}
%foo = func():void {
$B3: {
%6:i32 = load %a
%7:i32 = load %b
%8:i32 = add %6, %7
store %b, %8
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B1: {
%4:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%5:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%6:ptr<storage, i32, read> = access %5, 0u
%7:ptr<storage, i32, read_write> = access %4, 0u
%8:void = call %foo, %6, %7
ret
}
}
%foo = func(%a:ptr<storage, i32, read>, %b:ptr<storage, i32, read_write>):void {
$B2: {
%12:i32 = load %a
%13:i32 = load %b
%14:i32 = add %12, %13
store %b, %14
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
// Test that we do not add the variables to functions that do not need it.
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionThatDoesNotUseVars) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* foo = b.Function("foo", ty.i32());
b.Append(foo->Block(), [&] { //
b.Return(foo, 42_i);
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, b.Call(foo))));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
}
%foo = func():i32 {
$B2: {
ret 42i
}
}
%main = @fragment func():void {
$B3: {
%5:i32 = load %a
%6:i32 = load %b
%7:i32 = call %foo
%8:i32 = add %6, %7
%9:i32 = add %5, %8
store %b, %9
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
%foo = func():i32 {
$B1: {
ret 42i
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B2: {
%5:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%6:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%7:ptr<storage, i32, read> = access %6, 0u
%8:i32 = load %7
%9:ptr<storage, i32, read_write> = access %5, 0u
%10:i32 = load %9
%11:i32 = call %foo
%12:i32 = add %10, %11
%13:i32 = add %8, %12
%14:ptr<storage, i32, read_write> = access %5, 0u
store %14, %13
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
// Test that we *do* add the variables to functions that only have transitive uses.
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionWithOnlyTransitiveUses) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* bar = b.Function("bar", ty.i32());
b.Append(bar->Block(), [&] { //
b.Return(bar, b.Load(var_a));
});
auto* foo = b.Function("foo", ty.i32());
b.Append(foo->Block(), [&] { //
b.Return(foo, b.Call(bar));
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, b.Call(foo))));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
}
%bar = func():i32 {
$B2: {
%4:i32 = load %a
ret %4
}
}
%foo = func():i32 {
$B3: {
%6:i32 = call %bar
ret %6
}
}
%main = @fragment func():void {
$B4: {
%8:i32 = load %a
%9:i32 = load %b
%10:i32 = call %foo
%11:i32 = add %9, %10
%12:i32 = add %8, %11
store %b, %12
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
%bar = func(%a:ptr<storage, i32, read>):i32 {
$B1: {
%3:i32 = load %a
ret %3
}
}
%foo = func(%a_1:ptr<storage, i32, read>):i32 { # %a_1: 'a'
$B2: {
%6:i32 = call %bar, %a_1
ret %6
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B3: {
%10:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%11:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%12:ptr<storage, i32, read> = access %11, 0u
%13:i32 = load %12
%14:ptr<storage, i32, read_write> = access %10, 0u
%15:i32 = load %14
%16:ptr<storage, i32, read> = access %11, 0u
%17:i32 = call %foo, %16
%18:i32 = add %15, %17
%19:i32 = add %13, %18
%20:ptr<storage, i32, read_write> = access %10, 0u
store %20, %19
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
// Test that we *do* add the structure to functions that only have transitive uses, where that
// function is declared first.
TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionWithOnlyTransitiveUses_OutOfOrder) {
auto* var_a = b.Var("a", ty.ptr<storage, i32, core::Access::kRead>());
auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
auto* foo = b.Function("foo", ty.i32());
auto* bar = b.Function("bar", ty.i32());
b.Append(bar->Block(), [&] { //
b.Return(bar, b.Load(var_a));
});
b.Append(foo->Block(), [&] { //
b.Return(foo, b.Call(bar));
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, b.Call(foo))));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%a:ptr<storage, i32, read> = var undef @binding_point(1, 2)
%b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
}
%foo = func():i32 {
$B2: {
%4:i32 = call %bar
ret %4
}
}
%bar = func():i32 {
$B3: {
%6:i32 = load %a
ret %6
}
}
%main = @fragment func():void {
$B4: {
%8:i32 = load %a
%9:i32 = load %b
%10:i32 = call %foo
%11:i32 = add %9, %10
%12:i32 = add %8, %11
store %b, %12
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
a:ptr<storage, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
b:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
%foo = func(%a:ptr<storage, i32, read>):i32 {
$B1: {
%3:i32 = call %bar, %a
ret %3
}
}
%bar = func(%a_1:ptr<storage, i32, read>):i32 { # %a_1: 'a'
$B2: {
%6:i32 = load %a_1
ret %6
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B3: {
%10:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%11:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%12:ptr<storage, i32, read> = access %11, 0u
%13:i32 = load %12
%14:ptr<storage, i32, read_write> = access %10, 0u
%15:i32 = load %14
%16:ptr<storage, i32, read> = access %11, 0u
%17:i32 = call %foo, %16
%18:i32 = add %15, %17
%19:i32 = add %13, %18
%20:ptr<storage, i32, read_write> = access %10, 0u
store %20, %19
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_ArgumentBuffersTest, VarsWithNoNames) {
auto* var_a = b.Var(ty.ptr<uniform, i32, core::Access::kRead>());
auto* var_b = b.Var(ty.ptr<storage, i32, core::Access::kReadWrite>());
auto* var_c = b.Var(ty.ptr<private_, i32, core::Access::kReadWrite>());
var_a->SetBindingPoint(1, 2);
var_b->SetBindingPoint(3, 4);
mod.root_block->Append(var_a);
mod.root_block->Append(var_b);
mod.root_block->Append(var_c);
auto* foo = b.Function("foo", ty.i32());
auto* bar = b.Function("bar", ty.i32());
b.Append(bar->Block(), [&] { //
b.Return(bar, b.Load(var_a));
});
b.Append(foo->Block(), [&] { //
b.Return(foo, b.Call(bar));
});
auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
auto* load_a = b.Load(var_a);
auto* load_b = b.Load(var_b);
b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, b.Call(foo))));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%1:ptr<uniform, i32, read> = var undef @binding_point(1, 2)
%2:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
%3:ptr<private, i32, read_write> = var undef
}
%foo = func():i32 {
$B2: {
%5:i32 = call %bar
ret %5
}
}
%bar = func():i32 {
$B3: {
%7:i32 = load %1
ret %7
}
}
%main = @fragment func():void {
$B4: {
%9:i32 = load %1
%10:i32 = load %2
%11:i32 = call %foo
%12:i32 = add %10, %11
%13:i32 = add %9, %12
store %2, %13
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
tint_arg_buffer_struct_1 = struct @align(1), @core.explicit_layout {
tint_symbol:ptr<uniform, i32, read> @offset(0), @binding_point(0, 2)
}
tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
tint_symbol_1:ptr<storage, i32, read_write> @offset(0), @binding_point(0, 4)
}
$B1: { # root
%1:ptr<private, i32, read_write> = var undef
}
%foo = func(%3:ptr<uniform, i32, read>):i32 {
$B2: {
%4:i32 = call %bar, %3
ret %4
}
}
%bar = func(%6:ptr<uniform, i32, read>):i32 {
$B3: {
%7:i32 = load %6
ret %7
}
}
%main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(1, 0)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(3, 0)]):void {
$B4: {
%11:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
%12:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
%13:ptr<uniform, i32, read> = access %12, 0u
%14:i32 = load %13
%15:ptr<storage, i32, read_write> = access %11, 0u
%16:i32 = load %15
%17:ptr<uniform, i32, read> = access %12, 0u
%18:i32 = call %foo, %17
%19:i32 = add %16, %18
%20:i32 = add %14, %19
%21:ptr<storage, i32, read_write> = access %11, 0u
store %21, %20
ret
}
}
)";
Run(ArgumentBuffers);
EXPECT_EQ(expect, str());
}
} // namespace
} // namespace tint::msl::writer::raise