| // Copyright 2023 The Tint Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "src/tint/lang/core/ir/transform/demote_to_helper.h" |
| |
| #include <utility> |
| |
| #include "src/tint/lang/core/ir/transform/test_helper.h" |
| #include "src/tint/lang/core/type/builtin_structs.h" |
| #include "src/tint/lang/core/type/f32.h" |
| #include "src/tint/lang/core/type/storage_texture.h" |
| |
| namespace tint::ir::transform { |
| namespace { |
| |
| using namespace tint::builtin::fluent_types; // NOLINT |
| using namespace tint::number_suffixes; // NOLINT |
| |
| using IR_DemoteToHelperTest = TransformTest; |
| |
| TEST_F(IR_DemoteToHelperTest, NoModify_NoDiscard) { |
| auto* buffer = b.Var("buffer", ty.ptr<storage, i32>()); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { // |
| b.Store(buffer, 42_i); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| } |
| |
| %ep = @fragment func():f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| store %buffer, 42i |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = src; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, DiscardInEntryPoint_WriteInEntryPoint) { |
| auto* buffer = b.Var("buffer", ty.ptr<storage, i32>()); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| b.Store(buffer, 42_i); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| store %buffer, 42i |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| %5:bool = load %continue_execution |
| if %5 [t: %b4] { # if_2 |
| %b4 = block { # true |
| store %buffer, 42i |
| exit_if # if_2 |
| } |
| } |
| %6:bool = load %continue_execution |
| if %6 [t: %b5] { # if_3 |
| %b5 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, DiscardInEntryPoint_WriteInHelper) { |
| auto* buffer = b.Var("buffer", ty.ptr<storage, i32>()); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* helper = b.Function("foo", ty.void_()); |
| b.With(helper->Block(), [&] { |
| b.Store(buffer, 42_i); |
| b.Return(helper); |
| }); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| b.Call(ty.void_(), helper); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| } |
| |
| %foo = func():void -> %b2 { |
| %b2 = block { |
| store %buffer, 42i |
| ret |
| } |
| } |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b3 { |
| %b3 = block { |
| if %front_facing [t: %b4] { # if_1 |
| %b4 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| %5:void = call %foo |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %foo = func():void -> %b2 { |
| %b2 = block { |
| %4:bool = load %continue_execution |
| if %4 [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %buffer, 42i |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b4 { |
| %b4 = block { |
| if %front_facing [t: %b5] { # if_2 |
| %b5 = block { # true |
| store %continue_execution, false |
| exit_if # if_2 |
| } |
| } |
| %7:void = call %foo |
| %8:bool = load %continue_execution |
| if %8 [t: %b6] { # if_3 |
| %b6 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, DiscardInHelper_WriteInEntryPoint) { |
| auto* buffer = b.Var("buffer", ty.ptr<storage, i32>()); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* cond = b.FunctionParam("cond", ty.bool_()); |
| auto* helper = b.Function("foo", ty.void_()); |
| helper->SetParams({cond}); |
| b.With(helper->Block(), [&] { |
| auto* ifelse = b.If(cond); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| b.Return(helper); |
| }); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| b.Call(ty.void_(), helper, front_facing); |
| b.Store(buffer, 42_i); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| } |
| |
| %foo = func(%cond:bool):void -> %b2 { |
| %b2 = block { |
| if %cond [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b4 { |
| %b4 = block { |
| %6:void = call %foo, %front_facing |
| store %buffer, 42i |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %foo = func(%cond:bool):void -> %b2 { |
| %b2 = block { |
| if %cond [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b4 { |
| %b4 = block { |
| %7:void = call %foo, %front_facing |
| %8:bool = load %continue_execution |
| if %8 [t: %b5] { # if_2 |
| %b5 = block { # true |
| store %buffer, 42i |
| exit_if # if_2 |
| } |
| } |
| %9:bool = load %continue_execution |
| if %9 [t: %b6] { # if_3 |
| %b6 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, DiscardInHelper_WriteInHelper) { |
| auto* buffer = b.Var("buffer", ty.ptr<storage, i32>()); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* cond = b.FunctionParam("cond", ty.bool_()); |
| auto* helper = b.Function("foo", ty.void_()); |
| helper->SetParams({cond}); |
| b.With(helper->Block(), [&] { |
| auto* ifelse = b.If(cond); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| b.Store(buffer, 42_i); |
| b.Return(helper); |
| }); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| b.Call(ty.void_(), helper, front_facing); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| } |
| |
| %foo = func(%cond:bool):void -> %b2 { |
| %b2 = block { |
| if %cond [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| store %buffer, 42i |
| ret |
| } |
| } |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b4 { |
| %b4 = block { |
| %6:void = call %foo, %front_facing |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, i32, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %foo = func(%cond:bool):void -> %b2 { |
| %b2 = block { |
| if %cond [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| %5:bool = load %continue_execution |
| if %5 [t: %b4] { # if_2 |
| %b4 = block { # true |
| store %buffer, 42i |
| exit_if # if_2 |
| } |
| } |
| ret |
| } |
| } |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b5 { |
| %b5 = block { |
| %8:void = call %foo, %front_facing |
| %9:bool = load %continue_execution |
| if %9 [t: %b6] { # if_3 |
| %b6 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, WriteToInvocationPrivateAddressSpace) { |
| auto* priv = b.RootBlock()->Append(b.Var("priv", ty.ptr<private_, i32>())); |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| auto* func = b.Var("func", ty.ptr<function, i32>()); |
| auto* ifelse = b.If(front_facing); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| b.Store(priv, 42_i); |
| b.Store(func, 42_i); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %priv:ptr<private, i32, read_write> = var |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| %func:ptr<function, i32, read_write> = var |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| store %priv, 42i |
| store %func, 42i |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %priv:ptr<private, i32, read_write> = var |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| %func:ptr<function, i32, read_write> = var |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| store %priv, 42i |
| store %func, 42i |
| %6:bool = load %continue_execution |
| if %6 [t: %b4] { # if_2 |
| %b4 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, TextureStore) { |
| auto format = builtin::TexelFormat::kR32Float; |
| auto* texture = |
| b.Var("texture", ty.ptr(builtin::AddressSpace::kHandle, |
| ty.Get<type::StorageTexture>( |
| type::TextureDimension::k2d, format, builtin::Access::kWrite, |
| type::StorageTexture::SubtypeFor(format, ty)))); |
| texture->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(texture); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* coord = b.FunctionParam("coord", ty.vec2<i32>()); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing, coord}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| b.Call(ty.void_(), builtin::Function::kTextureStore, texture, coord, 0.5_f); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %texture:ptr<handle, texture_storage_2d<r32float, write>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing], %coord:vec2<i32>):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| %5:void = textureStore %texture, %coord, 0.5f |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %texture:ptr<handle, texture_storage_2d<r32float, write>, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing], %coord:vec2<i32>):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| %6:bool = load %continue_execution |
| if %6 [t: %b4] { # if_2 |
| %b4 = block { # true |
| %7:void = textureStore %texture, %coord, 0.5f |
| exit_if # if_2 |
| } |
| } |
| %8:bool = load %continue_execution |
| if %8 [t: %b5] { # if_3 |
| %b5 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, AtomicStore) { |
| auto* buffer = b.Var("buffer", ty.ptr(storage, ty.atomic<i32>())); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| b.Call(ty.void_(), builtin::Function::kAtomicStore, buffer, 42_i); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| %4:void = atomicStore %buffer, 42i |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| %5:bool = load %continue_execution |
| if %5 [t: %b4] { # if_2 |
| %b4 = block { # true |
| %6:void = atomicStore %buffer, 42i |
| exit_if # if_2 |
| } |
| } |
| %7:bool = load %continue_execution |
| if %7 [t: %b5] { # if_3 |
| %b5 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, AtomicAdd) { |
| auto* buffer = b.Var("buffer", ty.ptr(storage, ty.atomic<i32>())); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| auto* old = b.Call(ty.i32(), builtin::Function::kAtomicAdd, buffer, 42_i); |
| b.Add(ty.i32(), old, 1_i); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| %4:i32 = atomicAdd %buffer, 42i |
| %5:i32 = add %4, 1i |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| %b1 = block { # root |
| %buffer:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| %5:bool = load %continue_execution |
| %6:i32 = if %5 [t: %b4] { # if_2 |
| %b4 = block { # true |
| %7:i32 = atomicAdd %buffer, 42i |
| exit_if %7 # if_2 |
| } |
| # implicit false block: exit_if undef |
| } |
| %8:i32 = add %6, 1i |
| %9:bool = load %continue_execution |
| if %9 [t: %b5] { # if_3 |
| %b5 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(IR_DemoteToHelperTest, AtomicCompareExchange) { |
| auto* buffer = b.Var("buffer", ty.ptr(storage, ty.atomic<i32>())); |
| buffer->SetBindingPoint(0, 0); |
| b.RootBlock()->Append(buffer); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing); |
| auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing}); |
| ep->SetReturnLocation(0_u, {}); |
| |
| b.With(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.With(ifelse->True(), [&] { // |
| b.Discard(); |
| b.ExitIf(ifelse); |
| }); |
| auto* result = b.Call(type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()), |
| builtin::Function::kAtomicCompareExchangeWeak, buffer, 0_i, 42_i); |
| b.Add(ty.i32(), b.Access(ty.i32(), result, 0_i), 1_i); |
| b.Return(ep, 0.5_f); |
| }); |
| |
| auto* src = R"( |
| __atomic_compare_exchange_result_i32 = struct @align(4) { |
| old_value:i32 @offset(0) |
| exchanged:bool @offset(4) |
| } |
| |
| %b1 = block { # root |
| %buffer:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0) |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| discard |
| exit_if # if_1 |
| } |
| } |
| %4:__atomic_compare_exchange_result_i32 = atomicCompareExchangeWeak %buffer, 0i, 42i |
| %5:i32 = access %4, 0i |
| %6:i32 = add %5, 1i |
| ret 0.5f |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| __atomic_compare_exchange_result_i32 = struct @align(4) { |
| old_value:i32 @offset(0) |
| exchanged:bool @offset(4) |
| } |
| |
| %b1 = block { # root |
| %buffer:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0) |
| %continue_execution:ptr<private, bool, read_write> = var, true |
| } |
| |
| %ep = @fragment func(%front_facing:bool [@front_facing]):f32 [@location(0)] -> %b2 { |
| %b2 = block { |
| if %front_facing [t: %b3] { # if_1 |
| %b3 = block { # true |
| store %continue_execution, false |
| exit_if # if_1 |
| } |
| } |
| %5:bool = load %continue_execution |
| %6:__atomic_compare_exchange_result_i32 = if %5 [t: %b4] { # if_2 |
| %b4 = block { # true |
| %7:__atomic_compare_exchange_result_i32 = atomicCompareExchangeWeak %buffer, 0i, 42i |
| exit_if %7 # if_2 |
| } |
| # implicit false block: exit_if undef |
| } |
| %8:i32 = access %6, 0i |
| %9:i32 = add %8, 1i |
| %10:bool = load %continue_execution |
| if %10 [t: %b5] { # if_3 |
| %b5 = block { # true |
| terminate_invocation |
| } |
| } |
| ret 0.5f |
| } |
| } |
| )"; |
| |
| Run<DemoteToHelper>(); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| } // namespace |
| } // namespace tint::ir::transform |