[ir] Update DirectrVariableAccess to add `handle`.
This CL adds a `transform_handle` flag to DirectVariableAccess which
will also move handle objects into their local functions from
parameters.
Change-Id: I14c95f12d2f049e7f6bd17f68407f4165ffe4601
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/209196
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.cc b/src/tint/lang/core/ir/transform/direct_variable_access.cc
index dfc01e4..9ed4f09 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.cc
@@ -103,9 +103,9 @@
/// A AccessShape describes the static "path" from a root variable to an element within the
/// variable.
///
-/// Functions that have pointer parameters which need transforming will be forked into one or more
-/// 'variants'. Each variant has different AccessShapes for the pointer parameters - the transform
-/// will only emit one variant when the shapes of the pointer parameter accesses match.
+/// Functions that have parameters which need transforming will be forked into one or more
+/// 'variants'. Each variant has different AccessShapes for the parameters - the transform will only
+/// emit one variant when the shapes of the parameter accesses match.
///
/// Array accessors index expressions are held externally to the AccessShape, so
/// AccessShape will be considered equal even if the array or matrix index values differ.
@@ -260,13 +260,15 @@
/// transforming. These functions will be replaced with variants based on the access shapes.
void GatherFnsThatNeedForking() {
for (auto& fn : ir.functions) {
- if (fn->Alive()) {
- for (auto* param : fn->Params()) {
- if (ParamNeedsTransforming(param)) {
- need_forking.Add(fn, fn_info_allocator.Create());
- break;
- }
+ if (!fn->Alive()) {
+ continue;
+ }
+ for (auto* param : fn->Params()) {
+ if (!NeedsTransforming(param)) {
+ continue;
}
+ need_forking.Add(fn, fn_info_allocator.Create());
+ break;
}
}
}
@@ -311,7 +313,17 @@
for (size_t i = 0, n = call->Args().Length(); i < n; i++) {
auto* arg = call->Args()[i];
auto* param = target->Params()[i];
- if (ParamNeedsTransforming(param)) {
+
+ if (HandleNeedsTransforming(param)) {
+ b.InsertBefore(call, [&] {
+ // Get the handle chain for the pointer argument.
+ auto chain = HandleChainFor(arg);
+ // Record the parameter shape for the variant's signature.
+ signature.Add(i, chain.shape);
+ });
+ // Record that this handle argument has been replaced.
+ replaced_args.Push(arg);
+ } else if (ParamNeedsTransforming(param)) {
// This argument needs replacing with:
// * Nothing: root is a module-scope var and the access chain has no indicies.
// * A single pointer argument to the root variable: The root is a pointer
@@ -394,6 +406,35 @@
}
}
+ /// Walks the instructions that built `value` to obtain the root variable.
+ /// @param value the value to get the root for
+ /// @return an AccessChain
+ AccessChain HandleChainFor(Value* value) {
+ AccessChain chain;
+ TINT_ASSERT(value->Alive());
+
+ tint::Switch(
+ value, //
+ [&](InstructionResult* res) {
+ auto* inst = res->Instruction()->As<Load>();
+ TINT_ASSERT(inst);
+
+ auto* var_res = inst->From()->As<InstructionResult>();
+ TINT_ASSERT(var_res);
+
+ auto* var = var_res->Instruction()->As<Var>();
+ TINT_ASSERT(var);
+ TINT_ASSERT(var->Block() == ir.root_block);
+
+ // Root handle is a module-scope 'var'
+ chain.shape.root = RootModuleScopeVar{var};
+ chain.root_ptr = var->Result(0);
+ },
+ TINT_ICE_ON_NO_MATCH);
+
+ return chain;
+ }
+
/// Walks the instructions that built #value to obtain the root variable and the pointer
/// accesses.
/// @param value the pointer value to get the access chain for
@@ -500,7 +541,7 @@
// For each parameter in the original function...
for (size_t param_idx = 0; param_idx < old_params.Length(); param_idx++) {
auto* old_param = old_params[param_idx];
- if (!ParamNeedsTransforming(old_param)) {
+ if (!NeedsTransforming(old_param)) {
// Parameter does not need transforming.
new_params.Push(old_param);
continue;
@@ -526,38 +567,46 @@
TINT_ICE() << "unhandled AccessShape root variant";
}
- // Build the access indices parameter, if required.
- ir::FunctionParam* indices_param = nullptr;
- if (uint32_t n = shape->NumIndexAccesses(); n > 0) {
- // Indices are passed as an array of u32
- indices_param = b.FunctionParam(ty.array(ty.u32(), n));
- new_params.Push(indices_param);
+ if (ParamNeedsTransforming(old_param)) {
+ // Build the access indices parameter, if required.
+ ir::FunctionParam* indices_param = nullptr;
+ if (uint32_t n = shape->NumIndexAccesses(); n > 0) {
+ // Indices are passed as an array of u32
+ indices_param = b.FunctionParam(ty.array(ty.u32(), n));
+ new_params.Push(indices_param);
+ }
+
+ // Generate names for the new parameter(s) based on the replaced parameter name.
+ if (auto param_name = ir.NameOf(old_param); param_name.IsValid()) {
+ // Propagate old parameter name to the new parameters
+ if (root_ptr_param) {
+ ir.SetName(root_ptr_param, param_name.Name() + "_root");
+ }
+ if (indices_param) {
+ ir.SetName(indices_param, param_name.Name() + "_indices");
+ }
+ }
+
+ // Rebuild the pointer from the root pointer and accesses.
+ uint32_t index_index = 0;
+ auto chain = Transform(shape->ops, [&](const AccessOp& op) -> Value* {
+ if (auto* m = std::get_if<MemberAccess>(&op)) {
+ return b.Constant(u32(m->member->Index()));
+ }
+ auto* access = b.Access(ty.u32(), indices_param, u32(index_index++));
+ return access->Result(0);
+ });
+ auto* access = b.Access(old_param->Type(), root_ptr, std::move(chain));
+
+ // Replace the now removed parameter value with the access instruction
+ old_param->ReplaceAllUsesWith(access->Result(0));
+ } else if (HandleNeedsTransforming(old_param)) {
+ auto* load = b.Load(root_ptr);
+
+ // Replace the now removed parameter value with the load instruction
+ old_param->ReplaceAllUsesWith(load->Result(0));
}
- // Generate names for the new parameter(s) based on the replaced parameter name.
- if (auto param_name = ir.NameOf(old_param); param_name.IsValid()) {
- // Propagate old parameter name to the new parameters
- if (root_ptr_param) {
- ir.SetName(root_ptr_param, param_name.Name() + "_root");
- }
- if (indices_param) {
- ir.SetName(indices_param, param_name.Name() + "_indices");
- }
- }
-
- // Rebuild the pointer from the root pointer and accesses.
- uint32_t index_index = 0;
- auto chain = Transform(shape->ops, [&](const AccessOp& op) -> Value* {
- if (auto* m = std::get_if<MemberAccess>(&op)) {
- return b.Constant(u32(m->member->Index()));
- }
- auto* access = b.Access(ty.u32(), indices_param, u32(index_index++));
- return access->Result(0);
- });
- auto* access = b.Access(old_param->Type(), root_ptr, std::move(chain));
-
- // Replace the now removed parameter value with the access instruction
- old_param->ReplaceAllUsesWith(access->Result(0));
old_param->Destroy();
}
@@ -583,6 +632,20 @@
}
}
+ /// @return true if @p param is a parameter that requires transforming, based on the
+ /// transform options.
+ /// @param param the function parameter
+ bool NeedsTransforming(FunctionParam* param) const {
+ return HandleNeedsTransforming(param) || ParamNeedsTransforming(param);
+ }
+
+ /// @return true if @p param is a handle parameter that requires transforming, based on the
+ /// transform options.
+ /// @param param the function parameter
+ bool HandleNeedsTransforming(FunctionParam* param) const {
+ return options.transform_handle && param->Type()->IsHandle();
+ }
+
/// @return true if @p param is a pointer parameter that requires transforming, based on the
/// address space and transform options.
/// @param param the function parameter
@@ -622,6 +685,12 @@
[&](Let* let) {
TINT_DEFER(let->Destroy());
return let->Value();
+ },
+ [&](Load* load) {
+ if (options.transform_handle) {
+ TINT_DEFER(load->Destroy());
+ }
+ return nullptr;
});
}
}
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.h b/src/tint/lang/core/ir/transform/direct_variable_access.h
index ced1943..65f220f 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.h
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.h
@@ -44,23 +44,26 @@
bool transform_private = false;
/// If true, then 'function' sub-object pointer arguments will be transformed.
bool transform_function = false;
+ /// If true, then 'handle' sub-object handle type arguments will be transformed.
+ bool transform_handle = false;
/// Reflection for this class
TINT_REFLECT(DirectVariableAccessOptions, transform_private, transform_function);
};
-/// DirectVariableAccess is a transform that transforms pointer parameters in the 'storage',
+/// DirectVariableAccess is a transform that transforms parameters in the 'storage',
/// 'uniform' and 'workgroup' address space so that they're accessed directly by the function,
-/// instead of being passed by pointer.
+/// instead of being passed by pointer. It will potentiall also transform `private`, `handle` or
+/// `function` parameters depending on provided options.
///
-/// DirectVariableAccess works by creating specializations of functions that have pointer
-/// parameters, one specialization for each pointer argument's unique access chain 'shape' from a
-/// unique variable. Calls to specialized functions are transformed so that the pointer arguments
-/// are replaced with an array of access-chain indicies, and if the pointer is in the 'function' or
-/// 'private' address space, also with a pointer to the root object. For more information, see the
-/// comments in src/tint/lang/wgsl/ast/transform/direct_variable_access.cc.
+/// DirectVariableAccess works by creating specializations of functions that have matching
+/// parameters, one specialization for each argument's unique access chain 'shape' from a unique
+/// variable. Calls to specialized functions are transformed so that the arguments are replaced with
+/// an array of access-chain indices, and if the parameter is in the 'function' or 'private'
+/// address space, also with a pointer to the root object.
///
/// @param module the module to transform
+/// @param options the options
/// @returns error diagnostics on failure
Result<SuccessType> DirectVariableAccess(Module& module,
const DirectVariableAccessOptions& options);
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
index fd89e61..60dcedd 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
@@ -33,6 +33,8 @@
#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/sampled_texture.h"
+#include "src/tint/lang/core/type/sampler.h"
#include "src/tint/lang/core/type/struct.h"
namespace tint::core::ir::transform {
@@ -43,14 +45,22 @@
namespace {
+static constexpr DirectVariableAccessOptions kTransformHandle = {
+ /* transform_private */ false,
+ /* transform_function */ false,
+ /* transform_handle */ true,
+};
+
static constexpr DirectVariableAccessOptions kTransformPrivate = {
/* transform_private */ true,
/* transform_function */ false,
+ /* transform_handle */ false,
};
static constexpr DirectVariableAccessOptions kTransformFunction = {
/* transform_private */ false,
/* transform_function */ true,
+ /* transform_handle */ false,
};
} // namespace
@@ -323,6 +333,80 @@
EXPECT_EQ(expect, str());
}
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, HandleTexture_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.Get<type::SampledTexture>(core::type::TextureDimension::k1d, ty.f32()));
+ f->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(f->Block(), [&] { b.Return(f, b.Constant(2_i)); });
+
+ auto* src = R"(
+$B1: { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%f = func(%pre:i32, %p:texture_1d<f32>, %post:i32):i32 {
+ $B2: {
+ ret 2i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, HandleTexture_Enabled) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* f = b.Function("f", ty.u32());
+ auto* p = b.FunctionParam(
+ "p", ty.Get<type::SampledTexture>(core::type::TextureDimension::k1d, ty.f32()));
+ f->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(f->Block(),
+ [&] { b.Return(f, b.Call(ty.u32(), core::BuiltinFn::kTextureDimensions, p, 0_u)); });
+
+ auto* src = R"(
+$B1: { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%f = func(%pre:i32, %p:texture_1d<f32>, %post:i32):u32 {
+ $B2: {
+ %6:u32 = textureDimensions %p, 0u
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+)";
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
} // namespace remove_uncalled
////////////////////////////////////////////////////////////////////////////////
@@ -4979,6 +5063,695 @@
} // namespace function_as_tests
////////////////////////////////////////////////////////////////////////////////
+// 'handle' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace handle_as_tests {
+
+using IR_DirectVariableAccessTest_HandleAS = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Enabled_LocalTextureSampler) {
+ auto* tex =
+ b.Var("tex", handle,
+ ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32()),
+ core::Access::kReadWrite);
+ tex->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(samp);
+
+ auto* fn = b.Function("f", ty.void_());
+ b.Append(fn->Block(), [&] {
+ auto* t = b.Load(tex);
+ auto* s = b.Load(samp);
+
+ b.Let("p", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func():void {
+ $B2: {
+ %4:texture_2d<f32> = load %tex
+ %5:sampler = load %samp
+ %6:vec4<f32> = textureGather 0u, %4, %5, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src; // Nothing changes
+
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Enabled_LocalTextureParamSampler) {
+ auto* tex =
+ b.Var("tex", handle,
+ ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32()),
+ core::Access::kReadWrite);
+ tex->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(samp);
+
+ auto* s = b.FunctionParam("s", ty.sampler());
+
+ auto* fn = b.Function("f", ty.void_());
+ fn->SetParams({s});
+ b.Append(fn->Block(), [&] {
+ auto* t = b.Load(tex);
+
+ b.Let("p", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* fn2 = b.Function("g", ty.void_());
+ b.Append(fn2->Block(), [&] {
+ auto* s2 = b.Load(samp);
+ b.Call(ty.void_(), fn, s2);
+ b.Return(fn2);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func(%s:sampler):void {
+ $B2: {
+ %5:texture_2d<f32> = load %tex
+ %6:vec4<f32> = textureGather 0u, %5, %s, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %9:sampler = load %samp
+ %10:void = call %f, %9
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func():void {
+ $B2: {
+ %4:sampler = load %samp
+ %5:texture_2d<f32> = load %tex
+ %6:vec4<f32> = textureGather 0u, %5, %4, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %9:void = call %f
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Enabled_ParamTextureLocalSampler) {
+ auto* tex_ty = ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+ auto* tex = b.Var("tex", handle, tex_ty, core::Access::kReadWrite);
+ tex->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(samp);
+
+ auto* t = b.FunctionParam("t", tex_ty);
+
+ auto* fn = b.Function("f", ty.void_());
+ fn->SetParams({t});
+ b.Append(fn->Block(), [&] {
+ auto* s = b.Load(samp);
+
+ b.Let("p", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* fn2 = b.Function("g", ty.void_());
+ b.Append(fn2->Block(), [&] {
+ auto* t2 = b.Load(tex);
+ b.Call(ty.void_(), fn, t2);
+ b.Return(fn2);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func(%t:texture_2d<f32>):void {
+ $B2: {
+ %5:sampler = load %samp
+ %6:vec4<f32> = textureGather 0u, %t, %5, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %9:texture_2d<f32> = load %tex
+ %10:void = call %f, %9
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func():void {
+ $B2: {
+ %4:texture_2d<f32> = load %tex
+ %5:sampler = load %samp
+ %6:vec4<f32> = textureGather 0u, %4, %5, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %9:void = call %f
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Enabled_ParamTextureParamSampler) {
+ auto* tex_ty = ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+ auto* tex = b.Var("tex", handle, tex_ty, core::Access::kReadWrite);
+ tex->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(samp);
+
+ auto* t = b.FunctionParam("t", tex_ty);
+ auto* s = b.FunctionParam("s", ty.sampler());
+
+ auto* fn = b.Function("f", ty.void_());
+ fn->SetParams({t, s});
+ b.Append(fn->Block(), [&] {
+ b.Let("p", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* fn2 = b.Function("g", ty.void_());
+ b.Append(fn2->Block(), [&] {
+ auto* s2 = b.Load(samp);
+ auto* t2 = b.Load(tex);
+ b.Call(ty.void_(), fn, t2, s2);
+ b.Return(fn2);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func(%t:texture_2d<f32>, %s:sampler):void {
+ $B2: {
+ %6:vec4<f32> = textureGather 0u, %t, %s, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %9:sampler = load %samp
+ %10:texture_2d<f32> = load %tex
+ %11:void = call %f, %10, %9
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func():void {
+ $B2: {
+ %4:texture_2d<f32> = load %tex
+ %5:sampler = load %samp
+ %6:vec4<f32> = textureGather 0u, %4, %5, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %9:void = call %f
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Enabled_MultiFunction) {
+ auto* tex_ty = ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+ auto* tex = b.Var("tex", handle, tex_ty, core::Access::kReadWrite);
+ tex->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(samp);
+
+ auto* t = b.FunctionParam("t", tex_ty);
+ auto* s = b.FunctionParam("s", ty.sampler());
+
+ auto* fn = b.Function("f", ty.void_());
+ fn->SetParams({t, s});
+ b.Append(fn->Block(), [&] {
+ b.Let("p", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* t2 = b.FunctionParam("t", tex_ty);
+ auto* fn2 = b.Function("g", ty.void_());
+ fn2->SetParams({t2});
+ b.Append(fn2->Block(), [&] {
+ auto* s2 = b.Load(samp);
+ b.Call(ty.void_(), fn, t2, s2);
+ b.Return(fn2);
+ });
+
+ auto* fn3 = b.Function("h", ty.void_());
+ b.Append(fn3->Block(), [&] {
+ auto* t3 = b.Load(tex);
+ b.Call(ty.void_(), fn2, t3);
+ b.Return(fn3);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func(%t:texture_2d<f32>, %s:sampler):void {
+ $B2: {
+ %6:vec4<f32> = textureGather 0u, %t, %s, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func(%t_1:texture_2d<f32>):void { # %t_1: 't'
+ $B3: {
+ %10:sampler = load %samp
+ %11:void = call %f, %t_1, %10
+ ret
+ }
+}
+%h = func():void {
+ $B4: {
+ %13:texture_2d<f32> = load %tex
+ %14:void = call %g, %13
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func():void {
+ $B2: {
+ %4:texture_2d<f32> = load %tex
+ %5:sampler = load %samp
+ %6:vec4<f32> = textureGather 0u, %4, %5, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %9:void = call %f
+ ret
+ }
+}
+%h = func():void {
+ $B4: {
+ %11:void = call %g
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Disabled_MultiFunction) {
+ auto* tex_ty = ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+ auto* tex = b.Var("tex", handle, tex_ty, core::Access::kReadWrite);
+ tex->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(samp);
+
+ auto* t = b.FunctionParam("t", tex_ty);
+ auto* s = b.FunctionParam("s", ty.sampler());
+
+ auto* fn = b.Function("f", ty.void_());
+ fn->SetParams({t, s});
+ b.Append(fn->Block(), [&] {
+ b.Let("p", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* t2 = b.FunctionParam("t", tex_ty);
+ auto* fn2 = b.Function("g", ty.void_());
+ fn2->SetParams({t2});
+ b.Append(fn2->Block(), [&] {
+ auto* s2 = b.Load(samp);
+ b.Call(ty.void_(), fn, t2, s2);
+ b.Return(fn2);
+ });
+
+ auto* fn3 = b.Function("h", ty.void_());
+ b.Append(fn3->Block(), [&] {
+ auto* t3 = b.Load(tex);
+ b.Call(ty.void_(), fn2, t3);
+ b.Return(fn3);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func(%t:texture_2d<f32>, %s:sampler):void {
+ $B2: {
+ %6:vec4<f32> = textureGather 0u, %t, %s, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %6
+ ret
+ }
+}
+%g = func(%t_1:texture_2d<f32>):void { # %t_1: 't'
+ $B3: {
+ %10:sampler = load %samp
+ %11:void = call %f, %t_1, %10
+ ret
+ }
+}
+%h = func():void {
+ $B4: {
+ %13:texture_2d<f32> = load %tex
+ %14:void = call %g, %13
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src; // No change
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Enabled_DuplicateParam) {
+ auto* tex_ty = ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+ auto* tex = b.Var("tex", handle, tex_ty, core::Access::kReadWrite);
+ tex->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(samp);
+
+ auto* t1 = b.FunctionParam("t1", tex_ty);
+ auto* t2 = b.FunctionParam("t2", tex_ty);
+
+ auto* fn = b.Function("f", ty.void_());
+ fn->SetParams({t1, t2});
+ b.Append(fn->Block(), [&] {
+ auto* s = b.Load(samp);
+
+ b.Let("p1", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t1, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Let("p2", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t2, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* fn2 = b.Function("g", ty.void_());
+ b.Append(fn2->Block(), [&] {
+ auto* t3 = b.Load(tex);
+ auto* t4 = b.Load(tex);
+ b.Call(ty.void_(), fn, t3, t4);
+ b.Return(fn2);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func(%t1:texture_2d<f32>, %t2:texture_2d<f32>):void {
+ $B2: {
+ %6:sampler = load %samp
+ %7:vec4<f32> = textureGather 0u, %t1, %6, vec2<f32>(0.0f)
+ %p1:vec4<f32> = let %7
+ %9:vec4<f32> = textureGather 0u, %t2, %6, vec2<f32>(0.0f)
+ %p2:vec4<f32> = let %9
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %12:texture_2d<f32> = load %tex
+ %13:texture_2d<f32> = load %tex
+ %14:void = call %f, %12, %13
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %tex:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 1)
+}
+
+%f = func():void {
+ $B2: {
+ %4:texture_2d<f32> = load %tex
+ %5:texture_2d<f32> = load %tex
+ %6:sampler = load %samp
+ %7:vec4<f32> = textureGather 0u, %4, %6, vec2<f32>(0.0f)
+ %p1:vec4<f32> = let %7
+ %9:vec4<f32> = textureGather 0u, %5, %6, vec2<f32>(0.0f)
+ %p2:vec4<f32> = let %9
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %12:void = call %f
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_HandleAS, Enabled_Fork) {
+ auto* tex_ty = ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+ auto* tex1 = b.Var("tex1", handle, tex_ty, core::Access::kReadWrite);
+ tex1->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(tex1);
+ auto* tex2 = b.Var("tex2", handle, tex_ty, core::Access::kReadWrite);
+ tex2->SetBindingPoint(0, 1);
+ b.ir.root_block->Append(tex2);
+
+ auto* samp = b.Var("samp", handle, ty.sampler(), core::Access::kReadWrite);
+ samp->SetBindingPoint(0, 2);
+ b.ir.root_block->Append(samp);
+
+ auto* t = b.FunctionParam("t", tex_ty);
+
+ auto* fn = b.Function("f", ty.void_());
+ fn->SetParams({t});
+ b.Append(fn->Block(), [&] {
+ auto* s = b.Load(samp);
+ b.Let("p", b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, 0_u, t, s,
+ b.Splat(ty.vec2<f32>(), 0_f)));
+ b.Return(fn);
+ });
+
+ auto* fn2 = b.Function("g", ty.void_());
+ b.Append(fn2->Block(), [&] {
+ auto* t2 = b.Load(tex1);
+ b.Call(ty.void_(), fn, t2);
+ b.Return(fn2);
+ });
+
+ auto* fn3 = b.Function("h", ty.void_());
+ b.Append(fn3->Block(), [&] {
+ auto* t2 = b.Load(tex2);
+ b.Call(ty.void_(), fn, t2);
+ b.Return(fn3);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %tex1:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %tex2:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 1)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 2)
+}
+
+%f = func(%t:texture_2d<f32>):void {
+ $B2: {
+ %6:sampler = load %samp
+ %7:vec4<f32> = textureGather 0u, %t, %6, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %7
+ ret
+ }
+}
+%g = func():void {
+ $B3: {
+ %10:texture_2d<f32> = load %tex1
+ %11:void = call %f, %10
+ ret
+ }
+}
+%h = func():void {
+ $B4: {
+ %13:texture_2d<f32> = load %tex2
+ %14:void = call %f, %13
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %tex1:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 0)
+ %tex2:ptr<handle, texture_2d<f32>, read_write> = var @binding_point(0, 1)
+ %samp:ptr<handle, sampler, read_write> = var @binding_point(0, 2)
+}
+
+%f = func():void {
+ $B2: {
+ %5:texture_2d<f32> = load %tex1
+ %6:sampler = load %samp
+ %7:vec4<f32> = textureGather 0u, %5, %6, vec2<f32>(0.0f)
+ %p:vec4<f32> = let %7
+ ret
+ }
+}
+%f_1 = func():void { # %f_1: 'f'
+ $B3: {
+ %10:texture_2d<f32> = load %tex2
+ %11:sampler = load %samp
+ %12:vec4<f32> = textureGather 0u, %10, %11, vec2<f32>(0.0f)
+ %p_1:vec4<f32> = let %12 # %p_1: 'p'
+ ret
+ }
+}
+%g = func():void {
+ $B4: {
+ %15:void = call %f
+ ret
+ }
+}
+%h = func():void {
+ $B5: {
+ %17:void = call %f_1
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformHandle);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace handle_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
// builtin function calls
////////////////////////////////////////////////////////////////////////////////
namespace builtin_fn_calls {