blob: ad5878eee28deb66b0a0a09852532bca0487dd5d [file]
// Copyright 2026 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/decompose_buffer.h"
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/core/ir/transform/helper_test.h"
namespace tint::msl::writer::raise {
namespace {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
class MslWriter_DecomposeBufferTest : public core::ir::transform::TransformTest {
void SetUp() override {
capabilities.Add(core::ir::Capability::kAllow8BitIntegers);
mod.properties.Add(core::ir::Property::kAllowMslEntryPointInterface);
}
};
TEST_F(MslWriter_DecomposeBufferTest, BufferView_u32_ZeroOffset) {
auto* gv = b.Var("gv", ty.ptr(storage, ty.unsized_buffer()));
gv->SetBindingPoint(0, 0);
mod.root_block->Append(gv);
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
auto* view = b.CallExplicit(ty.ptr(storage, ty.u32()), core::BuiltinFn::kBufferView,
Vector<core::ir::TemplateParameter, 1>{ty.u32()}, gv, 0_u);
b.Load(view);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%gv:ptr<storage, buffer, read_write> = var undef @binding_point(0, 0)
}
%foo = func():void {
$B2: {
%3:ptr<storage, u32, read_write> = bufferView<u32> %gv, 0u
%4:u32 = load %3
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%gv:ptr<storage, array<u8>, read_write> = var undef @binding_point(0, 0)
}
%foo = func():void {
$B2: {
%3:ptr<storage, u32, read_write> = msl.pointer_offset<u32> %gv, 0u
%4:u32 = load %3
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, BufferArrayView_ArrayU32) {
auto* gv = b.Var("gv", ty.ptr(storage, ty.unsized_buffer()));
gv->SetBindingPoint(0, 0);
mod.root_block->Append(gv);
auto* func = b.Function("foo", ty.void_());
auto* offset = b.FunctionParam("offset", ty.i32());
auto* size = b.FunctionParam("size", ty.i32());
func->SetParams({offset, size});
b.Append(func->Block(), [&] {
auto* view = b.CallExplicit(
ty.ptr(storage, ty.runtime_array(ty.u32())), core::BuiltinFn::kBufferArrayView,
Vector<core::ir::TemplateParameter, 1>{ty.runtime_array(ty.u32())}, gv, offset, size);
b.Load(b.Access(ty.ptr(storage, ty.u32()), view, 4_u));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%gv:ptr<storage, buffer, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%offset:i32, %size:i32):void {
$B2: {
%5:ptr<storage, array<u32>, read_write> = bufferArrayView<array<u32>> %gv, %offset, %size
%6:ptr<storage, u32, read_write> = access %5, 4u
%7:u32 = load %6
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%gv:ptr<storage, array<u8>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%offset:i32, %size:i32):void {
$B2: {
%5:u32 = bitcast<u32> %offset
%6:ptr<storage, array<u32>, read_write> = msl.pointer_offset<array<u32>> %gv, %5
%7:ptr<storage, u32, read_write> = access %6, 4u
%8:u32 = load %7
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, MultipleCallsFromVariable) {
auto* gv = b.Var("gv", ty.ptr(storage, ty.unsized_buffer()));
gv->SetBindingPoint(0, 0);
mod.root_block->Append(gv);
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
auto* c1 = b.CallExplicit(ty.ptr(storage, ty.u32()), core::BuiltinFn::kBufferView,
Vector<core::ir::TemplateParameter, 1>{ty.u32()}, gv, 0_u);
b.Load(c1);
auto* c2 = b.CallExplicit(
ty.ptr(storage, ty.runtime_array(ty.u32())), core::BuiltinFn::kBufferArrayView,
Vector<core::ir::TemplateParameter, 1>{ty.runtime_array(ty.u32())}, gv, 16_u, 32_u);
b.Load(b.Access(ty.ptr(storage, ty.u32()), c2, 1_u));
b.Return(foo);
});
auto* src = R"(
$B1: { # root
%gv:ptr<storage, buffer, read_write> = var undef @binding_point(0, 0)
}
%foo = func():void {
$B2: {
%3:ptr<storage, u32, read_write> = bufferView<u32> %gv, 0u
%4:u32 = load %3
%5:ptr<storage, array<u32>, read_write> = bufferArrayView<array<u32>> %gv, 16u, 32u
%6:ptr<storage, u32, read_write> = access %5, 1u
%7:u32 = load %6
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%gv:ptr<storage, array<u8>, read_write> = var undef @binding_point(0, 0)
}
%foo = func():void {
$B2: {
%3:ptr<storage, u32, read_write> = msl.pointer_offset<u32> %gv, 0u
%4:u32 = load %3
%5:ptr<storage, array<u32>, read_write> = msl.pointer_offset<array<u32>> %gv, 16u
%6:ptr<storage, u32, read_write> = access %5, 1u
%7:u32 = load %6
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, UnifyParameters) {
auto* large = b.Var("large", ty.ptr(storage, ty.buffer(1024)));
large->SetBindingPoint(0, 0);
mod.root_block->Append(large);
auto* small = b.Var("small", ty.ptr(workgroup, ty.buffer(512)));
mod.root_block->Append(small);
auto* baz = b.Function("baz", ty.void_());
auto* baz_p1 = b.FunctionParam("baz_p1", ty.ptr(storage, ty.unsized_buffer()));
auto* baz_p2 = b.FunctionParam("baz_p2", ty.ptr(workgroup, ty.unsized_buffer()));
baz->SetParams({baz_p1, baz_p2});
b.Append(baz->Block(), [&] { b.Return(baz); });
auto* bar = b.Function("bar", ty.void_());
auto* bar_p1 = b.FunctionParam("bar_p1", ty.ptr(storage, ty.buffer(512)));
auto* bar_p2 = b.FunctionParam("bar_p2", ty.ptr(workgroup, ty.buffer(256)));
bar->SetParams({bar_p1, bar_p2});
b.Append(bar->Block(), [&] {
b.Call(ty.void_(), baz, bar_p1, bar_p2);
b.Return(bar);
});
auto* foobar = b.Function("foobar", ty.void_());
auto* foobar_p1 = b.FunctionParam("foobar_p1", ty.ptr(storage, ty.buffer(512)));
auto* foobar_p2 = b.FunctionParam("foobar_p2", ty.ptr(workgroup, ty.buffer(256)));
foobar->SetParams({foobar_p1, foobar_p2});
b.Append(foobar->Block(), [&] {
b.Call(ty.void_(), bar, foobar_p1, foobar_p2);
b.Return(foobar);
});
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
b.Call(ty.void_(), foobar, large, small);
b.Return(foo);
});
auto* src = R"(
$B1: { # root
%large:ptr<storage, buffer<1024>, read_write> = var undef @binding_point(0, 0)
%small:ptr<workgroup, buffer<512>, read_write> = var undef
}
%baz = func(%baz_p1:ptr<storage, buffer, read_write>, %baz_p2:ptr<workgroup, buffer, read_write>):void {
$B2: {
ret
}
}
%bar = func(%bar_p1:ptr<storage, buffer<512>, read_write>, %bar_p2:ptr<workgroup, buffer<256>, read_write>):void {
$B3: {
%9:void = call %baz, %bar_p1, %bar_p2
ret
}
}
%foobar = func(%foobar_p1:ptr<storage, buffer<512>, read_write>, %foobar_p2:ptr<workgroup, buffer<256>, read_write>):void {
$B4: {
%13:void = call %bar, %foobar_p1, %foobar_p2
ret
}
}
%foo = func():void {
$B5: {
%15:void = call %foobar, %large, %small
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%large:ptr<storage, array<u8, 1024>, read_write> = var undef @binding_point(0, 0)
%small:ptr<workgroup, array<u8, 512>, read_write> = var undef
}
%baz = func(%baz_p1:ptr<storage, array<u8>, read_write>, %baz_p2:ptr<workgroup, array<u8>, read_write>):void {
$B2: {
ret
}
}
%bar = func(%bar_p1:ptr<storage, array<u8, 512>, read_write>, %bar_p2:ptr<workgroup, array<u8, 256>, read_write>):void {
$B3: {
%9:ptr<storage, array<u8>, read_write> = msl.pointer_offset<array<u8>> %bar_p1, 0u
%10:ptr<workgroup, array<u8>, read_write> = msl.pointer_offset<array<u8>> %bar_p2, 0u
%11:void = call %baz, %9, %10
ret
}
}
%foobar = func(%foobar_p1:ptr<storage, array<u8, 512>, read_write>, %foobar_p2:ptr<workgroup, array<u8, 256>, read_write>):void {
$B4: {
%15:void = call %bar, %foobar_p1, %foobar_p2
ret
}
}
%foo = func():void {
$B5: {
%17:ptr<storage, array<u8, 512>, read_write> = msl.pointer_offset<array<u8, 512>> %large, 0u
%18:ptr<workgroup, array<u8, 256>, read_write> = msl.pointer_offset<array<u8, 256>> %small, 0u
%19:void = call %foobar, %17, %18
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, UnifyParameters_Lets) {
auto* large = b.Var("large", ty.ptr(storage, ty.buffer(1024)));
large->SetBindingPoint(0, 0);
mod.root_block->Append(large);
auto* small = b.Var("small", ty.ptr(workgroup, ty.buffer(512)));
mod.root_block->Append(small);
auto* baz = b.Function("baz", ty.void_());
auto* baz_p1 = b.FunctionParam("baz_p1", ty.ptr(storage, ty.unsized_buffer()));
auto* baz_p2 = b.FunctionParam("baz_p2", ty.ptr(workgroup, ty.unsized_buffer()));
baz->SetParams({baz_p1, baz_p2});
b.Append(baz->Block(), [&] { b.Return(baz); });
auto* bar = b.Function("bar", ty.void_());
auto* bar_p1 = b.FunctionParam("bar_p1", ty.ptr(storage, ty.buffer(512)));
auto* bar_p2 = b.FunctionParam("bar_p2", ty.ptr(workgroup, ty.buffer(256)));
bar->SetParams({bar_p1, bar_p2});
b.Append(bar->Block(), [&] {
auto* l1 = b.Let("l1", bar_p1);
auto* l2 = b.Let("l2", bar_p2);
b.Call(ty.void_(), baz, l1, l2);
b.Return(bar);
});
auto* foobar = b.Function("foobar", ty.void_());
auto* foobar_p1 = b.FunctionParam("foobar_p1", ty.ptr(storage, ty.buffer(512)));
auto* foobar_p2 = b.FunctionParam("foobar_p2", ty.ptr(workgroup, ty.buffer(256)));
foobar->SetParams({foobar_p1, foobar_p2});
b.Append(foobar->Block(), [&] {
auto* l3 = b.Let("l3", foobar_p1);
b.Call(ty.void_(), bar, l3, foobar_p2);
b.Return(foobar);
});
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
b.Call(ty.void_(), foobar, large, small);
b.Return(foo);
});
auto* src = R"(
$B1: { # root
%large:ptr<storage, buffer<1024>, read_write> = var undef @binding_point(0, 0)
%small:ptr<workgroup, buffer<512>, read_write> = var undef
}
%baz = func(%baz_p1:ptr<storage, buffer, read_write>, %baz_p2:ptr<workgroup, buffer, read_write>):void {
$B2: {
ret
}
}
%bar = func(%bar_p1:ptr<storage, buffer<512>, read_write>, %bar_p2:ptr<workgroup, buffer<256>, read_write>):void {
$B3: {
%l1:ptr<storage, buffer<512>, read_write> = let %bar_p1
%l2:ptr<workgroup, buffer<256>, read_write> = let %bar_p2
%11:void = call %baz, %l1, %l2
ret
}
}
%foobar = func(%foobar_p1:ptr<storage, buffer<512>, read_write>, %foobar_p2:ptr<workgroup, buffer<256>, read_write>):void {
$B4: {
%l3:ptr<storage, buffer<512>, read_write> = let %foobar_p1
%16:void = call %bar, %l3, %foobar_p2
ret
}
}
%foo = func():void {
$B5: {
%18:void = call %foobar, %large, %small
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%large:ptr<storage, array<u8, 1024>, read_write> = var undef @binding_point(0, 0)
%small:ptr<workgroup, array<u8, 512>, read_write> = var undef
}
%baz = func(%baz_p1:ptr<storage, array<u8>, read_write>, %baz_p2:ptr<workgroup, array<u8>, read_write>):void {
$B2: {
ret
}
}
%bar = func(%bar_p1:ptr<storage, array<u8, 512>, read_write>, %bar_p2:ptr<workgroup, array<u8, 256>, read_write>):void {
$B3: {
%l1:ptr<storage, array<u8, 512>, read_write> = let %bar_p1
%l2:ptr<workgroup, array<u8, 256>, read_write> = let %bar_p2
%11:ptr<storage, array<u8>, read_write> = msl.pointer_offset<array<u8>> %l1, 0u
%12:ptr<workgroup, array<u8>, read_write> = msl.pointer_offset<array<u8>> %l2, 0u
%13:void = call %baz, %11, %12
ret
}
}
%foobar = func(%foobar_p1:ptr<storage, array<u8, 512>, read_write>, %foobar_p2:ptr<workgroup, array<u8, 256>, read_write>):void {
$B4: {
%l3:ptr<storage, array<u8, 512>, read_write> = let %foobar_p1
%18:void = call %bar, %l3, %foobar_p2
ret
}
}
%foo = func():void {
$B5: {
%20:ptr<storage, array<u8, 512>, read_write> = msl.pointer_offset<array<u8, 512>> %large, 0u
%21:ptr<workgroup, array<u8, 256>, read_write> = msl.pointer_offset<array<u8, 256>> %small, 0u
%22:void = call %foobar, %20, %21
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, UnifyParameters_Lets_Unused) {
auto* baz = b.Function("baz", ty.void_());
auto* baz_p1 = b.FunctionParam("baz_p1", ty.ptr(storage, ty.unsized_buffer()));
auto* baz_p2 = b.FunctionParam("baz_p2", ty.ptr(workgroup, ty.unsized_buffer()));
baz->SetParams({baz_p1, baz_p2});
b.Append(baz->Block(), [&] {
b.Let("l1", baz_p1);
b.Let("l2", baz_p2);
b.Return(baz);
});
auto* src = R"(
%baz = func(%baz_p1:ptr<storage, buffer, read_write>, %baz_p2:ptr<workgroup, buffer, read_write>):void {
$B1: {
%l1:ptr<storage, buffer, read_write> = let %baz_p1
%l2:ptr<workgroup, buffer, read_write> = let %baz_p2
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%baz = func(%baz_p1:ptr<storage, array<u8>, read_write>, %baz_p2:ptr<workgroup, array<u8>, read_write>):void {
$B1: {
%l1:ptr<storage, array<u8>, read_write> = let %baz_p1
%l2:ptr<workgroup, array<u8>, read_write> = let %baz_p2
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, UnifyParameters_WithUses) {
auto* large = b.Var("large", ty.ptr(storage, ty.buffer(1024)));
large->SetBindingPoint(0, 0);
mod.root_block->Append(large);
auto* small = b.Var("small", ty.ptr(workgroup, ty.buffer(512)));
mod.root_block->Append(small);
auto* baz = b.Function("baz", ty.void_());
auto* baz_p1 = b.FunctionParam("baz_p1", ty.ptr(storage, ty.array(ty.u32(), 64)));
auto* baz_p2 = b.FunctionParam("baz_p2", ty.ptr(workgroup, ty.runtime_array(ty.u32())));
baz->SetParams({baz_p1, baz_p2});
b.Append(baz->Block(), [&] {
b.Let("let", baz_p2);
b.Return(baz);
});
auto* bar = b.Function("bar", ty.void_());
auto* bar_p1 = b.FunctionParam("bar_p1", ty.ptr(storage, ty.buffer(512)));
auto* bar_p2 = b.FunctionParam("bar_p2", ty.ptr(workgroup, ty.buffer(256)));
bar->SetParams({bar_p1, bar_p2});
b.Append(bar->Block(), [&] {
auto* v1 = b.CallExplicit(
ty.ptr(storage, ty.array(ty.u32(), 64)), core::BuiltinFn::kBufferView,
Vector<core::ir::TemplateParameter, 1>{ty.array(ty.u32(), 64)}, bar_p1, 32_u);
auto* v2 = b.CallExplicit(
ty.ptr(workgroup, ty.runtime_array(ty.u32())), core::BuiltinFn::kBufferArrayView,
Vector<core::ir::TemplateParameter, 1>{ty.runtime_array(ty.u32())}, bar_p2, 4_u, 12_u);
b.Call(ty.void_(), baz, v1, v2);
b.Return(bar);
});
auto* foobar = b.Function("foobar", ty.void_());
auto* foobar_p1 = b.FunctionParam("foobar_p1", ty.ptr(storage, ty.buffer(512)));
auto* foobar_p2 = b.FunctionParam("foobar_p2", ty.ptr(workgroup, ty.buffer(256)));
foobar->SetParams({foobar_p1, foobar_p2});
b.Append(foobar->Block(), [&] {
b.Call(ty.void_(), bar, foobar_p1, foobar_p2);
b.Return(foobar);
});
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
b.Call(ty.void_(), foobar, large, small);
b.Return(foo);
});
auto* src = R"(
$B1: { # root
%large:ptr<storage, buffer<1024>, read_write> = var undef @binding_point(0, 0)
%small:ptr<workgroup, buffer<512>, read_write> = var undef
}
%baz = func(%baz_p1:ptr<storage, array<u32, 64>, read_write>, %baz_p2:ptr<workgroup, array<u32>, read_write>):void {
$B2: {
%let:ptr<workgroup, array<u32>, read_write> = let %baz_p2
ret
}
}
%bar = func(%bar_p1:ptr<storage, buffer<512>, read_write>, %bar_p2:ptr<workgroup, buffer<256>, read_write>):void {
$B3: {
%10:ptr<storage, array<u32, 64>, read_write> = bufferView<array<u32, 64>> %bar_p1, 32u
%11:ptr<workgroup, array<u32>, read_write> = bufferArrayView<array<u32>> %bar_p2, 4u, 12u
%12:void = call %baz, %10, %11
ret
}
}
%foobar = func(%foobar_p1:ptr<storage, buffer<512>, read_write>, %foobar_p2:ptr<workgroup, buffer<256>, read_write>):void {
$B4: {
%16:void = call %bar, %foobar_p1, %foobar_p2
ret
}
}
%foo = func():void {
$B5: {
%18:void = call %foobar, %large, %small
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%large:ptr<storage, array<u8, 1024>, read_write> = var undef @binding_point(0, 0)
%small:ptr<workgroup, array<u8, 512>, read_write> = var undef
}
%baz = func(%baz_p1:ptr<storage, array<u32, 64>, read_write>, %baz_p2:ptr<workgroup, array<u32>, read_write>):void {
$B2: {
%let:ptr<workgroup, array<u32>, read_write> = let %baz_p2
ret
}
}
%bar = func(%bar_p1:ptr<storage, array<u8, 512>, read_write>, %bar_p2:ptr<workgroup, array<u8, 256>, read_write>):void {
$B3: {
%10:ptr<storage, array<u32, 64>, read_write> = msl.pointer_offset<array<u32, 64>> %bar_p1, 32u
%11:ptr<workgroup, array<u32>, read_write> = msl.pointer_offset<array<u32>> %bar_p2, 4u
%12:void = call %baz, %10, %11
ret
}
}
%foobar = func(%foobar_p1:ptr<storage, array<u8, 512>, read_write>, %foobar_p2:ptr<workgroup, array<u8, 256>, read_write>):void {
$B4: {
%16:void = call %bar, %foobar_p1, %foobar_p2
ret
}
}
%foo = func():void {
$B5: {
%18:ptr<storage, array<u8, 512>, read_write> = msl.pointer_offset<array<u8, 512>> %large, 0u
%19:ptr<workgroup, array<u8, 256>, read_write> = msl.pointer_offset<array<u8, 256>> %small, 0u
%20:void = call %foobar, %18, %19
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, FunctionCall_MultiCallSite_SameCaller) {
auto* gv1 = b.Var("gv1", ty.ptr(storage, ty.unsized_buffer()));
gv1->SetBindingPoint(0, 0);
mod.root_block->Append(gv1);
auto* gv2 = b.Var("gv2", ty.ptr(storage, ty.buffer(128)));
gv2->SetBindingPoint(0, 0);
mod.root_block->Append(gv2);
auto* gv3 = b.Var("gv3", ty.ptr(storage, ty.runtime_array(ty.u32())));
gv3->SetBindingPoint(0, 0);
mod.root_block->Append(gv3);
auto* bar = b.Function("bar", ty.void_());
auto* param = b.FunctionParam("param", ty.ptr(storage, ty.runtime_array(ty.u32())));
bar->SetParams({param});
b.Append(bar->Block(), [&] {
b.Let("let", param);
b.Return(bar);
});
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
auto* v1 = b.CallExplicit(
ty.ptr(storage, ty.runtime_array(ty.u32())), core::BuiltinFn::kBufferArrayView,
Vector<core::ir::TemplateParameter, 1>{ty.runtime_array(ty.u32())}, gv1, 16_u, 64_u);
b.Call(ty.void_(), bar, v1);
auto* v2 = b.CallExplicit(
ty.ptr(storage, ty.runtime_array(ty.u32())), core::BuiltinFn::kBufferView,
Vector<core::ir::TemplateParameter, 1>{ty.runtime_array(ty.u32())}, gv2, 32_u);
b.Call(ty.void_(), bar, v2);
b.Call(ty.void_(), bar, gv3);
b.Return(foo);
});
auto* src = R"(
$B1: { # root
%gv1:ptr<storage, buffer, read_write> = var undef @binding_point(0, 0)
%gv2:ptr<storage, buffer<128>, read_write> = var undef @binding_point(0, 0)
%gv3:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%bar = func(%param:ptr<storage, array<u32>, read_write>):void {
$B2: {
%let:ptr<storage, array<u32>, read_write> = let %param
ret
}
}
%foo = func():void {
$B3: {
%8:ptr<storage, array<u32>, read_write> = bufferArrayView<array<u32>> %gv1, 16u, 64u
%9:void = call %bar, %8
%10:ptr<storage, array<u32>, read_write> = bufferView<array<u32>> %gv2, 32u
%11:void = call %bar, %10
%12:void = call %bar, %gv3
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%gv1:ptr<storage, array<u8>, read_write> = var undef @binding_point(0, 0)
%gv2:ptr<storage, array<u8, 128>, read_write> = var undef @binding_point(0, 0)
%gv3:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%bar = func(%param:ptr<storage, array<u32>, read_write>):void {
$B2: {
%let:ptr<storage, array<u32>, read_write> = let %param
ret
}
}
%foo = func():void {
$B3: {
%8:ptr<storage, array<u32>, read_write> = msl.pointer_offset<array<u32>> %gv1, 16u
%9:void = call %bar, %8
%10:ptr<storage, array<u32>, read_write> = msl.pointer_offset<array<u32>> %gv2, 32u
%11:void = call %bar, %10
%12:void = call %bar, %gv3
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
TEST_F(MslWriter_DecomposeBufferTest, FunctionCall_MultiCallSite_SameCaller_WithLets) {
auto* gv1 = b.Var("gv1", ty.ptr(storage, ty.unsized_buffer()));
gv1->SetBindingPoint(0, 0);
mod.root_block->Append(gv1);
auto* gv2 = b.Var("gv2", ty.ptr(storage, ty.buffer(128)));
gv2->SetBindingPoint(0, 0);
mod.root_block->Append(gv2);
auto* gv3 = b.Var("gv3", ty.ptr(storage, ty.runtime_array(ty.u32())));
gv3->SetBindingPoint(0, 0);
mod.root_block->Append(gv3);
auto* bar = b.Function("bar", ty.void_());
auto* param = b.FunctionParam("param", ty.ptr(storage, ty.runtime_array(ty.u32())));
bar->SetParams({param});
b.Append(bar->Block(), [&] {
b.Let("let", param);
b.Return(bar);
});
auto* foo = b.Function("foo", ty.void_());
b.Append(foo->Block(), [&] {
auto* v1 = b.CallExplicit(
ty.ptr(storage, ty.runtime_array(ty.u32())), core::BuiltinFn::kBufferArrayView,
Vector<core::ir::TemplateParameter, 1>{ty.runtime_array(ty.u32())}, gv1, 16_u, 64_u);
auto* l1 = b.Let("l1", v1);
b.Call(ty.void_(), bar, l1);
auto* l2 = b.Let("l2", gv2);
auto* v2 = b.CallExplicit(
ty.ptr(storage, ty.runtime_array(ty.u32())), core::BuiltinFn::kBufferView,
Vector<core::ir::TemplateParameter, 1>{ty.runtime_array(ty.u32())}, l2, 32_u);
b.Call(ty.void_(), bar, v2);
b.Call(ty.void_(), bar, gv3);
b.Return(foo);
});
auto* src = R"(
$B1: { # root
%gv1:ptr<storage, buffer, read_write> = var undef @binding_point(0, 0)
%gv2:ptr<storage, buffer<128>, read_write> = var undef @binding_point(0, 0)
%gv3:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%bar = func(%param:ptr<storage, array<u32>, read_write>):void {
$B2: {
%let:ptr<storage, array<u32>, read_write> = let %param
ret
}
}
%foo = func():void {
$B3: {
%8:ptr<storage, array<u32>, read_write> = bufferArrayView<array<u32>> %gv1, 16u, 64u
%l1:ptr<storage, array<u32>, read_write> = let %8
%10:void = call %bar, %l1
%l2:ptr<storage, buffer<128>, read_write> = let %gv2
%12:ptr<storage, array<u32>, read_write> = bufferView<array<u32>> %l2, 32u
%13:void = call %bar, %12
%14:void = call %bar, %gv3
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%gv1:ptr<storage, array<u8>, read_write> = var undef @binding_point(0, 0)
%gv2:ptr<storage, array<u8, 128>, read_write> = var undef @binding_point(0, 0)
%gv3:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%bar = func(%param:ptr<storage, array<u32>, read_write>):void {
$B2: {
%let:ptr<storage, array<u32>, read_write> = let %param
ret
}
}
%foo = func():void {
$B3: {
%8:ptr<storage, array<u32>, read_write> = msl.pointer_offset<array<u32>> %gv1, 16u
%l1:ptr<storage, array<u32>, read_write> = let %8
%10:void = call %bar, %l1
%l2:ptr<storage, array<u8, 128>, read_write> = let %gv2
%12:ptr<storage, array<u32>, read_write> = msl.pointer_offset<array<u32>> %l2, 32u
%13:void = call %bar, %12
%14:void = call %bar, %gv3
ret
}
}
)";
Run(DecomposeBuffer);
EXPECT_EQ(expect, str());
}
} // namespace
} // namespace tint::msl::writer::raise