[tint][ir] Add DirectVariableAccess IR transform
This is a reimplementation of the tint::ast::transform::DirectVariableAccess transform, for the new IR system.
Change-Id: Id400f13334f51535dba42be280af2cd2df113731
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/152400
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h
index 93e7aed..64448cf 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -834,7 +834,7 @@
/// @returns the instruction
template <core::AddressSpace SPACE, typename T, core::Access ACCESS = core::Access::kReadWrite>
ir::Var* Var(std::string_view name) {
- return Var(ir.Types().ptr<SPACE, T, ACCESS>(), name);
+ return Var(name, ir.Types().ptr<SPACE, T, ACCESS>());
}
/// Creates a new `let` declaration
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index f381912..e4c3240 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -33,6 +33,7 @@
"block_decorated_structs.cc",
"builtin_polyfill.cc",
"demote_to_helper.cc",
+ "direct_variable_access.cc",
"multiplanar_external_texture.cc",
"robustness.cc",
"shader_io.cc",
@@ -47,6 +48,7 @@
"block_decorated_structs.h",
"builtin_polyfill.h",
"demote_to_helper.h",
+ "direct_variable_access.h",
"multiplanar_external_texture.h",
"robustness.h",
"shader_io.h",
@@ -89,12 +91,18 @@
"block_decorated_structs_test.cc",
"builtin_polyfill_test.cc",
"demote_to_helper_test.cc",
+ "direct_variable_access_test.cc",
"helper_test.h",
"multiplanar_external_texture_test.cc",
"robustness_test.cc",
"std140_test.cc",
"zero_init_workgroup_memory_test.cc",
- ],
+ ] + select({
+ ":tint_build_wgsl_reader_and_tint_build_wgsl_writer": [
+ "direct_variable_access_wgsl_test.cc",
+ ],
+ "//conditions:default": [],
+ }),
deps = [
"//src/tint/api/common",
"//src/tint/api/options",
@@ -104,6 +112,14 @@
"//src/tint/lang/core/ir",
"//src/tint/lang/core/ir/transform",
"//src/tint/lang/core/type",
+ "//src/tint/lang/wgsl",
+ "//src/tint/lang/wgsl/ast",
+ "//src/tint/lang/wgsl/program",
+ "//src/tint/lang/wgsl/reader",
+ "//src/tint/lang/wgsl/reader/program_to_ir",
+ "//src/tint/lang/wgsl/sem",
+ "//src/tint/lang/wgsl/writer",
+ "//src/tint/lang/wgsl/writer/ir_to_program",
"//src/tint/utils/containers",
"//src/tint/utils/diagnostic",
"//src/tint/utils/ice",
@@ -123,3 +139,21 @@
visibility = ["//visibility:public"],
)
+alias(
+ name = "tint_build_wgsl_reader",
+ actual = "//src/tint:tint_build_wgsl_reader_true",
+)
+
+alias(
+ name = "tint_build_wgsl_writer",
+ actual = "//src/tint:tint_build_wgsl_writer_true",
+)
+
+selects.config_setting_group(
+ name = "tint_build_wgsl_reader_and_tint_build_wgsl_writer",
+ match_all = [
+ ":tint_build_wgsl_reader",
+ ":tint_build_wgsl_writer",
+ ],
+)
+
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index c441513..1164934 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -40,6 +40,8 @@
lang/core/ir/transform/builtin_polyfill.h
lang/core/ir/transform/demote_to_helper.cc
lang/core/ir/transform/demote_to_helper.h
+ lang/core/ir/transform/direct_variable_access.cc
+ lang/core/ir/transform/direct_variable_access.h
lang/core/ir/transform/multiplanar_external_texture.cc
lang/core/ir/transform/multiplanar_external_texture.h
lang/core/ir/transform/robustness.cc
@@ -87,6 +89,7 @@
lang/core/ir/transform/block_decorated_structs_test.cc
lang/core/ir/transform/builtin_polyfill_test.cc
lang/core/ir/transform/demote_to_helper_test.cc
+ lang/core/ir/transform/direct_variable_access_test.cc
lang/core/ir/transform/helper_test.h
lang/core/ir/transform/multiplanar_external_texture_test.cc
lang/core/ir/transform/robustness_test.cc
@@ -103,6 +106,14 @@
tint_lang_core_ir
tint_lang_core_ir_transform
tint_lang_core_type
+ tint_lang_wgsl
+ tint_lang_wgsl_ast
+ tint_lang_wgsl_program
+ tint_lang_wgsl_reader
+ tint_lang_wgsl_reader_program_to_ir
+ tint_lang_wgsl_sem
+ tint_lang_wgsl_writer
+ tint_lang_wgsl_writer_ir_to_program
tint_utils_containers
tint_utils_diagnostic
tint_utils_ice
@@ -121,3 +132,9 @@
tint_target_add_external_dependencies(tint_lang_core_ir_transform_test test
"gtest"
)
+
+if(TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER)
+ tint_target_add_sources(tint_lang_core_ir_transform_test test
+ "lang/core/ir/transform/direct_variable_access_wgsl_test.cc"
+ )
+endif(TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER)
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index b907ec2..9ee4ae0 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -45,6 +45,8 @@
"builtin_polyfill.h",
"demote_to_helper.cc",
"demote_to_helper.h",
+ "direct_variable_access.cc",
+ "direct_variable_access.h",
"multiplanar_external_texture.cc",
"multiplanar_external_texture.h",
"robustness.cc",
@@ -90,6 +92,7 @@
"block_decorated_structs_test.cc",
"builtin_polyfill_test.cc",
"demote_to_helper_test.cc",
+ "direct_variable_access_test.cc",
"helper_test.h",
"multiplanar_external_texture_test.cc",
"robustness_test.cc",
@@ -106,6 +109,14 @@
"${tint_src_dir}/lang/core/ir",
"${tint_src_dir}/lang/core/ir/transform",
"${tint_src_dir}/lang/core/type",
+ "${tint_src_dir}/lang/wgsl",
+ "${tint_src_dir}/lang/wgsl/ast",
+ "${tint_src_dir}/lang/wgsl/program",
+ "${tint_src_dir}/lang/wgsl/reader",
+ "${tint_src_dir}/lang/wgsl/reader/program_to_ir",
+ "${tint_src_dir}/lang/wgsl/sem",
+ "${tint_src_dir}/lang/wgsl/writer",
+ "${tint_src_dir}/lang/wgsl/writer/ir_to_program",
"${tint_src_dir}/utils/containers",
"${tint_src_dir}/utils/diagnostic",
"${tint_src_dir}/utils/ice",
@@ -120,5 +131,9 @@
"${tint_src_dir}/utils/text",
"${tint_src_dir}/utils/traits",
]
+
+ if (tint_build_wgsl_reader && tint_build_wgsl_writer) {
+ sources += [ "direct_variable_access_wgsl_test.cc" ]
+ }
}
}
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.cc b/src/tint/lang/core/ir/transform/direct_variable_access.cc
new file mode 100644
index 0000000..5939b82
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.cc
@@ -0,0 +1,671 @@
+// 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/direct_variable_access.h"
+
+#include <string>
+#include <utility>
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/traverse.h"
+#include "src/tint/lang/core/ir/user_call.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/lang/core/ir/var.h"
+#include "src/tint/utils/containers/reverse.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::core::ir::transform {
+namespace {
+
+/// An access root, originating from a module-scope var.
+/// These roots are not passed by parameter, but instead the callee references the module-scope var
+/// directly.
+struct RootModuleScopeVar {
+ /// The module-scope var
+ Var* var = nullptr;
+
+ /// @return a hash value for this object
+ uint64_t HashCode() const { return Hash(var); }
+
+ /// Inequality operator
+ bool operator!=(const RootModuleScopeVar& other) const { return var != other.var; }
+};
+
+/// An access root, originating from a pointer parameter or function-scope var.
+/// These roots are passed by pointer parameter.
+struct RootPtrParameter {
+ /// The parameter pointer type
+ const type::Pointer* type = nullptr;
+
+ /// @return a hash value for this object
+ uint64_t HashCode() const { return Hash(type); }
+
+ /// Inequality operator
+ bool operator!=(const RootPtrParameter& other) const { return type != other.type; }
+};
+
+/// An access root. Either a RootModuleScopeVar or RootPtrParameter.
+using AccessRoot = std::variant<RootModuleScopeVar, RootPtrParameter>;
+
+/// MemberAccess is an access operator to a struct member.
+struct MemberAccess {
+ /// The member being accessed
+ const type::StructMember* member;
+
+ /// @return a hash member for this object
+ uint64_t HashCode() const { return Hash(member); }
+
+ /// Inequality operator
+ bool operator!=(const MemberAccess& other) const { return member != other.member; }
+};
+
+/// IndexAccess is an access operator to an array element or matrix column.
+/// The ordered list of indices is passed by parameter.
+struct IndexAccess {
+ /// @return a hash value for this object
+ uint64_t HashCode() const { return 42; }
+
+ /// Inequality operator
+ bool operator!=(const IndexAccess&) const { return false; }
+};
+
+/// An access operation. Either a MemberAccess or IndexAccess.
+using AccessOp = std::variant<MemberAccess, IndexAccess>;
+
+/// 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.
+///
+/// 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.
+///
+/// For example, consider the following:
+///
+/// ```
+/// struct A {
+/// x : array<i32, 8>,
+/// y : u32,
+/// };
+/// struct B {
+/// x : i32,
+/// y : array<A, 4>
+/// };
+/// var<workgroup> C : B;
+/// ```
+///
+/// The following AccessShape would describe the following:
+///
+/// +====================================+===============+=================================+
+/// | AccessShape | Type | Expression |
+/// +====================================+===============+=================================+
+/// | [ Var 'C', MemberAccess 'x' ] | i32 | C.x |
+/// +------------------------------------+---------------+---------------------------------+
+/// | [ Var 'C', MemberAccess 'y' ] | array<A, 4> | C.y |
+/// +------------------------------------+---------------+---------------------------------+
+/// | [ Var 'C', MemberAccess 'y', | A | C.y[indices[0]] |
+/// | IndexAccess ] | | |
+/// +------------------------------------+---------------+---------------------------------+
+/// | [ Var 'C', MemberAccess 'y', | array<i32, 8> | C.y[indices[0]].x |
+/// | IndexAccess, MemberAccess 'x' ] | | |
+/// +------------------------------------+---------------+---------------------------------+
+/// | [ Var 'C', MemberAccess 'y', | i32 | C.y[indices[0]].x[indices[1]] |
+/// | IndexAccess, MemberAccess 'x', | | |
+/// | IndexAccess ] | | |
+/// +------------------------------------+---------------+---------------------------------+
+/// | [ Var 'C', MemberAccess 'y', | u32 | C.y[indices[0]].y |
+/// | IndexAccess, MemberAccess 'y' ] | | |
+/// +------------------------------------+---------------+---------------------------------+
+///
+/// Where: `indices` is the AccessChain::indices.
+struct AccessShape {
+ /// The access root.
+ AccessRoot root;
+ /// The access operations.
+ Vector<AccessOp, 8> ops;
+
+ /// @returns the number of IndexAccess operations in #ops.
+ uint32_t NumIndexAccesses() const {
+ uint32_t count = 0;
+ for (auto& op : ops) {
+ if (std::holds_alternative<IndexAccess>(op)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /// @return a hash value for this object
+ uint64_t HashCode() const { return Hash(root, ops); }
+
+ /// Inequality operator
+ bool operator!=(const AccessShape& other) const {
+ return root != other.root || ops != other.ops;
+ }
+};
+
+/// AccessChain describes a chain of access expressions originating from a variable.
+struct AccessChain {
+ /// The shape of the access chain
+ AccessShape shape;
+ /// The originating pointer
+ Value* root_ptr = nullptr;
+ /// The array of dynamic indices
+ Vector<Value*, 8> indices;
+};
+
+/// A variant signature describes the access shape of all the function's pointer parameters.
+/// This is a map of pointer parameter index to access shape.
+using VariantSignature = Hashmap<size_t, AccessShape, 4>;
+
+/// FnInfo describes a function that has pointer parameters which need replacing.
+/// This function will be replaced by zero, one or many variants. Each variant will have a unique
+/// access shape for the function's the pointer arguments.
+struct FnInfo {
+ /// A map of variant signature to the variant's unique IR function.
+ Hashmap<VariantSignature, Function*, 4> variants_by_sig;
+ /// The order to emit the variants in the final module.
+ Vector<Function*, 4> ordered_variants;
+};
+
+/// FnVariant describes a unique variant of a function that has pointer parameters that need
+/// replacing.
+struct FnVariant {
+ /// The signature of the variant.
+ VariantSignature signature;
+ /// The IR function for this variant.
+ Function* fn = nullptr;
+ /// The function information of the original function that this variant is based off.
+ FnInfo* info = nullptr;
+};
+
+/// PIMPL state for the transform.
+struct State {
+ /// The IR module.
+ Module& ir;
+
+ /// The transform options
+ const DirectVariableAccessOptions& options;
+
+ /// The IR builder.
+ Builder b{ir};
+
+ /// The type manager.
+ core::type::Manager& ty{ir.Types()};
+
+ /// The symbol table.
+ SymbolTable& sym{ir.symbols};
+
+ /// The functions that have pointer parameters that need transforming.
+ /// These functions will be replaced with zero, one or many forked variants.
+ Hashmap<ir::Function*, FnInfo*, 8> need_forking{};
+
+ /// Queue of variants that need building
+ Vector<FnVariant, 8> variants_to_build{};
+
+ /// Allocator for FnInfo
+ BlockAllocator<FnInfo> fn_info_allocator{};
+
+ /// Process the module.
+ void Process() {
+ // Make a copy of all the functions in the IR module.
+ auto input_fns = ir.functions;
+
+ // Populate #need_forking
+ GatherFnsThatNeedForking();
+
+ // Transform the functions that make calls to #need_forking, which aren't in #need_forking
+ // themselves.
+ BuildRootFns();
+
+ // Build variants of the functions in #need_forking.
+ BuildFnVariants();
+
+ // Rebuild ir.functions.
+ EmitFunctions(input_fns);
+ }
+
+ /// Populates #need_forking with all the functions that have pointer parameters which need
+ /// transforming. These functions will be replaced with variants based on the access shapes.
+ void GatherFnsThatNeedForking() {
+ for (auto* fn : ir.functions) {
+ for (auto* param : fn->Params()) {
+ if (ParamNeedsTransforming(param)) {
+ need_forking.Add(fn, fn_info_allocator.Create());
+ break;
+ }
+ }
+ }
+ }
+
+ /// Adjusts the calls of all the functions that make calls to #need_forking, which aren't in
+ /// #need_forking themselves. This populates #variants_to_build with the called functions.
+ void BuildRootFns() {
+ for (auto* fn : ir.functions) {
+ if (!need_forking.Contains(fn)) {
+ TransformCalls(fn);
+ }
+ }
+ }
+
+ /// Applies the necessary transformations to all the pointer arguments of calls made to
+ /// functions in #need_forking. Also populates #variants_to_build with the variants of the
+ /// callee functions.
+ /// @param fn the function to transform
+ void TransformCalls(Function* fn) {
+ // For all the function calls in the function...
+ Traverse(fn->Block(), [&](UserCall* call) {
+ auto* target = call->Target();
+ auto target_info = need_forking.Get(target);
+ if (!target_info) {
+ // Not a call to a function in #need_forking. Leave alone.
+ return;
+ }
+
+ // Found a call to a function in #need_forking.
+ // This call needs transforming to call the generated variant.
+
+ // New arguments to the call. This includes transformed and untransformed arguments.
+ Vector<Value*, 8> new_args;
+
+ // Pointer arguments that are being replaced.
+ Vector<Value*, 8> replaced_args;
+
+ // Signature of the callee variant
+ VariantSignature signature;
+
+ // For each argument / parameter...
+ 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)) {
+ // 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
+ // parameter or a function-scoped variable, and the access chain has no
+ // indicies.
+ // * A single indices array argument: The root is a module-scope var and the
+ // access chain has indices.
+ // * Both a pointer argument and indices array argument: The root is a pointer
+ // parameter or a function-scoped variable and the access chain has indices.
+ b.InsertBefore(call, [&] {
+ // Get the access chain for the pointer argument.
+ auto chain = AccessChainFor(arg);
+ // If the root is not a module-scope variable, then pass this root pointer
+ // as an argument.
+ if (std::holds_alternative<RootPtrParameter>(chain.shape.root)) {
+ new_args.Push(chain.root_ptr);
+ }
+ // If the chain access contains indices, then pass these as an array of u32.
+ if (size_t array_len = chain.indices.Length(); array_len > 0) {
+ auto* array = ty.array(ty.u32(), static_cast<uint32_t>(array_len));
+ auto* indices = b.Construct(array, std::move(chain.indices));
+ new_args.Push(indices->Result());
+ }
+ // Record the parameter shape for the variant's signature.
+ signature.Add(i, chain.shape);
+ });
+ // Record that this pointer argument has been replaced.
+ replaced_args.Push(arg);
+ } else {
+ // Argument does not need transformation.
+ // Push the existing argument to new_args.
+ new_args.Push(arg);
+ }
+ }
+
+ // Replace the call's arguments with new_args.
+ call->SetArgs(std::move(new_args));
+
+ // Clean up instructions that provided the now unused argument values.
+ for (auto* old_arg : replaced_args) {
+ DeleteDeadInstructions(old_arg);
+ }
+
+ // Look to see if this callee signature already has a variant created.
+ auto* new_target = (*target_info)->variants_by_sig.GetOrCreate(signature, [&] {
+ // New signature.
+
+ // Clone the original function to seed the new variant.
+ auto* variant_fn = CloneContext{ir}.Clone(target);
+ (*target_info)->ordered_variants.Push(variant_fn);
+
+ // Build a unique name for the variant.
+ if (auto fn_name = ir.NameOf(variant_fn); fn_name.IsValid()) {
+ StringStream variant_name;
+ variant_name << fn_name.NameView();
+ auto params = signature.Keys().Sort();
+ for (auto param_idx : params) {
+ variant_name << "_" << AccessShapeName(*signature.Get(param_idx));
+ }
+ ir.SetName(variant_fn, variant_name.str());
+ }
+
+ // Create an entry for the variant, and add it to the queue of variants that need to
+ // be built. We don't do this here to avoid unbounded stack usage.
+ variants_to_build.Push(FnVariant{/* signature */ signature,
+ /* fn */ variant_fn,
+ /* info */ *target_info});
+ return variant_fn;
+ });
+
+ // Repoint the target of the call to the variant.
+ call->SetTarget(new_target);
+ });
+ }
+
+ /// Builds all the variants in #variants_to_build by:
+ /// * Replacing the pointer parameters with zero, one or two parameters (root pointer, indices).
+ /// * Transforming any calls made by that variant to other functions found in #need_forking.
+ /// Note: The transformation of calls can add more variants to #variants_to_build.
+ /// BuildFnVariants() will continue to build variants until #variants_to_build is empty.
+ void BuildFnVariants() {
+ while (!variants_to_build.IsEmpty()) {
+ auto variant = variants_to_build.Pop();
+ BuildFnVariantParams(variant);
+ TransformCalls(variant.fn);
+ }
+ }
+
+ /// 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
+ /// @return an AccessChain
+ AccessChain AccessChainFor(Value* value) {
+ AccessChain chain;
+ while (value) {
+ TINT_ASSERT(value->Alive());
+ value = tint::Switch(
+ value, //
+ [&](InstructionResult* res) {
+ // value was emitted by an instruction
+ auto* inst = res->Source();
+ return tint::Switch(
+ inst,
+ [&](Access* access) {
+ // The AccessOp of this access instruction
+ Vector<AccessOp, 8> ops;
+ // The ordered, non-member accesses performed by this access instruction
+ Vector<Value*, 8> indices;
+ // The pointee-type that each access is being performed on
+ auto* obj_ty = access->Object()->Type()->UnwrapPtr();
+
+ // For each access operation...
+ for (auto idx : access->Indices()) {
+ if (auto* str = obj_ty->As<type::Struct>()) {
+ // Struct type accesses must be constant, representing the index
+ // of the member being accessed.
+ TINT_ASSERT(idx->Is<Constant>());
+ auto i = idx->As<Constant>()->Value()->ValueAs<uint32_t>();
+ auto* member = str->Members()[i];
+ ops.Push(MemberAccess{member});
+ obj_ty = member->Type();
+ continue;
+ }
+
+ // Array or matrix access.
+ // Convert index to u32 if it isn't already.
+ if (!idx->Type()->Is<type::U32>()) {
+ idx = b.Convert(ty.u32(), idx)->Result();
+ }
+
+ ops.Push(IndexAccess{});
+ indices.Push(idx);
+ obj_ty = obj_ty->Elements().type;
+ }
+
+ // Push the ops and indices in reverse order to the chain. This is done
+ // so we can continue to walk the IR values and push accesses (without
+ // insertion) that bring us closer to the pointer root. These are
+ // reversed again once the root variable is found.
+ for (auto& op : Reverse(ops)) {
+ chain.shape.ops.Push(op);
+ }
+ for (auto& idx : Reverse(indices)) {
+ chain.indices.Push(idx);
+ }
+
+ TINT_ASSERT(obj_ty == access->Result()->Type()->UnwrapPtr());
+ return access->Object();
+ },
+ [&](Var* var) {
+ // A 'var' is a pointer root.
+ if (var->Block() == ir.root_block) {
+ // Root pointer is a module-scope 'var'
+ chain.shape.root = RootModuleScopeVar{var};
+ } else {
+ // Root pointer is a function-scope 'var'
+ chain.shape.root =
+ RootPtrParameter{var->Result()->Type()->As<type::Pointer>()};
+ }
+ chain.root_ptr = var->Result();
+ return nullptr;
+ },
+ [&](Let* let) { return let->Value(); },
+ [&](Default) {
+ TINT_ICE() << "unhandled instruction type: "
+ << (inst ? inst->TypeInfo().name : "<null>");
+ return nullptr;
+ });
+ },
+ [&](FunctionParam* param) {
+ // Root pointer is a parameter of the caller
+ chain.shape.root = RootPtrParameter{param->Type()->As<type::Pointer>()};
+ chain.root_ptr = param;
+ return nullptr;
+ },
+ [&](Default) {
+ TINT_ICE() << "unhandled value type: "
+ << (value ? value->TypeInfo().name : "<null>");
+ return nullptr;
+ });
+ }
+
+ // Reverse the chain's ops and indices. See above for why.
+ chain.shape.ops.Reverse();
+ chain.indices.Reverse();
+
+ return chain;
+ }
+
+ /// Replaces the pointer parameters that need transforming of the variant function @p variant.
+ /// Instructions are inserted at the top of the @p variant function block to reconstruct the
+ /// pointer parameters from the access chain using the root pointer and access ops.
+ /// @param variant the variant function to transform
+ void BuildFnVariantParams(const FnVariant& variant) {
+ // Insert new instructions at the top of the function block...
+ b.InsertBefore(variant.fn->Block()->Front(), [&] {
+ // The replacement parameters for the variant function
+ Vector<ir::FunctionParam*, 8> new_params;
+ const auto& old_params = variant.fn->Params();
+ // 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)) {
+ // Parameter does not need transforming.
+ new_params.Push(old_param);
+ continue;
+ }
+
+ // Pointer parameter that needs transforming
+ // Grab the access shape of the pointer parameter from the signature
+ auto shape = variant.signature.Get(param_idx);
+ // The root pointer value
+ Value* root_ptr = nullptr;
+
+ // Build the root pointer parameter, if required.
+ FunctionParam* root_ptr_param = nullptr;
+ if (auto* ptr_param = std::get_if<RootPtrParameter>(&shape->root)) {
+ // Root pointer is passed as a parameter
+ root_ptr_param = b.FunctionParam(ptr_param->type);
+ new_params.Push(root_ptr_param);
+ root_ptr = root_ptr_param;
+ } else if (auto* global = std::get_if<RootModuleScopeVar>(&shape->root)) {
+ // Root pointer is a module-scope var
+ root_ptr = global->var->Result();
+ } else {
+ 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);
+ }
+
+ // 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();
+ });
+ 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());
+ old_param->Destroy();
+ }
+
+ // Replace the function's parameters
+ variant.fn->SetParams(std::move(new_params));
+ });
+ }
+
+ /// Repopulates #ir.functions with the functions in #need_forking replaced with their generated
+ /// variants.
+ /// @param input_fns the content of #ir.functions before transformation began.
+ void EmitFunctions(VectorRef<Function*> input_fns) {
+ ir.functions.Clear();
+ for (auto* fn : input_fns) {
+ if (auto info = need_forking.Get(fn)) {
+ for (auto variant : (*info)->ordered_variants) {
+ ir.functions.Push(variant);
+ }
+ } else {
+ ir.functions.Push(fn);
+ }
+ }
+ }
+
+ /// @returns a string describing the given AccessShape, used to suffix the generated function
+ /// variants.
+ std::string AccessShapeName(const AccessShape& shape) {
+ StringStream ss;
+
+ if (auto* global = std::get_if<RootModuleScopeVar>(&shape.root)) {
+ ss << ir.NameOf(global->var).NameView();
+ } else {
+ ss << "P";
+ }
+
+ for (auto& op : shape.ops) {
+ ss << "_";
+
+ if (std::holds_alternative<IndexAccess>(op)) {
+ /// The op uses an index taken from an array parameter.
+ ss << "X";
+ continue;
+ }
+
+ if (auto* access = std::get_if<MemberAccess>(&op); TINT_LIKELY(access)) {
+ ss << access->member->Name().NameView();
+ continue;
+ }
+
+ TINT_ICE() << "unhandled variant for access chain";
+ break;
+ }
+ return ss.str();
+ }
+
+ /// @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
+ bool ParamNeedsTransforming(FunctionParam* param) const {
+ if (auto* ptr = param->Type()->As<type::Pointer>()) {
+ switch (ptr->AddressSpace()) {
+ case core::AddressSpace::kStorage:
+ case core::AddressSpace::kUniform:
+ case core::AddressSpace::kWorkgroup:
+ return true;
+ case core::AddressSpace::kFunction:
+ return options.transform_function;
+ case core::AddressSpace::kPrivate:
+ return options.transform_private;
+ default:
+ break;
+ }
+ }
+ return false;
+ }
+
+ /// Walks the instructions that built @p value, deleting those that are no longer used.
+ /// @param value the pointer value that was used as a now replaced pointer argument.
+ void DeleteDeadInstructions(ir::Value* value) {
+ // While value has no uses...
+ while (value && value->Usages().Count() == 0) {
+ auto* inst_res = value->As<InstructionResult>();
+ if (!inst_res) {
+ return; // Only instructions can be removed.
+ }
+ value = tint::Switch(
+ inst_res->Source(), //
+ [&](Access* access) {
+ TINT_DEFER(access->Destroy());
+ return access->Object();
+ },
+ [&](Let* let) {
+ TINT_DEFER(let->Destroy());
+ return let->Value();
+ });
+ }
+ }
+};
+
+} // namespace
+
+Result<SuccessType> DirectVariableAccess(Module& ir, const DirectVariableAccessOptions& options) {
+ auto result = ValidateAndDumpIfNeeded(ir, "DirectVariableAccess transform");
+ if (!result) {
+ return result;
+ }
+
+ State{ir, options}.Process();
+
+ return Success;
+}
+
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.h b/src/tint/lang/core/ir/transform/direct_variable_access.h
new file mode 100644
index 0000000..5f6917f
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.h
@@ -0,0 +1,53 @@
+// 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.
+
+#ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// DirectVariableAccessOptions adjusts the behaviour of the transform.
+struct DirectVariableAccessOptions {
+ /// If true, then 'private' sub-object pointer arguments will be transformed.
+ bool transform_private = false;
+ /// If true, then 'function' sub-object pointer arguments will be transformed.
+ bool transform_function = false;
+};
+
+/// DirectVariableAccess is a transform that transforms pointer parameters in the 'storage',
+/// 'uniform' and 'workgroup' address space so that they're accessed directly by the function,
+/// instead of being passed by pointer.
+///
+/// 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.
+///
+/// @param module the module to transform
+/// @returns error diagnostics on failure
+Result<SuccessType> DirectVariableAccess(Module& module,
+ const DirectVariableAccessOptions& options);
+
+} // namespace tint::core::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
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
new file mode 100644
index 0000000..5bccb80
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
@@ -0,0 +1,4823 @@
+// 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/direct_variable_access.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+#include "src/tint/lang/core/type/array.h"
+#include "src/tint/lang/core/type/matrix.h"
+#include "src/tint/lang/core/type/pointer.h"
+#include "src/tint/lang/core/type/struct.h"
+
+#include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h"
+#include "src/tint/lang/wgsl/reader/reader.h"
+#include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h"
+#include "src/tint/lang/wgsl/writer/writer.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace {
+
+static constexpr DirectVariableAccessOptions kTransformPrivate = {
+ /* transform_private */ true,
+ /* transform_function */ false,
+};
+
+static constexpr DirectVariableAccessOptions kTransformFunction = {
+ /* transform_private */ false,
+ /* transform_function */ true,
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// remove uncalled
+////////////////////////////////////////////////////////////////////////////////
+namespace remove_uncalled {
+
+using IR_DirectVariableAccessTest_RemoveUncalled = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrUniform) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* u = b.Function("u", ty.i32());
+ auto* p = b.FunctionParam("p", ty.ptr<uniform, i32, read>());
+ u->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(u->Block(), [&] { b.Return(u, b.Load(p)); });
+
+ auto* src = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%u = func(%pre:i32, %p:ptr<uniform, i32, read>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrStorage) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* s = b.Function("s", ty.i32());
+ auto* p = b.FunctionParam("p", ty.ptr<storage, i32, read>());
+ s->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(s->Block(), [&] { b.Return(s, b.Load(p)); });
+
+ auto* src = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%s = func(%pre:i32, %p:ptr<storage, i32, read>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrWorkgroup) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* w = b.Function("w", ty.i32());
+ auto* p = b.FunctionParam("p", ty.ptr<workgroup, i32>());
+ w->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(w->Block(), [&] { b.Return(w, b.Load(p)); });
+
+ auto* src = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%w = func(%pre:i32, %p:ptr<workgroup, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrPrivate_Disabled) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* f = b.Function("f", ty.i32());
+ auto* p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ f->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); });
+
+ auto* src = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%f = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrPrivate_Enabled) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* f = b.Function("f", ty.i32());
+ auto* p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ f->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); });
+
+ auto* src = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%f = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+)";
+ Run(DirectVariableAccess, kTransformPrivate);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrFunction_Disabled) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* f = b.Function("f", ty.i32());
+ auto* p = b.FunctionParam("p", ty.ptr<function, i32>());
+ f->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); });
+
+ auto* src = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%f = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_RemoveUncalled, PtrFunction_Enabled) {
+ b.Append(b.ir.root_block, [&] { b.Var<private_>("keep_me", 42_i); });
+
+ auto* f = b.Function("f", ty.i32());
+ auto* p = b.FunctionParam("p", ty.ptr<function, i32>());
+ f->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(f->Block(), [&] { b.Return(f, b.Load(p)); });
+
+ auto* src = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+%f = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %keep_me:ptr<private, i32, read_write> = var, 42i
+}
+
+)";
+
+ Run(DirectVariableAccess, kTransformFunction);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace remove_uncalled
+
+////////////////////////////////////////////////////////////////////////////////
+// pointer chains
+////////////////////////////////////////////////////////////////////////////////
+namespace pointer_chains_tests {
+
+using IR_DirectVariableAccessTest_PtrChains = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_PtrChains, ConstantIndices) {
+ Var* U = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ U = b.Var<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>("U");
+ U->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.vec4<i32>());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, vec4<i32>, read>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* p0 = b.Let("p0", U);
+ auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>, read>(), p0, 1_i);
+ b.ir.SetName(p1, "p1");
+ auto* p2 = b.Access(ty.ptr<uniform, array<vec4<i32>, 8>, read>(), p1, 2_i);
+ b.ir.SetName(p2, "p2");
+ auto* p3 = b.Access(ty.ptr<uniform, vec4<i32>, read>(), p2, 3_i);
+ b.ir.SetName(p3, "p3");
+ b.Call(ty.vec4<i32>(), fn_a, 10_i, p3, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* fn_c = b.Function("c", ty.void_());
+ auto* fn_c_p =
+ b.FunctionParam("p", ty.ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>());
+ fn_c->SetParams({fn_c_p});
+ b.Append(fn_c->Block(), [&] {
+ auto* p0 = b.Let("p0", fn_c_p);
+ auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>, read>(), p0, 1_i);
+ b.ir.SetName(p1, "p1");
+ auto* p2 = b.Access(ty.ptr<uniform, array<vec4<i32>, 8>, read>(), p1, 2_i);
+ b.ir.SetName(p2, "p2");
+ auto* p3 = b.Access(ty.ptr<uniform, vec4<i32>, read>(), p2, 3_i);
+ b.ir.SetName(p3, "p3");
+ b.Call(ty.vec4<i32>(), fn_a, 10_i, p3, 20_i);
+ b.Return(fn_c);
+ });
+
+ auto* fn_d = b.Function("d", ty.void_());
+ b.Append(fn_d->Block(), [&] {
+ b.Call(ty.void_(), fn_c, U);
+ b.Return(fn_d);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0)
+}
+
+%a = func(%pre:i32, %p:ptr<uniform, vec4<i32>, read>, %post:i32):vec4<i32> -> %b2 {
+ %b2 = block {
+ %6:vec4<i32> = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %p0:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %U
+ %p1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0, 1i
+ %p2:ptr<uniform, array<vec4<i32>, 8>, read> = access %p1, 2i
+ %p3:ptr<uniform, vec4<i32>, read> = access %p2, 3i
+ %12:vec4<i32> = call %a, 10i, %p3, 20i
+ ret
+ }
+}
+%c = func(%p_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>):void -> %b4 { # %p_1: 'p'
+ %b4 = block {
+ %p0_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %p_1 # %p0_1: 'p0'
+ %p1_1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0_1, 1i # %p1_1: 'p1'
+ %p2_1:ptr<uniform, array<vec4<i32>, 8>, read> = access %p1_1, 2i # %p2_1: 'p2'
+ %p3_1:ptr<uniform, vec4<i32>, read> = access %p2_1, 3i # %p3_1: 'p3'
+ %19:vec4<i32> = call %a, 10i, %p3_1, 20i
+ ret
+ }
+}
+%d = func():void -> %b5 {
+ %b5 = block {
+ %21:void = call %c, %U
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect =
+ R"(
+%b1 = block { # root
+ %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0)
+}
+
+%a_U_X_X_X = func(%pre:i32, %p_indices:array<u32, 3>, %post:i32):vec4<i32> -> %b2 {
+ %b2 = block {
+ %6:u32 = access %p_indices, 0u
+ %7:u32 = access %p_indices, 1u
+ %8:u32 = access %p_indices, 2u
+ %9:ptr<uniform, vec4<i32>, read> = access %U, %6, %7, %8
+ %10:vec4<i32> = load %9
+ ret %10
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %12:u32 = convert 3i
+ %13:u32 = convert 2i
+ %14:u32 = convert 1i
+ %15:array<u32, 3> = construct %14, %13, %12
+ %16:vec4<i32> = call %a_U_X_X_X, 10i, %15, 20i
+ ret
+ }
+}
+%c_U = func():void -> %b4 {
+ %b4 = block {
+ %18:u32 = convert 3i
+ %19:u32 = convert 2i
+ %20:u32 = convert 1i
+ %21:array<u32, 3> = construct %20, %19, %18
+ %22:vec4<i32> = call %a_U_X_X_X, 10i, %21, 20i
+ ret
+ }
+}
+%d = func():void -> %b5 {
+ %b5 = block {
+ %24:void = call %c_U
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PtrChains, DynamicIndices) {
+ Var* U = nullptr;
+ Var* i = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ U = b.Var<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>("U");
+ U->SetBindingPoint(0, 0);
+ i = b.Var<private_, i32>("i");
+ });
+
+ auto* fn_first = b.Function("first", ty.i32());
+ auto* fn_second = b.Function("second", ty.i32());
+ auto* fn_third = b.Function("third", ty.i32());
+ for (auto fn : {fn_first, fn_second, fn_third}) {
+ b.Append(fn->Block(), [&] {
+ b.Store(i, b.Add(ty.i32(), b.Load(i), 1_i));
+ b.Return(fn, b.Load(i));
+ });
+ }
+
+ auto* fn_a = b.Function("a", ty.vec4<i32>());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, vec4<i32>, read>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* p0 = b.Let("p0", U);
+ auto* first = b.Call(fn_first);
+ auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>, read>(), p0, first);
+ b.ir.SetName(p1, "p1");
+ auto* second = b.Call(fn_second);
+ auto* third = b.Call(fn_third);
+ auto* p2 = b.Access(ty.ptr<uniform, vec4<i32>, read>(), p1, second, third);
+ b.ir.SetName(p2, "p2");
+ b.Call(ty.vec4<i32>(), fn_a, 10_i, p2, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* fn_c = b.Function("c", ty.void_());
+ auto* fn_c_p =
+ b.FunctionParam("p", ty.ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>());
+ fn_c->SetParams({fn_c_p});
+ b.Append(fn_c->Block(), [&] {
+ auto* p0 = b.Let("p0", fn_c_p);
+ auto* first = b.Call(fn_first);
+ auto* p1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 8>, 8>, read>(), p0, first);
+ b.ir.SetName(p1, "p1");
+ auto* second = b.Call(fn_second);
+ auto* third = b.Call(fn_third);
+ auto* p2 = b.Access(ty.ptr<uniform, vec4<i32>, read>(), p1, second, third);
+ b.ir.SetName(p2, "p2");
+ b.Call(ty.vec4<i32>(), fn_a, 10_i, p2, 20_i);
+ b.Return(fn_c);
+ });
+
+ auto* fn_d = b.Function("d", ty.void_());
+ b.Append(fn_d->Block(), [&] {
+ b.Call(ty.void_(), fn_c, U);
+ b.Return(fn_d);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0)
+ %i:ptr<private, i32, read_write> = var
+}
+
+%first = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, 1i
+ store %i, %5
+ %6:i32 = load %i
+ ret %6
+ }
+}
+%second = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = load %i
+ %9:i32 = add %8, 1i
+ store %i, %9
+ %10:i32 = load %i
+ ret %10
+ }
+}
+%third = func():i32 -> %b4 {
+ %b4 = block {
+ %12:i32 = load %i
+ %13:i32 = add %12, 1i
+ store %i, %13
+ %14:i32 = load %i
+ ret %14
+ }
+}
+%a = func(%pre:i32, %p:ptr<uniform, vec4<i32>, read>, %post:i32):vec4<i32> -> %b5 {
+ %b5 = block {
+ %19:vec4<i32> = load %p
+ ret %19
+ }
+}
+%b = func():void -> %b6 {
+ %b6 = block {
+ %p0:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %U
+ %22:i32 = call %first
+ %p1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0, %22
+ %24:i32 = call %second
+ %25:i32 = call %third
+ %p2:ptr<uniform, vec4<i32>, read> = access %p1, %24, %25
+ %27:vec4<i32> = call %a, 10i, %p2, 20i
+ ret
+ }
+}
+%c = func(%p_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read>):void -> %b7 { # %p_1: 'p'
+ %b7 = block {
+ %p0_1:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = let %p_1 # %p0_1: 'p0'
+ %31:i32 = call %first
+ %p1_1:ptr<uniform, array<array<vec4<i32>, 8>, 8>, read> = access %p0_1, %31 # %p1_1: 'p1'
+ %33:i32 = call %second
+ %34:i32 = call %third
+ %p2_1:ptr<uniform, vec4<i32>, read> = access %p1_1, %33, %34 # %p2_1: 'p2'
+ %36:vec4<i32> = call %a, 10i, %p2_1, 20i
+ ret
+ }
+}
+%d = func():void -> %b8 {
+ %b8 = block {
+ %38:void = call %c, %U
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %U:ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>, read> = var @binding_point(0, 0)
+ %i:ptr<private, i32, read_write> = var
+}
+
+%first = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, 1i
+ store %i, %5
+ %6:i32 = load %i
+ ret %6
+ }
+}
+%second = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = load %i
+ %9:i32 = add %8, 1i
+ store %i, %9
+ %10:i32 = load %i
+ ret %10
+ }
+}
+%third = func():i32 -> %b4 {
+ %b4 = block {
+ %12:i32 = load %i
+ %13:i32 = add %12, 1i
+ store %i, %13
+ %14:i32 = load %i
+ ret %14
+ }
+}
+%a_U_X_X_X = func(%pre:i32, %p_indices:array<u32, 3>, %post:i32):vec4<i32> -> %b5 {
+ %b5 = block {
+ %19:u32 = access %p_indices, 0u
+ %20:u32 = access %p_indices, 1u
+ %21:u32 = access %p_indices, 2u
+ %22:ptr<uniform, vec4<i32>, read> = access %U, %19, %20, %21
+ %23:vec4<i32> = load %22
+ ret %23
+ }
+}
+%b = func():void -> %b6 {
+ %b6 = block {
+ %25:i32 = call %first
+ %26:i32 = call %second
+ %27:i32 = call %third
+ %28:u32 = convert %26
+ %29:u32 = convert %27
+ %30:u32 = convert %25
+ %31:array<u32, 3> = construct %30, %28, %29
+ %32:vec4<i32> = call %a_U_X_X_X, 10i, %31, 20i
+ ret
+ }
+}
+%c_U = func():void -> %b7 {
+ %b7 = block {
+ %34:i32 = call %first
+ %35:i32 = call %second
+ %36:i32 = call %third
+ %37:u32 = convert %35
+ %38:u32 = convert %36
+ %39:u32 = convert %34
+ %40:array<u32, 3> = construct %39, %37, %38
+ %41:vec4<i32> = call %a_U_X_X_X, 10i, %40, 20i
+ ret
+ }
+}
+%d = func():void -> %b8 {
+ %b8 = block {
+ %43:void = call %c_U
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace pointer_chains_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'uniform' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace uniform_as_tests {
+
+using IR_DirectVariableAccessTest_UniformAS = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_UniformAS, Param_ptr_i32_read) {
+ Var* U = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ U = b.Var<uniform, i32, read>("U");
+ U->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, i32, read>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(fn_a, 10_i, U, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %U:ptr<uniform, i32, read> = var @binding_point(0, 0)
+}
+
+%a = func(%pre:i32, %p:ptr<uniform, i32, read>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 10i, %U, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %U:ptr<uniform, i32, read> = var @binding_point(0, 0)
+}
+
+%a_U = func(%pre:i32, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %5:ptr<uniform, i32, read> = access %U
+ %6:i32 = load %5
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a_U, 10i, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_UniformAS, Param_ptr_vec4i32_Via_array_DynamicRead) {
+ Var* U = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ U = b.Var<uniform, array<vec4<i32>, 8>, read>("U");
+ U->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.vec4<i32>());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<uniform, vec4<i32>, read>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* I = b.Let("I", 3_i);
+ auto* access = b.Access(ty.ptr<uniform, vec4<i32>, read>(), U, I);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %U:ptr<uniform, array<vec4<i32>, 8>, read> = var @binding_point(0, 0)
+}
+
+%a = func(%pre:i32, %p:ptr<uniform, vec4<i32>, read>, %post:i32):vec4<i32> -> %b2 {
+ %b2 = block {
+ %6:vec4<i32> = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %I:i32 = let 3i
+ %9:ptr<uniform, vec4<i32>, read> = access %U, %I
+ %10:vec4<i32> = call %a, 10i, %9, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %U:ptr<uniform, array<vec4<i32>, 8>, read> = var @binding_point(0, 0)
+}
+
+%a_U_X = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):vec4<i32> -> %b2 {
+ %b2 = block {
+ %6:u32 = access %p_indices, 0u
+ %7:ptr<uniform, vec4<i32>, read> = access %U, %6
+ %8:vec4<i32> = load %7
+ ret %8
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %I:i32 = let 3i
+ %11:u32 = convert %I
+ %12:array<u32, 1> = construct %11
+ %13:vec4<i32> = call %a_U_X, 10i, %12, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_UniformAS, CallChaining) {
+ auto* Inner =
+ ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ auto* Outer =
+ ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.Register("arr"), ty.array(Inner, 4)},
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ Var* U = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ U = b.Var("U", ty.ptr<uniform, read>(Outer));
+ U->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_0 = b.Function("f0", ty.f32());
+ auto* fn_0_p = b.FunctionParam("p", ty.ptr<uniform, vec4<f32>, read>());
+ fn_0->SetParams({fn_0_p});
+ b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); });
+
+ auto* fn_1 = b.Function("f1", ty.f32());
+ auto* fn_1_p = b.FunctionParam("p", ty.ptr<uniform, mat3x4<f32>, read>());
+ fn_1->SetParams({fn_1_p});
+ b.Append(fn_1->Block(), [&] {
+ auto* res = b.Var<function, f32>("res");
+ {
+ // res += f0(&(*p)[1]);
+ auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<uniform, vec4<f32>, read>(), fn_1_p, 1_i));
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &(*p)[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<uniform, vec4<f32>, read>(), fn_1_p, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // res += f0(&U.arr[2].mat[1]);
+ auto* access = b.Access(ty.ptr<uniform, vec4<f32>, read>(), U, 0_u, 2_i, 0_u, 1_i);
+ auto* call_0 = b.Call(fn_0, access);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &U.arr[2].mat[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<uniform, vec4<f32>, read>(), U, 0_u, 2_i, 0_u, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+
+ b.Return(fn_1, b.Load(res));
+ });
+
+ auto* fn_2 = b.Function("f2", ty.f32());
+ auto* fn_2_p = b.FunctionParam("p", ty.ptr<uniform, read>(Inner));
+ fn_2->SetParams({fn_2_p});
+ b.Append(fn_2->Block(), [&] {
+ auto* p_mat = b.Access(ty.ptr<uniform, mat3x4<f32>, read>(), fn_2_p, 0_u);
+ b.ir.SetName(p_mat, "p_mat");
+ b.Return(fn_2, b.Call(fn_1, p_mat));
+ });
+
+ auto* fn_3 = b.Function("f3", ty.f32());
+ auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<uniform, read>(ty.array(Inner, 4)));
+ auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<uniform, mat3x4<f32>, read>());
+ fn_3->SetParams({fn_3_p0, fn_3_p1});
+ b.Append(fn_3->Block(), [&] {
+ auto* p0_inner = b.Access(ty.ptr<uniform, read>(Inner), fn_3_p0, 3_i);
+ b.ir.SetName(p0_inner, "p0_inner");
+ auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner);
+ auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1);
+ b.Return(fn_3, b.Add(ty.f32(), call_0, call_1));
+ });
+
+ auto* fn_4 = b.Function("f4", ty.f32());
+ auto* fn_4_p = b.FunctionParam("p", ty.ptr<uniform, read>(Outer));
+ fn_4->SetParams({fn_4_p});
+ b.Append(fn_4->Block(), [&] {
+ auto* access_0 = b.Access(ty.ptr<uniform, read>(ty.array(Inner, 4)), fn_4_p, 0_u);
+ auto* access_1 = b.Access(ty.ptr<uniform, mat3x4<f32>, read>(), U, 1_u);
+ b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1));
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(ty.f32(), fn_4, U);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %U:ptr<uniform, Outer, read> = var @binding_point(0, 0)
+}
+
+%f0 = func(%p:ptr<uniform, vec4<f32>, read>):f32 -> %b2 {
+ %b2 = block {
+ %4:f32 = load_vector_element %p, 0u
+ ret %4
+ }
+}
+%f1 = func(%p_1:ptr<uniform, mat3x4<f32>, read>):f32 -> %b3 { # %p_1: 'p'
+ %b3 = block {
+ %res:ptr<function, f32, read_write> = var
+ %8:ptr<uniform, vec4<f32>, read> = access %p_1, 1i
+ %9:f32 = call %f0, %8
+ %10:f32 = load %res
+ %11:f32 = add %10, %9
+ store %res, %11
+ %p_vec:ptr<uniform, vec4<f32>, read> = access %p_1, 1i
+ %13:f32 = call %f0, %p_vec
+ %14:f32 = load %res
+ %15:f32 = add %14, %13
+ store %res, %15
+ %16:ptr<uniform, vec4<f32>, read> = access %U, 0u, 2i, 0u, 1i
+ %17:f32 = call %f0, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %p_vec_1:ptr<uniform, vec4<f32>, read> = access %U, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec'
+ %21:f32 = call %f0, %p_vec_1
+ %22:f32 = load %res
+ %23:f32 = add %22, %21
+ store %res, %23
+ %24:f32 = load %res
+ ret %24
+ }
+}
+%f2 = func(%p_2:ptr<uniform, Inner, read>):f32 -> %b4 { # %p_2: 'p'
+ %b4 = block {
+ %p_mat:ptr<uniform, mat3x4<f32>, read> = access %p_2, 0u
+ %28:f32 = call %f1, %p_mat
+ ret %28
+ }
+}
+%f3 = func(%p0:ptr<uniform, array<Inner, 4>, read>, %p1:ptr<uniform, mat3x4<f32>, read>):f32 -> %b5 {
+ %b5 = block {
+ %p0_inner:ptr<uniform, Inner, read> = access %p0, 3i
+ %33:f32 = call %f2, %p0_inner
+ %34:f32 = call %f1, %p1
+ %35:f32 = add %33, %34
+ ret %35
+ }
+}
+%f4 = func(%p_3:ptr<uniform, Outer, read>):f32 -> %b6 { # %p_3: 'p'
+ %b6 = block {
+ %38:ptr<uniform, array<Inner, 4>, read> = access %p_3, 0u
+ %39:ptr<uniform, mat3x4<f32>, read> = access %U, 1u
+ %40:f32 = call %f3, %38, %39
+ ret %40
+ }
+}
+%b = func():void -> %b7 {
+ %b7 = block {
+ %42:f32 = call %f4, %U
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %U:ptr<uniform, Outer, read> = var @binding_point(0, 0)
+}
+
+%f0_U_mat_X = func(%p_indices:array<u32, 1>):f32 -> %b2 {
+ %b2 = block {
+ %4:u32 = access %p_indices, 0u
+ %5:ptr<uniform, vec4<f32>, read> = access %U, 1u, %4
+ %6:f32 = load_vector_element %5, 0u
+ ret %6
+ }
+}
+%f0_U_arr_X_mat_X = func(%p_indices_1:array<u32, 2>):f32 -> %b3 { # %p_indices_1: 'p_indices'
+ %b3 = block {
+ %9:u32 = access %p_indices_1, 0u
+ %10:u32 = access %p_indices_1, 1u
+ %11:ptr<uniform, vec4<f32>, read> = access %U, 0u, %9, 0u, %10
+ %12:f32 = load_vector_element %11, 0u
+ ret %12
+ }
+}
+%f1_U_mat = func():f32 -> %b4 {
+ %b4 = block {
+ %res:ptr<function, f32, read_write> = var
+ %15:u32 = convert 1i
+ %16:array<u32, 1> = construct %15
+ %17:f32 = call %f0_U_mat_X, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %20:u32 = convert 1i
+ %21:array<u32, 1> = construct %20
+ %22:f32 = call %f0_U_mat_X, %21
+ %23:f32 = load %res
+ %24:f32 = add %23, %22
+ store %res, %24
+ %25:u32 = convert 2i
+ %26:u32 = convert 1i
+ %27:array<u32, 2> = construct %25, %26
+ %28:f32 = call %f0_U_arr_X_mat_X, %27
+ %29:f32 = load %res
+ %30:f32 = add %29, %28
+ store %res, %30
+ %31:u32 = convert 2i
+ %32:u32 = convert 1i
+ %33:array<u32, 2> = construct %31, %32
+ %34:f32 = call %f0_U_arr_X_mat_X, %33
+ %35:f32 = load %res
+ %36:f32 = add %35, %34
+ store %res, %36
+ %37:f32 = load %res
+ ret %37
+ }
+}
+%f1_U_arr_X_mat = func(%p_indices_2:array<u32, 1>):f32 -> %b5 { # %p_indices_2: 'p_indices'
+ %b5 = block {
+ %40:u32 = access %p_indices_2, 0u
+ %res_1:ptr<function, f32, read_write> = var # %res_1: 'res'
+ %42:u32 = convert 1i
+ %43:array<u32, 2> = construct %40, %42
+ %44:f32 = call %f0_U_arr_X_mat_X, %43
+ %45:f32 = load %res_1
+ %46:f32 = add %45, %44
+ store %res_1, %46
+ %47:u32 = convert 1i
+ %48:array<u32, 2> = construct %40, %47
+ %49:f32 = call %f0_U_arr_X_mat_X, %48
+ %50:f32 = load %res_1
+ %51:f32 = add %50, %49
+ store %res_1, %51
+ %52:u32 = convert 2i
+ %53:u32 = convert 1i
+ %54:array<u32, 2> = construct %52, %53
+ %55:f32 = call %f0_U_arr_X_mat_X, %54
+ %56:f32 = load %res_1
+ %57:f32 = add %56, %55
+ store %res_1, %57
+ %58:u32 = convert 2i
+ %59:u32 = convert 1i
+ %60:array<u32, 2> = construct %58, %59
+ %61:f32 = call %f0_U_arr_X_mat_X, %60
+ %62:f32 = load %res_1
+ %63:f32 = add %62, %61
+ store %res_1, %63
+ %64:f32 = load %res_1
+ ret %64
+ }
+}
+%f2_U_arr_X = func(%p_indices_3:array<u32, 1>):f32 -> %b6 { # %p_indices_3: 'p_indices'
+ %b6 = block {
+ %67:u32 = access %p_indices_3, 0u
+ %68:array<u32, 1> = construct %67
+ %69:f32 = call %f1_U_arr_X_mat, %68
+ ret %69
+ }
+}
+%f3_U_arr_U_mat = func():f32 -> %b7 {
+ %b7 = block {
+ %71:u32 = convert 3i
+ %72:array<u32, 1> = construct %71
+ %73:f32 = call %f2_U_arr_X, %72
+ %74:f32 = call %f1_U_mat
+ %75:f32 = add %73, %74
+ ret %75
+ }
+}
+%f4_U = func():f32 -> %b8 {
+ %b8 = block {
+ %77:f32 = call %f3_U_arr_U_mat
+ ret %77
+ }
+}
+%b = func():void -> %b9 {
+ %b9 = block {
+ %79:f32 = call %f4_U
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace uniform_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'storage' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace storage_as_tests {
+
+using IR_DirectVariableAccessTest_StorageAS = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_StorageAS, Param_ptr_i32_Via_struct_read) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ Var* S = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var("S", ty.ptr<storage, read>(str_));
+ S->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<storage, i32, read>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<storage, i32, read>(), S, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %S:ptr<storage, str, read> = var @binding_point(0, 0)
+}
+
+%a = func(%pre:i32, %p:ptr<storage, i32, read>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:ptr<storage, i32, read> = access %S, 0u
+ %9:i32 = call %a, 10i, %8, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %S:ptr<storage, str, read> = var @binding_point(0, 0)
+}
+
+%a_S_i = func(%pre:i32, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %5:ptr<storage, i32, read> = access %S, 0u
+ %6:i32 = load %5
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a_S_i, 10i, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_StorageAS, Param_ptr_arr_i32_Via_struct_write) {
+ auto* str_ =
+ ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("arr"), ty.array<i32, 4>()},
+ });
+
+ Var* S = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var("S", ty.ptr<storage>(str_));
+ S->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<storage, array<i32, 4>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, b.Splat(ty.array<i32, 4>(), 0_i, 4));
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<storage, array<i32, 4>>(), S, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %S:ptr<storage, str, read_write> = var @binding_point(0, 0)
+}
+
+%a = func(%pre:i32, %p:ptr<storage, array<i32, 4>, read_write>, %post:i32):void -> %b2 {
+ %b2 = block {
+ store %p, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:ptr<storage, array<i32, 4>, read_write> = access %S, 0u
+ %8:void = call %a, 10i, %7, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %S:ptr<storage, str, read_write> = var @binding_point(0, 0)
+}
+
+%a_S_arr = func(%pre:i32, %post:i32):void -> %b2 {
+ %b2 = block {
+ %5:ptr<storage, array<i32, 4>, read_write> = access %S, 0u
+ store %5, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:void = call %a_S_arr, 10i, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_StorageAS, Param_ptr_vec4i32_Via_array_DynamicWrite) {
+ Var* S = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var<storage, array<vec4<i32>, 8>>("S");
+ S->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<storage, vec4<i32>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, b.Splat(ty.vec4<i32>(), 0_i, 4));
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* I = b.Let("I", 3_i);
+ auto* access = b.Access(ty.ptr<storage, vec4<i32>>(), S, I);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<vec4<i32>, 8>, read_write> = var @binding_point(0, 0)
+}
+
+%a = func(%pre:i32, %p:ptr<storage, vec4<i32>, read_write>, %post:i32):void -> %b2 {
+ %b2 = block {
+ store %p, vec4<i32>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %I:i32 = let 3i
+ %8:ptr<storage, vec4<i32>, read_write> = access %S, %I
+ %9:void = call %a, 10i, %8, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<vec4<i32>, 8>, read_write> = var @binding_point(0, 0)
+}
+
+%a_S_X = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):void -> %b2 {
+ %b2 = block {
+ %6:u32 = access %p_indices, 0u
+ %7:ptr<storage, vec4<i32>, read_write> = access %S, %6
+ store %7, vec4<i32>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %I:i32 = let 3i
+ %10:u32 = convert %I
+ %11:array<u32, 1> = construct %10
+ %12:void = call %a_S_X, 10i, %11, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_StorageAS, CallChaining) {
+ auto* Inner =
+ ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ auto* Outer =
+ ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.Register("arr"), ty.array(Inner, 4)},
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ Var* S = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var("S", ty.ptr<storage, read>(Outer));
+ S->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_0 = b.Function("f0", ty.f32());
+ auto* fn_0_p = b.FunctionParam("p", ty.ptr<storage, vec4<f32>, read>());
+ fn_0->SetParams({fn_0_p});
+ b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); });
+
+ auto* fn_1 = b.Function("f1", ty.f32());
+ auto* fn_1_p = b.FunctionParam("p", ty.ptr<storage, mat3x4<f32>, read>());
+ fn_1->SetParams({fn_1_p});
+ b.Append(fn_1->Block(), [&] {
+ auto* res = b.Var<function, f32>("res");
+ {
+ // res += f0(&(*p)[1]);
+ auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<storage, vec4<f32>, read>(), fn_1_p, 1_i));
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &(*p)[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<storage, vec4<f32>, read>(), fn_1_p, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // res += f0(&U.arr[2].mat[1]);
+ auto* access = b.Access(ty.ptr<storage, vec4<f32>, read>(), S, 0_u, 2_i, 0_u, 1_i);
+ auto* call_0 = b.Call(fn_0, access);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &U.arr[2].mat[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<storage, vec4<f32>, read>(), S, 0_u, 2_i, 0_u, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+
+ b.Return(fn_1, b.Load(res));
+ });
+
+ auto* fn_2 = b.Function("f2", ty.f32());
+ auto* fn_2_p = b.FunctionParam("p", ty.ptr<storage, read>(Inner));
+ fn_2->SetParams({fn_2_p});
+ b.Append(fn_2->Block(), [&] {
+ auto* p_mat = b.Access(ty.ptr<storage, mat3x4<f32>, read>(), fn_2_p, 0_u);
+ b.ir.SetName(p_mat, "p_mat");
+ b.Return(fn_2, b.Call(fn_1, p_mat));
+ });
+
+ auto* fn_3 = b.Function("f3", ty.f32());
+ auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<storage, read>(ty.array(Inner, 4)));
+ auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<storage, mat3x4<f32>, read>());
+ fn_3->SetParams({fn_3_p0, fn_3_p1});
+ b.Append(fn_3->Block(), [&] {
+ auto* p0_inner = b.Access(ty.ptr<storage, read>(Inner), fn_3_p0, 3_i);
+ b.ir.SetName(p0_inner, "p0_inner");
+ auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner);
+ auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1);
+ b.Return(fn_3, b.Add(ty.f32(), call_0, call_1));
+ });
+
+ auto* fn_4 = b.Function("f4", ty.f32());
+ auto* fn_4_p = b.FunctionParam("p", ty.ptr<storage, read>(Outer));
+ fn_4->SetParams({fn_4_p});
+ b.Append(fn_4->Block(), [&] {
+ auto* access_0 = b.Access(ty.ptr<storage, read>(ty.array(Inner, 4)), fn_4_p, 0_u);
+ auto* access_1 = b.Access(ty.ptr<storage, mat3x4<f32>, read>(), S, 1_u);
+ b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1));
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(ty.f32(), fn_4, S);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %S:ptr<storage, Outer, read> = var @binding_point(0, 0)
+}
+
+%f0 = func(%p:ptr<storage, vec4<f32>, read>):f32 -> %b2 {
+ %b2 = block {
+ %4:f32 = load_vector_element %p, 0u
+ ret %4
+ }
+}
+%f1 = func(%p_1:ptr<storage, mat3x4<f32>, read>):f32 -> %b3 { # %p_1: 'p'
+ %b3 = block {
+ %res:ptr<function, f32, read_write> = var
+ %8:ptr<storage, vec4<f32>, read> = access %p_1, 1i
+ %9:f32 = call %f0, %8
+ %10:f32 = load %res
+ %11:f32 = add %10, %9
+ store %res, %11
+ %p_vec:ptr<storage, vec4<f32>, read> = access %p_1, 1i
+ %13:f32 = call %f0, %p_vec
+ %14:f32 = load %res
+ %15:f32 = add %14, %13
+ store %res, %15
+ %16:ptr<storage, vec4<f32>, read> = access %S, 0u, 2i, 0u, 1i
+ %17:f32 = call %f0, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %p_vec_1:ptr<storage, vec4<f32>, read> = access %S, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec'
+ %21:f32 = call %f0, %p_vec_1
+ %22:f32 = load %res
+ %23:f32 = add %22, %21
+ store %res, %23
+ %24:f32 = load %res
+ ret %24
+ }
+}
+%f2 = func(%p_2:ptr<storage, Inner, read>):f32 -> %b4 { # %p_2: 'p'
+ %b4 = block {
+ %p_mat:ptr<storage, mat3x4<f32>, read> = access %p_2, 0u
+ %28:f32 = call %f1, %p_mat
+ ret %28
+ }
+}
+%f3 = func(%p0:ptr<storage, array<Inner, 4>, read>, %p1:ptr<storage, mat3x4<f32>, read>):f32 -> %b5 {
+ %b5 = block {
+ %p0_inner:ptr<storage, Inner, read> = access %p0, 3i
+ %33:f32 = call %f2, %p0_inner
+ %34:f32 = call %f1, %p1
+ %35:f32 = add %33, %34
+ ret %35
+ }
+}
+%f4 = func(%p_3:ptr<storage, Outer, read>):f32 -> %b6 { # %p_3: 'p'
+ %b6 = block {
+ %38:ptr<storage, array<Inner, 4>, read> = access %p_3, 0u
+ %39:ptr<storage, mat3x4<f32>, read> = access %S, 1u
+ %40:f32 = call %f3, %38, %39
+ ret %40
+ }
+}
+%b = func():void -> %b7 {
+ %b7 = block {
+ %42:f32 = call %f4, %S
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %S:ptr<storage, Outer, read> = var @binding_point(0, 0)
+}
+
+%f0_S_mat_X = func(%p_indices:array<u32, 1>):f32 -> %b2 {
+ %b2 = block {
+ %4:u32 = access %p_indices, 0u
+ %5:ptr<storage, vec4<f32>, read> = access %S, 1u, %4
+ %6:f32 = load_vector_element %5, 0u
+ ret %6
+ }
+}
+%f0_S_arr_X_mat_X = func(%p_indices_1:array<u32, 2>):f32 -> %b3 { # %p_indices_1: 'p_indices'
+ %b3 = block {
+ %9:u32 = access %p_indices_1, 0u
+ %10:u32 = access %p_indices_1, 1u
+ %11:ptr<storage, vec4<f32>, read> = access %S, 0u, %9, 0u, %10
+ %12:f32 = load_vector_element %11, 0u
+ ret %12
+ }
+}
+%f1_S_mat = func():f32 -> %b4 {
+ %b4 = block {
+ %res:ptr<function, f32, read_write> = var
+ %15:u32 = convert 1i
+ %16:array<u32, 1> = construct %15
+ %17:f32 = call %f0_S_mat_X, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %20:u32 = convert 1i
+ %21:array<u32, 1> = construct %20
+ %22:f32 = call %f0_S_mat_X, %21
+ %23:f32 = load %res
+ %24:f32 = add %23, %22
+ store %res, %24
+ %25:u32 = convert 2i
+ %26:u32 = convert 1i
+ %27:array<u32, 2> = construct %25, %26
+ %28:f32 = call %f0_S_arr_X_mat_X, %27
+ %29:f32 = load %res
+ %30:f32 = add %29, %28
+ store %res, %30
+ %31:u32 = convert 2i
+ %32:u32 = convert 1i
+ %33:array<u32, 2> = construct %31, %32
+ %34:f32 = call %f0_S_arr_X_mat_X, %33
+ %35:f32 = load %res
+ %36:f32 = add %35, %34
+ store %res, %36
+ %37:f32 = load %res
+ ret %37
+ }
+}
+%f1_S_arr_X_mat = func(%p_indices_2:array<u32, 1>):f32 -> %b5 { # %p_indices_2: 'p_indices'
+ %b5 = block {
+ %40:u32 = access %p_indices_2, 0u
+ %res_1:ptr<function, f32, read_write> = var # %res_1: 'res'
+ %42:u32 = convert 1i
+ %43:array<u32, 2> = construct %40, %42
+ %44:f32 = call %f0_S_arr_X_mat_X, %43
+ %45:f32 = load %res_1
+ %46:f32 = add %45, %44
+ store %res_1, %46
+ %47:u32 = convert 1i
+ %48:array<u32, 2> = construct %40, %47
+ %49:f32 = call %f0_S_arr_X_mat_X, %48
+ %50:f32 = load %res_1
+ %51:f32 = add %50, %49
+ store %res_1, %51
+ %52:u32 = convert 2i
+ %53:u32 = convert 1i
+ %54:array<u32, 2> = construct %52, %53
+ %55:f32 = call %f0_S_arr_X_mat_X, %54
+ %56:f32 = load %res_1
+ %57:f32 = add %56, %55
+ store %res_1, %57
+ %58:u32 = convert 2i
+ %59:u32 = convert 1i
+ %60:array<u32, 2> = construct %58, %59
+ %61:f32 = call %f0_S_arr_X_mat_X, %60
+ %62:f32 = load %res_1
+ %63:f32 = add %62, %61
+ store %res_1, %63
+ %64:f32 = load %res_1
+ ret %64
+ }
+}
+%f2_S_arr_X = func(%p_indices_3:array<u32, 1>):f32 -> %b6 { # %p_indices_3: 'p_indices'
+ %b6 = block {
+ %67:u32 = access %p_indices_3, 0u
+ %68:array<u32, 1> = construct %67
+ %69:f32 = call %f1_S_arr_X_mat, %68
+ ret %69
+ }
+}
+%f3_S_arr_S_mat = func():f32 -> %b7 {
+ %b7 = block {
+ %71:u32 = convert 3i
+ %72:array<u32, 1> = construct %71
+ %73:f32 = call %f2_S_arr_X, %72
+ %74:f32 = call %f1_S_mat
+ %75:f32 = add %73, %74
+ ret %75
+ }
+}
+%f4_S = func():f32 -> %b8 {
+ %b8 = block {
+ %77:f32 = call %f3_S_arr_S_mat
+ ret %77
+ }
+}
+%b = func():void -> %b9 {
+ %b9 = block {
+ %79:f32 = call %f4_S
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace storage_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'workgroup' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace workgroup_as_tests {
+
+using IR_DirectVariableAccessTest_WorkgroupAS = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_WorkgroupAS, Param_ptr_vec4i32_Via_array_StaticRead) {
+ Var* W = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ W = b.Var("W", ty.ptr<workgroup, array<vec4<i32>, 8>>());
+ });
+
+ auto* fn_a = b.Function("a", ty.vec4<i32>());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<workgroup, vec4<i32>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<workgroup, vec4<i32>>(), W, 3_i);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<workgroup, vec4<i32>, read_write>, %post:i32):vec4<i32> -> %b2 {
+ %b2 = block {
+ %6:vec4<i32> = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:ptr<workgroup, vec4<i32>, read_write> = access %W, 3i
+ %9:vec4<i32> = call %a, 10i, %8, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var
+}
+
+%a_W_X = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):vec4<i32> -> %b2 {
+ %b2 = block {
+ %6:u32 = access %p_indices, 0u
+ %7:ptr<workgroup, vec4<i32>, read_write> = access %W, %6
+ %8:vec4<i32> = load %7
+ ret %8
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %10:u32 = convert 3i
+ %11:array<u32, 1> = construct %10
+ %12:vec4<i32> = call %a_W_X, 10i, %11, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_WorkgroupAS, Param_ptr_vec4i32_Via_array_StaticWrite) {
+ Var* W = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ W = b.Var<workgroup, array<vec4<i32>, 8>>("W");
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<workgroup, vec4<i32>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, b.Splat(ty.vec4<i32>(), 0_i, 4));
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<workgroup, vec4<i32>>(), W, 3_i);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<workgroup, vec4<i32>, read_write>, %post:i32):void -> %b2 {
+ %b2 = block {
+ store %p, vec4<i32>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:ptr<workgroup, vec4<i32>, read_write> = access %W, 3i
+ %8:void = call %a, 10i, %7, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %W:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var
+}
+
+%a_W_X = func(%pre:i32, %p_indices:array<u32, 1>, %post:i32):void -> %b2 {
+ %b2 = block {
+ %6:u32 = access %p_indices, 0u
+ %7:ptr<workgroup, vec4<i32>, read_write> = access %W, %6
+ store %7, vec4<i32>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %9:u32 = convert 3i
+ %10:array<u32, 1> = construct %9
+ %11:void = call %a_W_X, 10i, %10, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_WorkgroupAS, CallChaining) {
+ auto* Inner =
+ ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ auto* Outer =
+ ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.Register("arr"), ty.array(Inner, 4)},
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ Var* W = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ W = b.Var("W", ty.ptr<workgroup>(Outer));
+ });
+
+ auto* fn_0 = b.Function("f0", ty.f32());
+ auto* fn_0_p = b.FunctionParam("p", ty.ptr<workgroup, vec4<f32>>());
+ fn_0->SetParams({fn_0_p});
+ b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); });
+
+ auto* fn_1 = b.Function("f1", ty.f32());
+ auto* fn_1_p = b.FunctionParam("p", ty.ptr<workgroup, mat3x4<f32>>());
+ fn_1->SetParams({fn_1_p});
+ b.Append(fn_1->Block(), [&] {
+ auto* res = b.Var<function, f32>("res");
+ {
+ // res += f0(&(*p)[1]);
+ auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<workgroup, vec4<f32>>(), fn_1_p, 1_i));
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &(*p)[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<workgroup, vec4<f32>>(), fn_1_p, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // res += f0(&U.arr[2].mat[1]);
+ auto* access = b.Access(ty.ptr<workgroup, vec4<f32>>(), W, 0_u, 2_i, 0_u, 1_i);
+ auto* call_0 = b.Call(fn_0, access);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &U.arr[2].mat[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<workgroup, vec4<f32>>(), W, 0_u, 2_i, 0_u, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+
+ b.Return(fn_1, b.Load(res));
+ });
+
+ auto* fn_2 = b.Function("f2", ty.f32());
+ auto* fn_2_p = b.FunctionParam("p", ty.ptr<workgroup>(Inner));
+ fn_2->SetParams({fn_2_p});
+ b.Append(fn_2->Block(), [&] {
+ auto* p_mat = b.Access(ty.ptr<workgroup, mat3x4<f32>>(), fn_2_p, 0_u);
+ b.ir.SetName(p_mat, "p_mat");
+ b.Return(fn_2, b.Call(fn_1, p_mat));
+ });
+
+ auto* fn_3 = b.Function("f3", ty.f32());
+ auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<workgroup>(ty.array(Inner, 4)));
+ auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<workgroup, mat3x4<f32>>());
+ fn_3->SetParams({fn_3_p0, fn_3_p1});
+ b.Append(fn_3->Block(), [&] {
+ auto* p0_inner = b.Access(ty.ptr<workgroup>(Inner), fn_3_p0, 3_i);
+ b.ir.SetName(p0_inner, "p0_inner");
+ auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner);
+ auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1);
+ b.Return(fn_3, b.Add(ty.f32(), call_0, call_1));
+ });
+
+ auto* fn_4 = b.Function("f4", ty.f32());
+ auto* fn_4_p = b.FunctionParam("p", ty.ptr<workgroup>(Outer));
+ fn_4->SetParams({fn_4_p});
+ b.Append(fn_4->Block(), [&] {
+ auto* access_0 = b.Access(ty.ptr<workgroup>(ty.array(Inner, 4)), fn_4_p, 0_u);
+ auto* access_1 = b.Access(ty.ptr<workgroup, mat3x4<f32>>(), W, 1_u);
+ b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1));
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(ty.f32(), fn_4, W);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %W:ptr<workgroup, Outer, read_write> = var
+}
+
+%f0 = func(%p:ptr<workgroup, vec4<f32>, read_write>):f32 -> %b2 {
+ %b2 = block {
+ %4:f32 = load_vector_element %p, 0u
+ ret %4
+ }
+}
+%f1 = func(%p_1:ptr<workgroup, mat3x4<f32>, read_write>):f32 -> %b3 { # %p_1: 'p'
+ %b3 = block {
+ %res:ptr<function, f32, read_write> = var
+ %8:ptr<workgroup, vec4<f32>, read_write> = access %p_1, 1i
+ %9:f32 = call %f0, %8
+ %10:f32 = load %res
+ %11:f32 = add %10, %9
+ store %res, %11
+ %p_vec:ptr<workgroup, vec4<f32>, read_write> = access %p_1, 1i
+ %13:f32 = call %f0, %p_vec
+ %14:f32 = load %res
+ %15:f32 = add %14, %13
+ store %res, %15
+ %16:ptr<workgroup, vec4<f32>, read_write> = access %W, 0u, 2i, 0u, 1i
+ %17:f32 = call %f0, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %p_vec_1:ptr<workgroup, vec4<f32>, read_write> = access %W, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec'
+ %21:f32 = call %f0, %p_vec_1
+ %22:f32 = load %res
+ %23:f32 = add %22, %21
+ store %res, %23
+ %24:f32 = load %res
+ ret %24
+ }
+}
+%f2 = func(%p_2:ptr<workgroup, Inner, read_write>):f32 -> %b4 { # %p_2: 'p'
+ %b4 = block {
+ %p_mat:ptr<workgroup, mat3x4<f32>, read_write> = access %p_2, 0u
+ %28:f32 = call %f1, %p_mat
+ ret %28
+ }
+}
+%f3 = func(%p0:ptr<workgroup, array<Inner, 4>, read_write>, %p1:ptr<workgroup, mat3x4<f32>, read_write>):f32 -> %b5 {
+ %b5 = block {
+ %p0_inner:ptr<workgroup, Inner, read_write> = access %p0, 3i
+ %33:f32 = call %f2, %p0_inner
+ %34:f32 = call %f1, %p1
+ %35:f32 = add %33, %34
+ ret %35
+ }
+}
+%f4 = func(%p_3:ptr<workgroup, Outer, read_write>):f32 -> %b6 { # %p_3: 'p'
+ %b6 = block {
+ %38:ptr<workgroup, array<Inner, 4>, read_write> = access %p_3, 0u
+ %39:ptr<workgroup, mat3x4<f32>, read_write> = access %W, 1u
+ %40:f32 = call %f3, %38, %39
+ ret %40
+ }
+}
+%b = func():void -> %b7 {
+ %b7 = block {
+ %42:f32 = call %f4, %W
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %W:ptr<workgroup, Outer, read_write> = var
+}
+
+%f0_W_mat_X = func(%p_indices:array<u32, 1>):f32 -> %b2 {
+ %b2 = block {
+ %4:u32 = access %p_indices, 0u
+ %5:ptr<workgroup, vec4<f32>, read_write> = access %W, 1u, %4
+ %6:f32 = load_vector_element %5, 0u
+ ret %6
+ }
+}
+%f0_W_arr_X_mat_X = func(%p_indices_1:array<u32, 2>):f32 -> %b3 { # %p_indices_1: 'p_indices'
+ %b3 = block {
+ %9:u32 = access %p_indices_1, 0u
+ %10:u32 = access %p_indices_1, 1u
+ %11:ptr<workgroup, vec4<f32>, read_write> = access %W, 0u, %9, 0u, %10
+ %12:f32 = load_vector_element %11, 0u
+ ret %12
+ }
+}
+%f1_W_mat = func():f32 -> %b4 {
+ %b4 = block {
+ %res:ptr<function, f32, read_write> = var
+ %15:u32 = convert 1i
+ %16:array<u32, 1> = construct %15
+ %17:f32 = call %f0_W_mat_X, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %20:u32 = convert 1i
+ %21:array<u32, 1> = construct %20
+ %22:f32 = call %f0_W_mat_X, %21
+ %23:f32 = load %res
+ %24:f32 = add %23, %22
+ store %res, %24
+ %25:u32 = convert 2i
+ %26:u32 = convert 1i
+ %27:array<u32, 2> = construct %25, %26
+ %28:f32 = call %f0_W_arr_X_mat_X, %27
+ %29:f32 = load %res
+ %30:f32 = add %29, %28
+ store %res, %30
+ %31:u32 = convert 2i
+ %32:u32 = convert 1i
+ %33:array<u32, 2> = construct %31, %32
+ %34:f32 = call %f0_W_arr_X_mat_X, %33
+ %35:f32 = load %res
+ %36:f32 = add %35, %34
+ store %res, %36
+ %37:f32 = load %res
+ ret %37
+ }
+}
+%f1_W_arr_X_mat = func(%p_indices_2:array<u32, 1>):f32 -> %b5 { # %p_indices_2: 'p_indices'
+ %b5 = block {
+ %40:u32 = access %p_indices_2, 0u
+ %res_1:ptr<function, f32, read_write> = var # %res_1: 'res'
+ %42:u32 = convert 1i
+ %43:array<u32, 2> = construct %40, %42
+ %44:f32 = call %f0_W_arr_X_mat_X, %43
+ %45:f32 = load %res_1
+ %46:f32 = add %45, %44
+ store %res_1, %46
+ %47:u32 = convert 1i
+ %48:array<u32, 2> = construct %40, %47
+ %49:f32 = call %f0_W_arr_X_mat_X, %48
+ %50:f32 = load %res_1
+ %51:f32 = add %50, %49
+ store %res_1, %51
+ %52:u32 = convert 2i
+ %53:u32 = convert 1i
+ %54:array<u32, 2> = construct %52, %53
+ %55:f32 = call %f0_W_arr_X_mat_X, %54
+ %56:f32 = load %res_1
+ %57:f32 = add %56, %55
+ store %res_1, %57
+ %58:u32 = convert 2i
+ %59:u32 = convert 1i
+ %60:array<u32, 2> = construct %58, %59
+ %61:f32 = call %f0_W_arr_X_mat_X, %60
+ %62:f32 = load %res_1
+ %63:f32 = add %62, %61
+ store %res_1, %63
+ %64:f32 = load %res_1
+ ret %64
+ }
+}
+%f2_W_arr_X = func(%p_indices_3:array<u32, 1>):f32 -> %b6 { # %p_indices_3: 'p_indices'
+ %b6 = block {
+ %67:u32 = access %p_indices_3, 0u
+ %68:array<u32, 1> = construct %67
+ %69:f32 = call %f1_W_arr_X_mat, %68
+ ret %69
+ }
+}
+%f3_W_arr_W_mat = func():f32 -> %b7 {
+ %b7 = block {
+ %71:u32 = convert 3i
+ %72:array<u32, 1> = construct %71
+ %73:f32 = call %f2_W_arr_X, %72
+ %74:f32 = call %f1_W_mat
+ %75:f32 = add %73, %74
+ ret %75
+ }
+}
+%f4_W = func():f32 -> %b8 {
+ %b8 = block {
+ %77:f32 = call %f3_W_arr_W_mat
+ ret %77
+ }
+}
+%b = func():void -> %b9 {
+ %b9 = block {
+ %79:f32 = call %f4_W
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace workgroup_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'private' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace private_as_tests {
+
+using IR_DirectVariableAccessTest_PrivateAS = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Enabled_Param_ptr_i32_read) {
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_, i32>());
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(fn_a, 10_i, P, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %P:ptr<private, i32, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 10i, %P, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %P:ptr<private, i32, read_write> = var
+}
+
+%a_P = func(%pre:i32, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %5:ptr<private, i32, read_write> = access %P
+ %6:i32 = load %5
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a_P, 10i, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformPrivate);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Enabled_Param_ptr_i32_write) {
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_, i32>());
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, 42_i);
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(fn_a, 10_i, P, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %P:ptr<private, i32, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):void -> %b2 {
+ %b2 = block {
+ store %p, 42i
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:void = call %a, 10i, %P, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %P:ptr<private, i32, read_write> = var
+}
+
+%a_P = func(%pre:i32, %post:i32):void -> %b2 {
+ %b2 = block {
+ %5:ptr<private, i32, read_write> = access %P
+ store %5, 42i
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:void = call %a_P, 10i, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformPrivate);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Enabled_Param_ptr_i32_Via_struct_read) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_>(str_));
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<private_, i32>(), P, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %P:ptr<private, str, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:ptr<private, i32, read_write> = access %P, 0u
+ %9:i32 = call %a, 10i, %8, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %P:ptr<private, str, read_write> = var
+}
+
+%a_P_i = func(%pre:i32, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %5:ptr<private, i32, read_write> = access %P, 0u
+ %6:i32 = load %5
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a_P_i, 10i, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformPrivate);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Disabled_Param_ptr_i32_Via_struct_read) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_>(str_));
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<private_, i32>(), P, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %P:ptr<private, str, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %p
+ ret %6
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %8:ptr<private, i32, read_write> = access %P, 0u
+ %9:i32 = call %a, 10i, %8, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* str_ =
+ ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("arr"), ty.array<i32, 4>()},
+ });
+
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_>(str_));
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, array<i32, 4>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, b.Splat(ty.array<i32, 4>(), 0_i, 4));
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<private_, array<i32, 4>>(), P, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %P:ptr<private, str, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, array<i32, 4>, read_write>, %post:i32):void -> %b2 {
+ %b2 = block {
+ store %p, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:ptr<private, array<i32, 4>, read_write> = access %P, 0u
+ %8:void = call %a, 10i, %7, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %P:ptr<private, str, read_write> = var
+}
+
+%a_P_arr = func(%pre:i32, %post:i32):void -> %b2 {
+ %b2 = block {
+ %5:ptr<private, array<i32, 4>, read_write> = access %P, 0u
+ store %5, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:void = call %a_P_arr, 10i, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformPrivate);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* str_ =
+ ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("arr"), ty.array<i32, 4>()},
+ });
+
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_>(str_));
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, array<i32, 4>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, b.Splat(ty.array<i32, 4>(), 0_i, 4));
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* access = b.Access(ty.ptr<private_, array<i32, 4>>(), P, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %P:ptr<private, str, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, array<i32, 4>, read_write>, %post:i32):void -> %b2 {
+ %b2 = block {
+ store %p, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %7:ptr<private, array<i32, 4>, read_write> = access %P, 0u
+ %8:void = call %a, 10i, %7, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Enabled_Param_ptr_i32_mixed) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ Var* Pi = nullptr;
+ Var* Ps = nullptr;
+ Var* Pa = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ Pi = b.Var("Pi", ty.ptr<private_, i32>());
+ Ps = b.Var("Ps", ty.ptr<private_>(str_));
+ Pa = b.Var("Pa", ty.ptr<private_, array<i32, 4>>());
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ { // a(10, &Pi, 20);
+ b.Call(fn_a, 10_i, Pi, 20_i);
+ }
+ { // a(30, &Ps.i, 40);
+ auto* access = b.Access(ty.ptr<private_, i32>(), Ps, 0_u);
+ b.Call(fn_a, 30_i, access, 40_i);
+ }
+ { // a(50, &Pa[2], 60);
+ auto* access = b.Access(ty.ptr<private_, i32>(), Pa, 2_i);
+ b.Call(fn_a, 50_i, access, 60_i);
+ }
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %Pi:ptr<private, i32, read_write> = var
+ %Ps:ptr<private, str, read_write> = var
+ %Pa:ptr<private, array<i32, 4>, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %8:i32 = load %p
+ ret %8
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %10:i32 = call %a, 10i, %Pi, 20i
+ %11:ptr<private, i32, read_write> = access %Ps, 0u
+ %12:i32 = call %a, 30i, %11, 40i
+ %13:ptr<private, i32, read_write> = access %Pa, 2i
+ %14:i32 = call %a, 50i, %13, 60i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %Pi:ptr<private, i32, read_write> = var
+ %Ps:ptr<private, str, read_write> = var
+ %Pa:ptr<private, array<i32, 4>, read_write> = var
+}
+
+%a_Pi = func(%pre:i32, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %7:ptr<private, i32, read_write> = access %Pi
+ %8:i32 = load %7
+ ret %8
+ }
+}
+%a_Ps_i = func(%pre_1:i32, %post_1:i32):i32 -> %b3 { # %pre_1: 'pre', %post_1: 'post'
+ %b3 = block {
+ %12:ptr<private, i32, read_write> = access %Ps, 0u
+ %13:i32 = load %12
+ ret %13
+ }
+}
+%a_Pa_X = func(%pre_2:i32, %p_indices:array<u32, 1>, %post_2:i32):i32 -> %b4 { # %pre_2: 'pre', %post_2: 'post'
+ %b4 = block {
+ %18:u32 = access %p_indices, 0u
+ %19:ptr<private, i32, read_write> = access %Pa, %18
+ %20:i32 = load %19
+ ret %20
+ }
+}
+%b = func():void -> %b5 {
+ %b5 = block {
+ %22:i32 = call %a_Pi, 10i, 20i
+ %23:i32 = call %a_Ps_i, 30i, 40i
+ %24:u32 = convert 2i
+ %25:array<u32, 1> = construct %24
+ %26:i32 = call %a_Pa_X, 50i, %25, 60i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformPrivate);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Disabled_Param_ptr_i32_mixed) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ Var* Pi = nullptr;
+ Var* Ps = nullptr;
+ Var* Pa = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ Pi = b.Var("Pi", ty.ptr<private_, i32>());
+ Ps = b.Var("Ps", ty.ptr<private_>(str_));
+ Pa = b.Var("Pa", ty.ptr<private_, array<i32, 4>>());
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<private_, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ { // a(10, &Pi, 20);
+ b.Call(fn_a, 10_i, Pi, 20_i);
+ }
+ { // a(30, &Ps.i, 40);
+ auto* access = b.Access(ty.ptr<private_, i32>(), Ps, 0_u);
+ b.Call(fn_a, 30_i, access, 40_i);
+ }
+ { // a(50, &Pa[2], 60);
+ auto* access = b.Access(ty.ptr<private_, i32>(), Pa, 2_i);
+ b.Call(fn_a, 50_i, access, 60_i);
+ }
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%b1 = block { # root
+ %Pi:ptr<private, i32, read_write> = var
+ %Ps:ptr<private, str, read_write> = var
+ %Pa:ptr<private, array<i32, 4>, read_write> = var
+}
+
+%a = func(%pre:i32, %p:ptr<private, i32, read_write>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %8:i32 = load %p
+ ret %8
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %10:i32 = call %a, 10i, %Pi, 20i
+ %11:ptr<private, i32, read_write> = access %Ps, 0u
+ %12:i32 = call %a, 30i, %11, 40i
+ %13:ptr<private, i32, read_write> = access %Pa, 2i
+ %14:i32 = call %a, 50i, %13, 60i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Enabled_CallChaining) {
+ auto* Inner =
+ ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ auto* Outer =
+ ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.Register("arr"), ty.array(Inner, 4)},
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_>(Outer));
+ });
+
+ auto* fn_0 = b.Function("f0", ty.f32());
+ auto* fn_0_p = b.FunctionParam("p", ty.ptr<private_, vec4<f32>>());
+ fn_0->SetParams({fn_0_p});
+ b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); });
+
+ auto* fn_1 = b.Function("f1", ty.f32());
+ auto* fn_1_p = b.FunctionParam("p", ty.ptr<private_, mat3x4<f32>>());
+ fn_1->SetParams({fn_1_p});
+ b.Append(fn_1->Block(), [&] {
+ auto* res = b.Var<function, f32>("res");
+ {
+ // res += f0(&(*p)[1]);
+ auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<private_, vec4<f32>>(), fn_1_p, 1_i));
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &(*p)[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<private_, vec4<f32>>(), fn_1_p, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // res += f0(&U.arr[2].mat[1]);
+ auto* access = b.Access(ty.ptr<private_, vec4<f32>>(), P, 0_u, 2_i, 0_u, 1_i);
+ auto* call_0 = b.Call(fn_0, access);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &U.arr[2].mat[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<private_, vec4<f32>>(), P, 0_u, 2_i, 0_u, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+
+ b.Return(fn_1, b.Load(res));
+ });
+
+ auto* fn_2 = b.Function("f2", ty.f32());
+ auto* fn_2_p = b.FunctionParam("p", ty.ptr<private_>(Inner));
+ fn_2->SetParams({fn_2_p});
+ b.Append(fn_2->Block(), [&] {
+ auto* p_mat = b.Access(ty.ptr<private_, mat3x4<f32>>(), fn_2_p, 0_u);
+ b.ir.SetName(p_mat, "p_mat");
+ b.Return(fn_2, b.Call(fn_1, p_mat));
+ });
+
+ auto* fn_3 = b.Function("f3", ty.f32());
+ auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<private_>(ty.array(Inner, 4)));
+ auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<private_, mat3x4<f32>>());
+ fn_3->SetParams({fn_3_p0, fn_3_p1});
+ b.Append(fn_3->Block(), [&] {
+ auto* p0_inner = b.Access(ty.ptr<private_>(Inner), fn_3_p0, 3_i);
+ b.ir.SetName(p0_inner, "p0_inner");
+ auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner);
+ auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1);
+ b.Return(fn_3, b.Add(ty.f32(), call_0, call_1));
+ });
+
+ auto* fn_4 = b.Function("f4", ty.f32());
+ auto* fn_4_p = b.FunctionParam("p", ty.ptr<private_>(Outer));
+ fn_4->SetParams({fn_4_p});
+ b.Append(fn_4->Block(), [&] {
+ auto* access_0 = b.Access(ty.ptr<private_>(ty.array(Inner, 4)), fn_4_p, 0_u);
+ auto* access_1 = b.Access(ty.ptr<private_, mat3x4<f32>>(), P, 1_u);
+ b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1));
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(ty.f32(), fn_4, P);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %P:ptr<private, Outer, read_write> = var
+}
+
+%f0 = func(%p:ptr<private, vec4<f32>, read_write>):f32 -> %b2 {
+ %b2 = block {
+ %4:f32 = load_vector_element %p, 0u
+ ret %4
+ }
+}
+%f1 = func(%p_1:ptr<private, mat3x4<f32>, read_write>):f32 -> %b3 { # %p_1: 'p'
+ %b3 = block {
+ %res:ptr<function, f32, read_write> = var
+ %8:ptr<private, vec4<f32>, read_write> = access %p_1, 1i
+ %9:f32 = call %f0, %8
+ %10:f32 = load %res
+ %11:f32 = add %10, %9
+ store %res, %11
+ %p_vec:ptr<private, vec4<f32>, read_write> = access %p_1, 1i
+ %13:f32 = call %f0, %p_vec
+ %14:f32 = load %res
+ %15:f32 = add %14, %13
+ store %res, %15
+ %16:ptr<private, vec4<f32>, read_write> = access %P, 0u, 2i, 0u, 1i
+ %17:f32 = call %f0, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %p_vec_1:ptr<private, vec4<f32>, read_write> = access %P, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec'
+ %21:f32 = call %f0, %p_vec_1
+ %22:f32 = load %res
+ %23:f32 = add %22, %21
+ store %res, %23
+ %24:f32 = load %res
+ ret %24
+ }
+}
+%f2 = func(%p_2:ptr<private, Inner, read_write>):f32 -> %b4 { # %p_2: 'p'
+ %b4 = block {
+ %p_mat:ptr<private, mat3x4<f32>, read_write> = access %p_2, 0u
+ %28:f32 = call %f1, %p_mat
+ ret %28
+ }
+}
+%f3 = func(%p0:ptr<private, array<Inner, 4>, read_write>, %p1:ptr<private, mat3x4<f32>, read_write>):f32 -> %b5 {
+ %b5 = block {
+ %p0_inner:ptr<private, Inner, read_write> = access %p0, 3i
+ %33:f32 = call %f2, %p0_inner
+ %34:f32 = call %f1, %p1
+ %35:f32 = add %33, %34
+ ret %35
+ }
+}
+%f4 = func(%p_3:ptr<private, Outer, read_write>):f32 -> %b6 { # %p_3: 'p'
+ %b6 = block {
+ %38:ptr<private, array<Inner, 4>, read_write> = access %p_3, 0u
+ %39:ptr<private, mat3x4<f32>, read_write> = access %P, 1u
+ %40:f32 = call %f3, %38, %39
+ ret %40
+ }
+}
+%b = func():void -> %b7 {
+ %b7 = block {
+ %42:f32 = call %f4, %P
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %P:ptr<private, Outer, read_write> = var
+}
+
+%f0_P_mat_X = func(%p_indices:array<u32, 1>):f32 -> %b2 {
+ %b2 = block {
+ %4:u32 = access %p_indices, 0u
+ %5:ptr<private, vec4<f32>, read_write> = access %P, 1u, %4
+ %6:f32 = load_vector_element %5, 0u
+ ret %6
+ }
+}
+%f0_P_arr_X_mat_X = func(%p_indices_1:array<u32, 2>):f32 -> %b3 { # %p_indices_1: 'p_indices'
+ %b3 = block {
+ %9:u32 = access %p_indices_1, 0u
+ %10:u32 = access %p_indices_1, 1u
+ %11:ptr<private, vec4<f32>, read_write> = access %P, 0u, %9, 0u, %10
+ %12:f32 = load_vector_element %11, 0u
+ ret %12
+ }
+}
+%f1_P_mat = func():f32 -> %b4 {
+ %b4 = block {
+ %res:ptr<function, f32, read_write> = var
+ %15:u32 = convert 1i
+ %16:array<u32, 1> = construct %15
+ %17:f32 = call %f0_P_mat_X, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %20:u32 = convert 1i
+ %21:array<u32, 1> = construct %20
+ %22:f32 = call %f0_P_mat_X, %21
+ %23:f32 = load %res
+ %24:f32 = add %23, %22
+ store %res, %24
+ %25:u32 = convert 2i
+ %26:u32 = convert 1i
+ %27:array<u32, 2> = construct %25, %26
+ %28:f32 = call %f0_P_arr_X_mat_X, %27
+ %29:f32 = load %res
+ %30:f32 = add %29, %28
+ store %res, %30
+ %31:u32 = convert 2i
+ %32:u32 = convert 1i
+ %33:array<u32, 2> = construct %31, %32
+ %34:f32 = call %f0_P_arr_X_mat_X, %33
+ %35:f32 = load %res
+ %36:f32 = add %35, %34
+ store %res, %36
+ %37:f32 = load %res
+ ret %37
+ }
+}
+%f1_P_arr_X_mat = func(%p_indices_2:array<u32, 1>):f32 -> %b5 { # %p_indices_2: 'p_indices'
+ %b5 = block {
+ %40:u32 = access %p_indices_2, 0u
+ %res_1:ptr<function, f32, read_write> = var # %res_1: 'res'
+ %42:u32 = convert 1i
+ %43:array<u32, 2> = construct %40, %42
+ %44:f32 = call %f0_P_arr_X_mat_X, %43
+ %45:f32 = load %res_1
+ %46:f32 = add %45, %44
+ store %res_1, %46
+ %47:u32 = convert 1i
+ %48:array<u32, 2> = construct %40, %47
+ %49:f32 = call %f0_P_arr_X_mat_X, %48
+ %50:f32 = load %res_1
+ %51:f32 = add %50, %49
+ store %res_1, %51
+ %52:u32 = convert 2i
+ %53:u32 = convert 1i
+ %54:array<u32, 2> = construct %52, %53
+ %55:f32 = call %f0_P_arr_X_mat_X, %54
+ %56:f32 = load %res_1
+ %57:f32 = add %56, %55
+ store %res_1, %57
+ %58:u32 = convert 2i
+ %59:u32 = convert 1i
+ %60:array<u32, 2> = construct %58, %59
+ %61:f32 = call %f0_P_arr_X_mat_X, %60
+ %62:f32 = load %res_1
+ %63:f32 = add %62, %61
+ store %res_1, %63
+ %64:f32 = load %res_1
+ ret %64
+ }
+}
+%f2_P_arr_X = func(%p_indices_3:array<u32, 1>):f32 -> %b6 { # %p_indices_3: 'p_indices'
+ %b6 = block {
+ %67:u32 = access %p_indices_3, 0u
+ %68:array<u32, 1> = construct %67
+ %69:f32 = call %f1_P_arr_X_mat, %68
+ ret %69
+ }
+}
+%f3_P_arr_P_mat = func():f32 -> %b7 {
+ %b7 = block {
+ %71:u32 = convert 3i
+ %72:array<u32, 1> = construct %71
+ %73:f32 = call %f2_P_arr_X, %72
+ %74:f32 = call %f1_P_mat
+ %75:f32 = add %73, %74
+ ret %75
+ }
+}
+%f4_P = func():f32 -> %b8 {
+ %b8 = block {
+ %77:f32 = call %f3_P_arr_P_mat
+ ret %77
+ }
+}
+%b = func():void -> %b9 {
+ %b9 = block {
+ %79:f32 = call %f4_P
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformPrivate);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Disabled_CallChaining) {
+ auto* Inner =
+ ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ auto* Outer =
+ ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.Register("arr"), ty.array(Inner, 4)},
+ {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+ });
+ Var* P = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ P = b.Var("P", ty.ptr<private_>(Outer));
+ });
+
+ auto* fn_0 = b.Function("f0", ty.f32());
+ auto* fn_0_p = b.FunctionParam("p", ty.ptr<private_, vec4<f32>>());
+ fn_0->SetParams({fn_0_p});
+ b.Append(fn_0->Block(), [&] { b.Return(fn_0, b.LoadVectorElement(fn_0_p, 0_u)); });
+
+ auto* fn_1 = b.Function("f1", ty.f32());
+ auto* fn_1_p = b.FunctionParam("p", ty.ptr<private_, mat3x4<f32>>());
+ fn_1->SetParams({fn_1_p});
+ b.Append(fn_1->Block(), [&] {
+ auto* res = b.Var<function, f32>("res");
+ {
+ // res += f0(&(*p)[1]);
+ auto* call_0 = b.Call(fn_0, b.Access(ty.ptr<private_, vec4<f32>>(), fn_1_p, 1_i));
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &(*p)[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<private_, vec4<f32>>(), fn_1_p, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // res += f0(&U.arr[2].mat[1]);
+ auto* access = b.Access(ty.ptr<private_, vec4<f32>>(), P, 0_u, 2_i, 0_u, 1_i);
+ auto* call_0 = b.Call(fn_0, access);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+ {
+ // let p_vec = &U.arr[2].mat[1];
+ // res += f0(p_vec);
+ auto* p_vec = b.Access(ty.ptr<private_, vec4<f32>>(), P, 0_u, 2_i, 0_u, 1_i);
+ b.ir.SetName(p_vec, "p_vec");
+ auto* call_0 = b.Call(fn_0, p_vec);
+ b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+ }
+
+ b.Return(fn_1, b.Load(res));
+ });
+
+ auto* fn_2 = b.Function("f2", ty.f32());
+ auto* fn_2_p = b.FunctionParam("p", ty.ptr<private_>(Inner));
+ fn_2->SetParams({fn_2_p});
+ b.Append(fn_2->Block(), [&] {
+ auto* p_mat = b.Access(ty.ptr<private_, mat3x4<f32>>(), fn_2_p, 0_u);
+ b.ir.SetName(p_mat, "p_mat");
+ b.Return(fn_2, b.Call(fn_1, p_mat));
+ });
+
+ auto* fn_3 = b.Function("f3", ty.f32());
+ auto* fn_3_p0 = b.FunctionParam("p0", ty.ptr<private_>(ty.array(Inner, 4)));
+ auto* fn_3_p1 = b.FunctionParam("p1", ty.ptr<private_, mat3x4<f32>>());
+ fn_3->SetParams({fn_3_p0, fn_3_p1});
+ b.Append(fn_3->Block(), [&] {
+ auto* p0_inner = b.Access(ty.ptr<private_>(Inner), fn_3_p0, 3_i);
+ b.ir.SetName(p0_inner, "p0_inner");
+ auto* call_0 = b.Call(ty.f32(), fn_2, p0_inner);
+ auto* call_1 = b.Call(ty.f32(), fn_1, fn_3_p1);
+ b.Return(fn_3, b.Add(ty.f32(), call_0, call_1));
+ });
+
+ auto* fn_4 = b.Function("f4", ty.f32());
+ auto* fn_4_p = b.FunctionParam("p", ty.ptr<private_>(Outer));
+ fn_4->SetParams({fn_4_p});
+ b.Append(fn_4->Block(), [&] {
+ auto* access_0 = b.Access(ty.ptr<private_>(ty.array(Inner, 4)), fn_4_p, 0_u);
+ auto* access_1 = b.Access(ty.ptr<private_, mat3x4<f32>>(), P, 1_u);
+ b.Return(fn_4, b.Call(ty.f32(), fn_3, access_0, access_1));
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ b.Call(ty.f32(), fn_4, P);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+Inner = struct @align(16) {
+ mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+ arr:array<Inner, 4> @offset(0)
+ mat:mat3x4<f32> @offset(192)
+}
+
+%b1 = block { # root
+ %P:ptr<private, Outer, read_write> = var
+}
+
+%f0 = func(%p:ptr<private, vec4<f32>, read_write>):f32 -> %b2 {
+ %b2 = block {
+ %4:f32 = load_vector_element %p, 0u
+ ret %4
+ }
+}
+%f1 = func(%p_1:ptr<private, mat3x4<f32>, read_write>):f32 -> %b3 { # %p_1: 'p'
+ %b3 = block {
+ %res:ptr<function, f32, read_write> = var
+ %8:ptr<private, vec4<f32>, read_write> = access %p_1, 1i
+ %9:f32 = call %f0, %8
+ %10:f32 = load %res
+ %11:f32 = add %10, %9
+ store %res, %11
+ %p_vec:ptr<private, vec4<f32>, read_write> = access %p_1, 1i
+ %13:f32 = call %f0, %p_vec
+ %14:f32 = load %res
+ %15:f32 = add %14, %13
+ store %res, %15
+ %16:ptr<private, vec4<f32>, read_write> = access %P, 0u, 2i, 0u, 1i
+ %17:f32 = call %f0, %16
+ %18:f32 = load %res
+ %19:f32 = add %18, %17
+ store %res, %19
+ %p_vec_1:ptr<private, vec4<f32>, read_write> = access %P, 0u, 2i, 0u, 1i # %p_vec_1: 'p_vec'
+ %21:f32 = call %f0, %p_vec_1
+ %22:f32 = load %res
+ %23:f32 = add %22, %21
+ store %res, %23
+ %24:f32 = load %res
+ ret %24
+ }
+}
+%f2 = func(%p_2:ptr<private, Inner, read_write>):f32 -> %b4 { # %p_2: 'p'
+ %b4 = block {
+ %p_mat:ptr<private, mat3x4<f32>, read_write> = access %p_2, 0u
+ %28:f32 = call %f1, %p_mat
+ ret %28
+ }
+}
+%f3 = func(%p0:ptr<private, array<Inner, 4>, read_write>, %p1:ptr<private, mat3x4<f32>, read_write>):f32 -> %b5 {
+ %b5 = block {
+ %p0_inner:ptr<private, Inner, read_write> = access %p0, 3i
+ %33:f32 = call %f2, %p0_inner
+ %34:f32 = call %f1, %p1
+ %35:f32 = add %33, %34
+ ret %35
+ }
+}
+%f4 = func(%p_3:ptr<private, Outer, read_write>):f32 -> %b6 { # %p_3: 'p'
+ %b6 = block {
+ %38:ptr<private, array<Inner, 4>, read_write> = access %p_3, 0u
+ %39:ptr<private, mat3x4<f32>, read_write> = access %P, 1u
+ %40:f32 = call %f3, %38, %39
+ ret %40
+ }
+}
+%b = func():void -> %b7 {
+ %b7 = block {
+ %42:f32 = call %f4, %P
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace private_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'function' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace function_as_tests {
+
+using IR_DirectVariableAccessTest_FunctionAS = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_LocalPtr) {
+ auto* fn = b.Function("f", ty.void_());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Var<function, i32>("v");
+ auto* p = b.Let("p", v);
+ b.Var<function>("x", b.Load(p));
+ b.Return(fn);
+ });
+
+ auto* src = R"(
+%f = func():void -> %b1 {
+ %b1 = block {
+ %v:ptr<function, i32, read_write> = var
+ %p:ptr<function, i32, read_write> = let %v
+ %4:i32 = load %p
+ %x:ptr<function, i32, read_write> = var, %4
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src; // Nothing changes
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_Param_ptr_i32_read) {
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<function, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* F = b.Var<function, i32>("F");
+ b.Call(fn_a, 10_i, F, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%a = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 -> %b1 {
+ %b1 = block {
+ %5:i32 = load %p
+ ret %5
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, i32, read_write> = var
+ %8:i32 = call %a, 10i, %F, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a_P = func(%pre:i32, %p_root:ptr<function, i32, read_write>, %post:i32):i32 -> %b1 {
+ %b1 = block {
+ %5:ptr<function, i32, read_write> = access %p_root
+ %6:i32 = load %5
+ ret %6
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, i32, read_write> = var
+ %9:i32 = call %a_P, 10i, %F, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformFunction);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_Param_ptr_i32_write) {
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<function, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, 42_i);
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* F = b.Var<function, i32>("F");
+ b.Call(fn_a, 10_i, F, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+%a = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):void -> %b1 {
+ %b1 = block {
+ store %p, 42i
+ ret
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, i32, read_write> = var
+ %7:void = call %a, 10i, %F, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a_P = func(%pre:i32, %p_root:ptr<function, i32, read_write>, %post:i32):void -> %b1 {
+ %b1 = block {
+ %5:ptr<function, i32, read_write> = access %p_root
+ store %5, 42i
+ ret
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, i32, read_write> = var
+ %8:void = call %a_P, 10i, %F, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformFunction);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_Param_ptr_i32_Via_struct_read) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<function, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* F = b.Var("F", ty.ptr<function>(str_));
+ auto* access = b.Access(ty.ptr<function, i32>(), F, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%a = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 -> %b1 {
+ %b1 = block {
+ %5:i32 = load %p
+ ret %5
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, str, read_write> = var
+ %8:ptr<function, i32, read_write> = access %F, 0u
+ %9:i32 = call %a, 10i, %8, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%a_P_i = func(%pre:i32, %p_root:ptr<function, str, read_write>, %post:i32):i32 -> %b1 {
+ %b1 = block {
+ %5:ptr<function, i32, read_write> = access %p_root, 0u
+ %6:i32 = load %5
+ ret %6
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, str, read_write> = var
+ %9:i32 = call %a_P_i, 10i, %F, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformFunction);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* str_ =
+ ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("arr"), ty.array<i32, 4>()},
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<function, array<i32, 4>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, b.Splat(ty.array<i32, 4>(), 0_i, 4));
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* F = b.Var("F", ty.ptr<function>(str_));
+ auto* access = b.Access(ty.ptr<function, array<i32, 4>>(), F, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%a = func(%pre:i32, %p:ptr<function, array<i32, 4>, read_write>, %post:i32):void -> %b1 {
+ %b1 = block {
+ store %p, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, str, read_write> = var
+ %7:ptr<function, array<i32, 4>, read_write> = access %F, 0u
+ %8:void = call %a, 10i, %7, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%a_P_arr = func(%pre:i32, %p_root:ptr<function, str, read_write>, %post:i32):void -> %b1 {
+ %b1 = block {
+ %5:ptr<function, array<i32, 4>, read_write> = access %p_root, 0u
+ store %5, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, str, read_write> = var
+ %8:void = call %a_P_arr, 10i, %F, 20i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformFunction);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_Param_ptr_i32_mixed) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<function, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* Fi = b.Var("Fi", ty.ptr<function, i32>());
+ auto* Fs = b.Var("Fs", ty.ptr<function>(str_));
+ auto* Fa = b.Var("Fa", ty.ptr<function, array<i32, 4>>());
+ { // a(10, &Fi, 20);
+ b.Call(fn_a, 10_i, Fi, 20_i);
+ }
+ { // a(30, &Fs.i, 40);
+ auto* access = b.Access(ty.ptr<function, i32>(), Fs, 0_u);
+ b.Call(fn_a, 30_i, access, 40_i);
+ }
+ { // a(50, &Fa[2], 60);
+ auto* access = b.Access(ty.ptr<function, i32>(), Fa, 2_i);
+ b.Call(fn_a, 50_i, access, 60_i);
+ }
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%a = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 -> %b1 {
+ %b1 = block {
+ %5:i32 = load %p
+ ret %5
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %Fi:ptr<function, i32, read_write> = var
+ %Fs:ptr<function, str, read_write> = var
+ %Fa:ptr<function, array<i32, 4>, read_write> = var
+ %10:i32 = call %a, 10i, %Fi, 20i
+ %11:ptr<function, i32, read_write> = access %Fs, 0u
+ %12:i32 = call %a, 30i, %11, 40i
+ %13:ptr<function, i32, read_write> = access %Fa, 2i
+ %14:i32 = call %a, 50i, %13, 60i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%a_P = func(%pre:i32, %p_root:ptr<function, i32, read_write>, %post:i32):i32 -> %b1 {
+ %b1 = block {
+ %5:ptr<function, i32, read_write> = access %p_root
+ %6:i32 = load %5
+ ret %6
+ }
+}
+%a_P_i = func(%pre_1:i32, %p_root_1:ptr<function, str, read_write>, %post_1:i32):i32 -> %b2 { # %pre_1: 'pre', %p_root_1: 'p_root', %post_1: 'post'
+ %b2 = block {
+ %11:ptr<function, i32, read_write> = access %p_root_1, 0u
+ %12:i32 = load %11
+ ret %12
+ }
+}
+%a_P_X = func(%pre_2:i32, %p_root_2:ptr<function, array<i32, 4>, read_write>, %p_indices:array<u32, 1>, %post_2:i32):i32 -> %b3 { # %pre_2: 'pre', %p_root_2: 'p_root', %post_2: 'post'
+ %b3 = block {
+ %18:u32 = access %p_indices, 0u
+ %19:ptr<function, i32, read_write> = access %p_root_2, %18
+ %20:i32 = load %19
+ ret %20
+ }
+}
+%b = func():void -> %b4 {
+ %b4 = block {
+ %Fi:ptr<function, i32, read_write> = var
+ %Fs:ptr<function, str, read_write> = var
+ %Fa:ptr<function, array<i32, 4>, read_write> = var
+ %25:i32 = call %a_P, 10i, %Fi, 20i
+ %26:i32 = call %a_P_i, 30i, %Fs, 40i
+ %27:u32 = convert 2i
+ %28:array<u32, 1> = construct %27
+ %29:i32 = call %a_P_X, 50i, %Fa, %28, 60i
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, kTransformFunction);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Disabled_Param_ptr_i32_Via_struct_read) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.i32()},
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<function, i32>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* F = b.Var("F", ty.ptr<function>(str_));
+ auto* access = b.Access(ty.ptr<function, i32>(), F, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ i:i32 @offset(0)
+}
+
+%a = func(%pre:i32, %p:ptr<function, i32, read_write>, %post:i32):i32 -> %b1 {
+ %b1 = block {
+ %5:i32 = load %p
+ ret %5
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, str, read_write> = var
+ %8:ptr<function, i32, read_write> = access %F, 0u
+ %9:i32 = call %a, 10i, %8, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* str_ =
+ ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("arr"), ty.array<i32, 4>()},
+ });
+
+ auto* fn_a = b.Function("a", ty.void_());
+ auto* fn_a_p = b.FunctionParam("p", ty.ptr<function, array<i32, 4>>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_p,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] {
+ b.Store(fn_a_p, b.Splat(ty.array<i32, 4>(), 0_i, 4));
+ b.Return(fn_a);
+ });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* F = b.Var("F", ty.ptr<function>(str_));
+ auto* access = b.Access(ty.ptr<function, array<i32, 4>>(), F, 0_u);
+ b.Call(fn_a, 10_i, access, 20_i);
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(4) {
+ arr:array<i32, 4> @offset(0)
+}
+
+%a = func(%pre:i32, %p:ptr<function, array<i32, 4>, read_write>, %post:i32):void -> %b1 {
+ %b1 = block {
+ store %p, array<i32, 4>(0i)
+ ret
+ }
+}
+%b = func():void -> %b2 {
+ %b2 = block {
+ %F:ptr<function, str, read_write> = var
+ %7:ptr<function, array<i32, 4>, read_write> = access %F, 0u
+ %8:void = call %a, 10i, %7, 20i
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace function_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// builtin function calls
+////////////////////////////////////////////////////////////////////////////////
+namespace builtin_fn_calls {
+
+using IR_DirectVariableAccessTest_BuiltinFn = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_BuiltinFn, ArrayLength) {
+ Var* S = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var<storage, array<f32>>("S");
+ S->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_len = b.Function("len", ty.u32());
+ auto* fn_len_p = b.FunctionParam("p", ty.ptr<storage, array<f32>>());
+ fn_len->SetParams({fn_len_p});
+ b.Append(fn_len->Block(),
+ [&] { //
+ b.Return(fn_len, b.Call(ty.u32(), core::BuiltinFn::kArrayLength, fn_len_p));
+ });
+
+ auto* fn_f = b.Function("b", ty.void_());
+ b.Append(fn_f->Block(), [&] {
+ b.Call(fn_len, S);
+ b.Return(fn_f);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<f32>, read_write> = var @binding_point(0, 0)
+}
+
+%len = func(%p:ptr<storage, array<f32>, read_write>):u32 -> %b2 {
+ %b2 = block {
+ %4:u32 = arrayLength %p
+ ret %4
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %6:u32 = call %len, %S
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<f32>, read_write> = var @binding_point(0, 0)
+}
+
+%len_S = func():u32 -> %b2 {
+ %b2 = block {
+ %3:ptr<storage, array<f32>, read_write> = access %S
+ %4:u32 = arrayLength %3
+ ret %4
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %6:u32 = call %len_S
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_BuiltinFn, WorkgroupUniformLoad) {
+ Var* W = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ W = b.Var<workgroup, f32>("W");
+ });
+
+ auto* fn_load = b.Function("load", ty.f32());
+ auto* fn_load_p = b.FunctionParam("p", ty.ptr<workgroup, f32>());
+ fn_load->SetParams({fn_load_p});
+ b.Append(fn_load->Block(),
+ [&] { //
+ b.Return(fn_load,
+ b.Call(ty.f32(), core::BuiltinFn::kWorkgroupUniformLoad, fn_load_p));
+ });
+
+ auto* fn_f = b.Function("b", ty.void_());
+ b.Append(fn_f->Block(), [&] {
+ b.Call(fn_load, W);
+ b.Return(fn_f);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %W:ptr<workgroup, f32, read_write> = var
+}
+
+%load = func(%p:ptr<workgroup, f32, read_write>):f32 -> %b2 {
+ %b2 = block {
+ %4:f32 = workgroupUniformLoad %p
+ ret %4
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %6:f32 = call %load, %W
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %W:ptr<workgroup, f32, read_write> = var
+}
+
+%load_W = func():f32 -> %b2 {
+ %b2 = block {
+ %3:ptr<workgroup, f32, read_write> = access %W
+ %4:f32 = workgroupUniformLoad %3
+ ret %4
+ }
+}
+%b = func():void -> %b3 {
+ %b3 = block {
+ %6:f32 = call %load_W
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace builtin_fn_calls
+
+////////////////////////////////////////////////////////////////////////////////
+// complex tests
+////////////////////////////////////////////////////////////////////////////////
+namespace complex_tests {
+
+using IR_DirectVariableAccessTest_Complex = TransformTest;
+
+TEST_F(IR_DirectVariableAccessTest_Complex, Param_ptr_mixed_vec4i32_ViaMultiple) {
+ auto* str_ = ty.Struct(mod.symbols.New("str"), {
+ {mod.symbols.Register("i"), ty.vec4<i32>()},
+ });
+
+ Var* U = nullptr;
+ Var* U_str = nullptr;
+ Var* U_arr = nullptr;
+ Var* U_arr_arr = nullptr;
+ Var* S = nullptr;
+ Var* S_str = nullptr;
+ Var* S_arr = nullptr;
+ Var* S_arr_arr = nullptr;
+ Var* W = nullptr;
+ Var* W_str = nullptr;
+ Var* W_arr = nullptr;
+ Var* W_arr_arr = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ U = b.Var<uniform, vec4<i32>, read>("U");
+ U->SetBindingPoint(0, 0);
+ U_str = b.Var("U_str", ty.ptr<uniform, read>(str_));
+ U_str->SetBindingPoint(0, 1);
+ U_arr = b.Var<uniform, array<vec4<i32>, 8>, read>("U_arr");
+ U_arr->SetBindingPoint(0, 2);
+ U_arr_arr = b.Var<uniform, array<array<vec4<i32>, 8>, 4>, read>("U_arr_arr");
+ U_arr_arr->SetBindingPoint(0, 3);
+
+ S = b.Var<storage, vec4<i32>, read>("S");
+ S->SetBindingPoint(1, 0);
+ S_str = b.Var("S_str", ty.ptr<storage, read>(str_));
+ S_str->SetBindingPoint(1, 1);
+ S_arr = b.Var<storage, array<vec4<i32>, 8>, read>("S_arr");
+ S_arr->SetBindingPoint(1, 2);
+ S_arr_arr = b.Var<storage, array<array<vec4<i32>, 8>, 4>, read>("S_arr_arr");
+ S_arr_arr->SetBindingPoint(1, 3);
+
+ W = b.Var<workgroup, vec4<i32>>("W");
+ W_str = b.Var("W_str", ty.ptr<workgroup>(str_));
+ W_arr = b.Var<workgroup, array<vec4<i32>, 8>>("W_arr");
+ W_arr_arr = b.Var<workgroup, array<array<vec4<i32>, 8>, 4>>("W_arr_arr");
+ });
+
+ auto* fn_u = b.Function("fn_u", ty.vec4<i32>());
+ auto* fn_u_p = b.FunctionParam("p", ty.ptr<uniform, vec4<i32>, read>());
+ fn_u->SetParams({fn_u_p});
+ b.Append(fn_u->Block(), [&] { b.Return(fn_u, b.Load(fn_u_p)); });
+
+ auto* fn_s = b.Function("fn_s", ty.vec4<i32>());
+ auto* fn_s_p = b.FunctionParam("p", ty.ptr<storage, vec4<i32>, read>());
+ fn_s->SetParams({fn_s_p});
+ b.Append(fn_s->Block(), [&] { b.Return(fn_s, b.Load(fn_s_p)); });
+
+ auto* fn_w = b.Function("fn_w", ty.vec4<i32>());
+ auto* fn_w_p = b.FunctionParam("p", ty.ptr<workgroup, vec4<i32>>());
+ fn_w->SetParams({fn_w_p});
+ b.Append(fn_w->Block(), [&] { b.Return(fn_w, b.Load(fn_w_p)); });
+
+ auto* fn_b = b.Function("b", ty.void_());
+ b.Append(fn_b->Block(), [&] {
+ auto* I = b.Let("I", 3_i);
+ auto* J = b.Let("J", 4_i);
+
+ auto* u = b.Call(fn_u, U);
+ b.ir.SetName(u, "u");
+ auto* u_str = b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_str, 0_u));
+ b.ir.SetName(u_str, "u_str");
+ auto* u_arr0 = b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_arr, 0_i));
+ b.ir.SetName(u_arr0, "u_arr0");
+ auto* u_arr1 = b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_arr, 1_i));
+ b.ir.SetName(u_arr1, "u_arr1");
+ auto* u_arrI = b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_arr, I));
+ b.ir.SetName(u_arrI, "u_arrI");
+ auto* u_arr1_arr0 =
+ b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_arr_arr, 1_i, 0_i));
+ b.ir.SetName(u_arr1_arr0, "u_arr1_arr0");
+ auto* u_arr2_arrI =
+ b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_arr_arr, 2_i, I));
+ b.ir.SetName(u_arr2_arrI, "u_arr2_arrI");
+ auto* u_arrI_arr2 =
+ b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_arr_arr, I, 2_i));
+ b.ir.SetName(u_arrI_arr2, "u_arrI_arr2");
+ auto* u_arrI_arrJ =
+ b.Call(fn_u, b.Access(ty.ptr<uniform, vec4<i32>, read>(), U_arr_arr, I, J));
+ b.ir.SetName(u_arrI_arrJ, "u_arrI_arrJ");
+
+ auto* s = b.Call(fn_s, S);
+ b.ir.SetName(s, "s");
+ auto* s_str = b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_str, 0_u));
+ b.ir.SetName(s_str, "s_str");
+ auto* s_arr0 = b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_arr, 0_i));
+ b.ir.SetName(s_arr0, "s_arr0");
+ auto* s_arr1 = b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_arr, 1_i));
+ b.ir.SetName(s_arr1, "s_arr1");
+ auto* s_arrI = b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_arr, I));
+ b.ir.SetName(s_arrI, "s_arrI");
+ auto* s_arr1_arr0 =
+ b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_arr_arr, 1_i, 0_i));
+ b.ir.SetName(s_arr1_arr0, "s_arr1_arr0");
+ auto* s_arr2_arrI =
+ b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_arr_arr, 2_i, I));
+ b.ir.SetName(s_arr2_arrI, "s_arr2_arrI");
+ auto* s_arrI_arr2 =
+ b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_arr_arr, I, 2_i));
+ b.ir.SetName(s_arrI_arr2, "s_arrI_arr2");
+ auto* s_arrI_arrJ =
+ b.Call(fn_s, b.Access(ty.ptr<storage, vec4<i32>, read>(), S_arr_arr, I, J));
+ b.ir.SetName(s_arrI_arrJ, "s_arrI_arrJ");
+
+ auto* w = b.Call(fn_w, W);
+ b.ir.SetName(w, "w");
+ auto* w_str = b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_str, 0_u));
+ b.ir.SetName(w_str, "w_str");
+ auto* w_arr0 = b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_arr, 0_i));
+ b.ir.SetName(w_arr0, "w_arr0");
+ auto* w_arr1 = b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_arr, 1_i));
+ b.ir.SetName(w_arr1, "w_arr1");
+ auto* w_arrI = b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_arr, I));
+ b.ir.SetName(w_arrI, "w_arrI");
+ auto* w_arr1_arr0 =
+ b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_arr_arr, 1_i, 0_i));
+ b.ir.SetName(w_arr1_arr0, "w_arr1_arr0");
+ auto* w_arr2_arrI =
+ b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_arr_arr, 2_i, I));
+ b.ir.SetName(w_arr2_arrI, "w_arr2_arrI");
+ auto* w_arrI_arr2 =
+ b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_arr_arr, I, 2_i));
+ b.ir.SetName(w_arrI_arr2, "w_arrI_arr2");
+ auto* w_arrI_arrJ = b.Call(fn_w, b.Access(ty.ptr<workgroup, vec4<i32>>(), W_arr_arr, I, J));
+ b.ir.SetName(w_arrI_arrJ, "w_arrI_arrJ");
+
+ b.Return(fn_b);
+ });
+
+ auto* src = R"(
+str = struct @align(16) {
+ i:vec4<i32> @offset(0)
+}
+
+%b1 = block { # root
+ %U:ptr<uniform, vec4<i32>, read> = var @binding_point(0, 0)
+ %U_str:ptr<uniform, str, read> = var @binding_point(0, 1)
+ %U_arr:ptr<uniform, array<vec4<i32>, 8>, read> = var @binding_point(0, 2)
+ %U_arr_arr:ptr<uniform, array<array<vec4<i32>, 8>, 4>, read> = var @binding_point(0, 3)
+ %S:ptr<storage, vec4<i32>, read> = var @binding_point(1, 0)
+ %S_str:ptr<storage, str, read> = var @binding_point(1, 1)
+ %S_arr:ptr<storage, array<vec4<i32>, 8>, read> = var @binding_point(1, 2)
+ %S_arr_arr:ptr<storage, array<array<vec4<i32>, 8>, 4>, read> = var @binding_point(1, 3)
+ %W:ptr<workgroup, vec4<i32>, read_write> = var
+ %W_str:ptr<workgroup, str, read_write> = var
+ %W_arr:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var
+ %W_arr_arr:ptr<workgroup, array<array<vec4<i32>, 8>, 4>, read_write> = var
+}
+
+%fn_u = func(%p:ptr<uniform, vec4<i32>, read>):vec4<i32> -> %b2 {
+ %b2 = block {
+ %15:vec4<i32> = load %p
+ ret %15
+ }
+}
+%fn_s = func(%p_1:ptr<storage, vec4<i32>, read>):vec4<i32> -> %b3 { # %p_1: 'p'
+ %b3 = block {
+ %18:vec4<i32> = load %p_1
+ ret %18
+ }
+}
+%fn_w = func(%p_2:ptr<workgroup, vec4<i32>, read_write>):vec4<i32> -> %b4 { # %p_2: 'p'
+ %b4 = block {
+ %21:vec4<i32> = load %p_2
+ ret %21
+ }
+}
+%b = func():void -> %b5 {
+ %b5 = block {
+ %I:i32 = let 3i
+ %J:i32 = let 4i
+ %u:vec4<i32> = call %fn_u, %U
+ %26:ptr<uniform, vec4<i32>, read> = access %U_str, 0u
+ %u_str:vec4<i32> = call %fn_u, %26
+ %28:ptr<uniform, vec4<i32>, read> = access %U_arr, 0i
+ %u_arr0:vec4<i32> = call %fn_u, %28
+ %30:ptr<uniform, vec4<i32>, read> = access %U_arr, 1i
+ %u_arr1:vec4<i32> = call %fn_u, %30
+ %32:ptr<uniform, vec4<i32>, read> = access %U_arr, %I
+ %u_arrI:vec4<i32> = call %fn_u, %32
+ %34:ptr<uniform, vec4<i32>, read> = access %U_arr_arr, 1i, 0i
+ %u_arr1_arr0:vec4<i32> = call %fn_u, %34
+ %36:ptr<uniform, vec4<i32>, read> = access %U_arr_arr, 2i, %I
+ %u_arr2_arrI:vec4<i32> = call %fn_u, %36
+ %38:ptr<uniform, vec4<i32>, read> = access %U_arr_arr, %I, 2i
+ %u_arrI_arr2:vec4<i32> = call %fn_u, %38
+ %40:ptr<uniform, vec4<i32>, read> = access %U_arr_arr, %I, %J
+ %u_arrI_arrJ:vec4<i32> = call %fn_u, %40
+ %s:vec4<i32> = call %fn_s, %S
+ %43:ptr<storage, vec4<i32>, read> = access %S_str, 0u
+ %s_str:vec4<i32> = call %fn_s, %43
+ %45:ptr<storage, vec4<i32>, read> = access %S_arr, 0i
+ %s_arr0:vec4<i32> = call %fn_s, %45
+ %47:ptr<storage, vec4<i32>, read> = access %S_arr, 1i
+ %s_arr1:vec4<i32> = call %fn_s, %47
+ %49:ptr<storage, vec4<i32>, read> = access %S_arr, %I
+ %s_arrI:vec4<i32> = call %fn_s, %49
+ %51:ptr<storage, vec4<i32>, read> = access %S_arr_arr, 1i, 0i
+ %s_arr1_arr0:vec4<i32> = call %fn_s, %51
+ %53:ptr<storage, vec4<i32>, read> = access %S_arr_arr, 2i, %I
+ %s_arr2_arrI:vec4<i32> = call %fn_s, %53
+ %55:ptr<storage, vec4<i32>, read> = access %S_arr_arr, %I, 2i
+ %s_arrI_arr2:vec4<i32> = call %fn_s, %55
+ %57:ptr<storage, vec4<i32>, read> = access %S_arr_arr, %I, %J
+ %s_arrI_arrJ:vec4<i32> = call %fn_s, %57
+ %w:vec4<i32> = call %fn_w, %W
+ %60:ptr<workgroup, vec4<i32>, read_write> = access %W_str, 0u
+ %w_str:vec4<i32> = call %fn_w, %60
+ %62:ptr<workgroup, vec4<i32>, read_write> = access %W_arr, 0i
+ %w_arr0:vec4<i32> = call %fn_w, %62
+ %64:ptr<workgroup, vec4<i32>, read_write> = access %W_arr, 1i
+ %w_arr1:vec4<i32> = call %fn_w, %64
+ %66:ptr<workgroup, vec4<i32>, read_write> = access %W_arr, %I
+ %w_arrI:vec4<i32> = call %fn_w, %66
+ %68:ptr<workgroup, vec4<i32>, read_write> = access %W_arr_arr, 1i, 0i
+ %w_arr1_arr0:vec4<i32> = call %fn_w, %68
+ %70:ptr<workgroup, vec4<i32>, read_write> = access %W_arr_arr, 2i, %I
+ %w_arr2_arrI:vec4<i32> = call %fn_w, %70
+ %72:ptr<workgroup, vec4<i32>, read_write> = access %W_arr_arr, %I, 2i
+ %w_arrI_arr2:vec4<i32> = call %fn_w, %72
+ %74:ptr<workgroup, vec4<i32>, read_write> = access %W_arr_arr, %I, %J
+ %w_arrI_arrJ:vec4<i32> = call %fn_w, %74
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+str = struct @align(16) {
+ i:vec4<i32> @offset(0)
+}
+
+%b1 = block { # root
+ %U:ptr<uniform, vec4<i32>, read> = var @binding_point(0, 0)
+ %U_str:ptr<uniform, str, read> = var @binding_point(0, 1)
+ %U_arr:ptr<uniform, array<vec4<i32>, 8>, read> = var @binding_point(0, 2)
+ %U_arr_arr:ptr<uniform, array<array<vec4<i32>, 8>, 4>, read> = var @binding_point(0, 3)
+ %S:ptr<storage, vec4<i32>, read> = var @binding_point(1, 0)
+ %S_str:ptr<storage, str, read> = var @binding_point(1, 1)
+ %S_arr:ptr<storage, array<vec4<i32>, 8>, read> = var @binding_point(1, 2)
+ %S_arr_arr:ptr<storage, array<array<vec4<i32>, 8>, 4>, read> = var @binding_point(1, 3)
+ %W:ptr<workgroup, vec4<i32>, read_write> = var
+ %W_str:ptr<workgroup, str, read_write> = var
+ %W_arr:ptr<workgroup, array<vec4<i32>, 8>, read_write> = var
+ %W_arr_arr:ptr<workgroup, array<array<vec4<i32>, 8>, 4>, read_write> = var
+}
+
+%fn_u_U = func():vec4<i32> -> %b2 {
+ %b2 = block {
+ %14:ptr<uniform, vec4<i32>, read> = access %U
+ %15:vec4<i32> = load %14
+ ret %15
+ }
+}
+%fn_u_U_str_i = func():vec4<i32> -> %b3 {
+ %b3 = block {
+ %17:ptr<uniform, vec4<i32>, read> = access %U_str, 0u
+ %18:vec4<i32> = load %17
+ ret %18
+ }
+}
+%fn_u_U_arr_X = func(%p_indices:array<u32, 1>):vec4<i32> -> %b4 {
+ %b4 = block {
+ %21:u32 = access %p_indices, 0u
+ %22:ptr<uniform, vec4<i32>, read> = access %U_arr, %21
+ %23:vec4<i32> = load %22
+ ret %23
+ }
+}
+%fn_u_U_arr_arr_X_X = func(%p_indices_1:array<u32, 2>):vec4<i32> -> %b5 { # %p_indices_1: 'p_indices'
+ %b5 = block {
+ %26:u32 = access %p_indices_1, 0u
+ %27:u32 = access %p_indices_1, 1u
+ %28:ptr<uniform, vec4<i32>, read> = access %U_arr_arr, %26, %27
+ %29:vec4<i32> = load %28
+ ret %29
+ }
+}
+%fn_s_S = func():vec4<i32> -> %b6 {
+ %b6 = block {
+ %31:ptr<storage, vec4<i32>, read> = access %S
+ %32:vec4<i32> = load %31
+ ret %32
+ }
+}
+%fn_s_S_str_i = func():vec4<i32> -> %b7 {
+ %b7 = block {
+ %34:ptr<storage, vec4<i32>, read> = access %S_str, 0u
+ %35:vec4<i32> = load %34
+ ret %35
+ }
+}
+%fn_s_S_arr_X = func(%p_indices_2:array<u32, 1>):vec4<i32> -> %b8 { # %p_indices_2: 'p_indices'
+ %b8 = block {
+ %38:u32 = access %p_indices_2, 0u
+ %39:ptr<storage, vec4<i32>, read> = access %S_arr, %38
+ %40:vec4<i32> = load %39
+ ret %40
+ }
+}
+%fn_s_S_arr_arr_X_X = func(%p_indices_3:array<u32, 2>):vec4<i32> -> %b9 { # %p_indices_3: 'p_indices'
+ %b9 = block {
+ %43:u32 = access %p_indices_3, 0u
+ %44:u32 = access %p_indices_3, 1u
+ %45:ptr<storage, vec4<i32>, read> = access %S_arr_arr, %43, %44
+ %46:vec4<i32> = load %45
+ ret %46
+ }
+}
+%fn_w_W = func():vec4<i32> -> %b10 {
+ %b10 = block {
+ %48:ptr<workgroup, vec4<i32>, read_write> = access %W
+ %49:vec4<i32> = load %48
+ ret %49
+ }
+}
+%fn_w_W_str_i = func():vec4<i32> -> %b11 {
+ %b11 = block {
+ %51:ptr<workgroup, vec4<i32>, read_write> = access %W_str, 0u
+ %52:vec4<i32> = load %51
+ ret %52
+ }
+}
+%fn_w_W_arr_X = func(%p_indices_4:array<u32, 1>):vec4<i32> -> %b12 { # %p_indices_4: 'p_indices'
+ %b12 = block {
+ %55:u32 = access %p_indices_4, 0u
+ %56:ptr<workgroup, vec4<i32>, read_write> = access %W_arr, %55
+ %57:vec4<i32> = load %56
+ ret %57
+ }
+}
+%fn_w_W_arr_arr_X_X = func(%p_indices_5:array<u32, 2>):vec4<i32> -> %b13 { # %p_indices_5: 'p_indices'
+ %b13 = block {
+ %60:u32 = access %p_indices_5, 0u
+ %61:u32 = access %p_indices_5, 1u
+ %62:ptr<workgroup, vec4<i32>, read_write> = access %W_arr_arr, %60, %61
+ %63:vec4<i32> = load %62
+ ret %63
+ }
+}
+%b = func():void -> %b14 {
+ %b14 = block {
+ %I:i32 = let 3i
+ %J:i32 = let 4i
+ %u:vec4<i32> = call %fn_u_U
+ %u_str:vec4<i32> = call %fn_u_U_str_i
+ %69:u32 = convert 0i
+ %70:array<u32, 1> = construct %69
+ %u_arr0:vec4<i32> = call %fn_u_U_arr_X, %70
+ %72:u32 = convert 1i
+ %73:array<u32, 1> = construct %72
+ %u_arr1:vec4<i32> = call %fn_u_U_arr_X, %73
+ %75:u32 = convert %I
+ %76:array<u32, 1> = construct %75
+ %u_arrI:vec4<i32> = call %fn_u_U_arr_X, %76
+ %78:u32 = convert 1i
+ %79:u32 = convert 0i
+ %80:array<u32, 2> = construct %78, %79
+ %u_arr1_arr0:vec4<i32> = call %fn_u_U_arr_arr_X_X, %80
+ %82:u32 = convert 2i
+ %83:u32 = convert %I
+ %84:array<u32, 2> = construct %82, %83
+ %u_arr2_arrI:vec4<i32> = call %fn_u_U_arr_arr_X_X, %84
+ %86:u32 = convert %I
+ %87:u32 = convert 2i
+ %88:array<u32, 2> = construct %86, %87
+ %u_arrI_arr2:vec4<i32> = call %fn_u_U_arr_arr_X_X, %88
+ %90:u32 = convert %I
+ %91:u32 = convert %J
+ %92:array<u32, 2> = construct %90, %91
+ %u_arrI_arrJ:vec4<i32> = call %fn_u_U_arr_arr_X_X, %92
+ %s:vec4<i32> = call %fn_s_S
+ %s_str:vec4<i32> = call %fn_s_S_str_i
+ %96:u32 = convert 0i
+ %97:array<u32, 1> = construct %96
+ %s_arr0:vec4<i32> = call %fn_s_S_arr_X, %97
+ %99:u32 = convert 1i
+ %100:array<u32, 1> = construct %99
+ %s_arr1:vec4<i32> = call %fn_s_S_arr_X, %100
+ %102:u32 = convert %I
+ %103:array<u32, 1> = construct %102
+ %s_arrI:vec4<i32> = call %fn_s_S_arr_X, %103
+ %105:u32 = convert 1i
+ %106:u32 = convert 0i
+ %107:array<u32, 2> = construct %105, %106
+ %s_arr1_arr0:vec4<i32> = call %fn_s_S_arr_arr_X_X, %107
+ %109:u32 = convert 2i
+ %110:u32 = convert %I
+ %111:array<u32, 2> = construct %109, %110
+ %s_arr2_arrI:vec4<i32> = call %fn_s_S_arr_arr_X_X, %111
+ %113:u32 = convert %I
+ %114:u32 = convert 2i
+ %115:array<u32, 2> = construct %113, %114
+ %s_arrI_arr2:vec4<i32> = call %fn_s_S_arr_arr_X_X, %115
+ %117:u32 = convert %I
+ %118:u32 = convert %J
+ %119:array<u32, 2> = construct %117, %118
+ %s_arrI_arrJ:vec4<i32> = call %fn_s_S_arr_arr_X_X, %119
+ %w:vec4<i32> = call %fn_w_W
+ %w_str:vec4<i32> = call %fn_w_W_str_i
+ %123:u32 = convert 0i
+ %124:array<u32, 1> = construct %123
+ %w_arr0:vec4<i32> = call %fn_w_W_arr_X, %124
+ %126:u32 = convert 1i
+ %127:array<u32, 1> = construct %126
+ %w_arr1:vec4<i32> = call %fn_w_W_arr_X, %127
+ %129:u32 = convert %I
+ %130:array<u32, 1> = construct %129
+ %w_arrI:vec4<i32> = call %fn_w_W_arr_X, %130
+ %132:u32 = convert 1i
+ %133:u32 = convert 0i
+ %134:array<u32, 2> = construct %132, %133
+ %w_arr1_arr0:vec4<i32> = call %fn_w_W_arr_arr_X_X, %134
+ %136:u32 = convert 2i
+ %137:u32 = convert %I
+ %138:array<u32, 2> = construct %136, %137
+ %w_arr2_arrI:vec4<i32> = call %fn_w_W_arr_arr_X_X, %138
+ %140:u32 = convert %I
+ %141:u32 = convert 2i
+ %142:array<u32, 2> = construct %140, %141
+ %w_arrI_arr2:vec4<i32> = call %fn_w_W_arr_arr_X_X, %142
+ %144:u32 = convert %I
+ %145:u32 = convert %J
+ %146:array<u32, 2> = construct %144, %145
+ %w_arrI_arrJ:vec4<i32> = call %fn_w_W_arr_arr_X_X, %146
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_Complex, Indexing) {
+ Var* S = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var<storage, array<array<array<array<i32, 9>, 9>, 9>, 50>, read>("S");
+ S->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_i = b.FunctionParam("i", ty.i32());
+ fn_a->SetParams({fn_a_i});
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, fn_a_i); });
+
+ auto* fn_b = b.Function("b", ty.i32());
+ auto* fn_b_p = b.FunctionParam("p", ty.ptr<storage, array<array<array<i32, 9>, 9>, 9>, read>());
+ fn_b->SetParams({fn_b_p});
+ b.Append(fn_b->Block(), [&] {
+ auto load_0 = b.Load(b.Access(ty.ptr<storage, i32, read>(), fn_b_p, 0_i, 1_i, 2_i));
+ auto call_0 = b.Call(fn_a, load_0);
+ auto call_1 = b.Call(fn_a, 3_i);
+ auto load_1 = b.Load(b.Access(ty.ptr<storage, i32, read>(), fn_b_p, call_1, 4_i, 5_i));
+ auto call_2 = b.Call(fn_a, load_1);
+ auto call_3 = b.Call(fn_a, 7_i);
+ auto load_2 = b.Load(b.Access(ty.ptr<storage, i32, read>(), fn_b_p, 6_i, call_3, 8_i));
+ auto call_4 = b.Call(fn_a, load_2);
+ auto load_3 =
+ b.Load(b.Access(ty.ptr<storage, i32, read>(), fn_b_p, call_0, call_2, call_4));
+
+ b.Return(fn_b, load_3);
+ });
+
+ auto* fn_c = b.Function("c", ty.void_());
+ b.Append(fn_c->Block(), [&] {
+ auto* access =
+ b.Access(ty.ptr<storage, array<array<array<i32, 9>, 9>, 9>, read>(), S, 42_i);
+ auto* v = b.Call(fn_b, access);
+ b.ir.SetName(v, "v");
+ b.Return(fn_c);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<array<array<array<i32, 9>, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+}
+
+%a = func(%i:i32):i32 -> %b2 {
+ %b2 = block {
+ ret %i
+ }
+}
+%b = func(%p:ptr<storage, array<array<array<i32, 9>, 9>, 9>, read>):i32 -> %b3 {
+ %b3 = block {
+ %6:ptr<storage, i32, read> = access %p, 0i, 1i, 2i
+ %7:i32 = load %6
+ %8:i32 = call %a, %7
+ %9:i32 = call %a, 3i
+ %10:ptr<storage, i32, read> = access %p, %9, 4i, 5i
+ %11:i32 = load %10
+ %12:i32 = call %a, %11
+ %13:i32 = call %a, 7i
+ %14:ptr<storage, i32, read> = access %p, 6i, %13, 8i
+ %15:i32 = load %14
+ %16:i32 = call %a, %15
+ %17:ptr<storage, i32, read> = access %p, %8, %12, %16
+ %18:i32 = load %17
+ ret %18
+ }
+}
+%c = func():void -> %b4 {
+ %b4 = block {
+ %20:ptr<storage, array<array<array<i32, 9>, 9>, 9>, read> = access %S, 42i
+ %v:i32 = call %b, %20
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<array<array<array<i32, 9>, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+}
+
+%a = func(%i:i32):i32 -> %b2 {
+ %b2 = block {
+ ret %i
+ }
+}
+%b_S_X = func(%p_indices:array<u32, 1>):i32 -> %b3 {
+ %b3 = block {
+ %6:u32 = access %p_indices, 0u
+ %7:ptr<storage, array<array<array<i32, 9>, 9>, 9>, read> = access %S, %6
+ %8:ptr<storage, i32, read> = access %7, 0i, 1i, 2i
+ %9:i32 = load %8
+ %10:i32 = call %a, %9
+ %11:i32 = call %a, 3i
+ %12:ptr<storage, i32, read> = access %7, %11, 4i, 5i
+ %13:i32 = load %12
+ %14:i32 = call %a, %13
+ %15:i32 = call %a, 7i
+ %16:ptr<storage, i32, read> = access %7, 6i, %15, 8i
+ %17:i32 = load %16
+ %18:i32 = call %a, %17
+ %19:ptr<storage, i32, read> = access %7, %10, %14, %18
+ %20:i32 = load %19
+ ret %20
+ }
+}
+%c = func():void -> %b4 {
+ %b4 = block {
+ %22:u32 = convert 42i
+ %23:array<u32, 1> = construct %22
+ %v:i32 = call %b_S_X, %23
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_Complex, IndexingInPtrCall) {
+ Var* S = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var<storage, array<array<array<array<i32, 9>, 9>, 9>, 50>, read>("S");
+ S->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_i = b.FunctionParam("i", ty.ptr<storage, i32, read>());
+ fn_a->SetParams({
+ b.FunctionParam("pre", ty.i32()),
+ fn_a_i,
+ b.FunctionParam("post", ty.i32()),
+ });
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, b.Load(fn_a_i)); });
+
+ auto* fn_b = b.Function("b", ty.i32());
+ auto* fn_b_p = b.FunctionParam("p", ty.ptr<storage, array<array<array<i32, 9>, 9>, 9>, read>());
+ fn_b->SetParams({fn_b_p});
+ b.Append(fn_b->Block(), [&] {
+ auto access_0 = b.Access(ty.ptr<storage, i32, read>(), fn_b_p, 0_i, 1_i, 2_i);
+ auto call_0 = b.Call(fn_a, 20_i, access_0, 30_i);
+
+ auto access_1 = b.Access(ty.ptr<storage, i32, read>(), fn_b_p, 3_i, 4_i, 5_i);
+ auto call_1 = b.Call(fn_a, 40_i, access_1, 50_i);
+
+ auto access_2 = b.Access(ty.ptr<storage, i32, read>(), fn_b_p, 6_i, 7_i, 8_i);
+ auto call_2 = b.Call(fn_a, 60_i, access_2, 70_i);
+
+ auto access_3 = b.Access(ty.ptr<storage, i32, read>(), fn_b_p, call_0, call_1, call_2);
+ auto call_3 = b.Call(fn_a, 10_i, access_3, 80_i);
+
+ b.Return(fn_b, call_3);
+ });
+
+ auto* fn_c = b.Function("c", ty.void_());
+ b.Append(fn_c->Block(), [&] {
+ auto* access =
+ b.Access(ty.ptr<storage, array<array<array<i32, 9>, 9>, 9>, read>(), S, 42_i);
+ auto* v = b.Call(fn_b, access);
+ b.ir.SetName(v, "v");
+ b.Return(fn_c);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<array<array<array<i32, 9>, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+}
+
+%a = func(%pre:i32, %i:ptr<storage, i32, read>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:i32 = load %i
+ ret %6
+ }
+}
+%b = func(%p:ptr<storage, array<array<array<i32, 9>, 9>, 9>, read>):i32 -> %b3 {
+ %b3 = block {
+ %9:ptr<storage, i32, read> = access %p, 0i, 1i, 2i
+ %10:i32 = call %a, 20i, %9, 30i
+ %11:ptr<storage, i32, read> = access %p, 3i, 4i, 5i
+ %12:i32 = call %a, 40i, %11, 50i
+ %13:ptr<storage, i32, read> = access %p, 6i, 7i, 8i
+ %14:i32 = call %a, 60i, %13, 70i
+ %15:ptr<storage, i32, read> = access %p, %10, %12, %14
+ %16:i32 = call %a, 10i, %15, 80i
+ ret %16
+ }
+}
+%c = func():void -> %b4 {
+ %b4 = block {
+ %18:ptr<storage, array<array<array<i32, 9>, 9>, 9>, read> = access %S, 42i
+ %v:i32 = call %b, %18
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<array<array<array<i32, 9>, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+}
+
+%a_S_X_X_X_X = func(%pre:i32, %i_indices:array<u32, 4>, %post:i32):i32 -> %b2 {
+ %b2 = block {
+ %6:u32 = access %i_indices, 0u
+ %7:u32 = access %i_indices, 1u
+ %8:u32 = access %i_indices, 2u
+ %9:u32 = access %i_indices, 3u
+ %10:ptr<storage, i32, read> = access %S, %6, %7, %8, %9
+ %11:i32 = load %10
+ ret %11
+ }
+}
+%b_S_X = func(%p_indices:array<u32, 1>):i32 -> %b3 {
+ %b3 = block {
+ %14:u32 = access %p_indices, 0u
+ %15:u32 = convert 0i
+ %16:u32 = convert 1i
+ %17:u32 = convert 2i
+ %18:array<u32, 4> = construct %14, %15, %16, %17
+ %19:i32 = call %a_S_X_X_X_X, 20i, %18, 30i
+ %20:u32 = convert 3i
+ %21:u32 = convert 4i
+ %22:u32 = convert 5i
+ %23:array<u32, 4> = construct %14, %20, %21, %22
+ %24:i32 = call %a_S_X_X_X_X, 40i, %23, 50i
+ %25:u32 = convert 6i
+ %26:u32 = convert 7i
+ %27:u32 = convert 8i
+ %28:array<u32, 4> = construct %14, %25, %26, %27
+ %29:i32 = call %a_S_X_X_X_X, 60i, %28, 70i
+ %30:u32 = convert %19
+ %31:u32 = convert %24
+ %32:u32 = convert %29
+ %33:array<u32, 4> = construct %14, %30, %31, %32
+ %34:i32 = call %a_S_X_X_X_X, 10i, %33, 80i
+ ret %34
+ }
+}
+%c = func():void -> %b4 {
+ %b4 = block {
+ %36:u32 = convert 42i
+ %37:array<u32, 1> = construct %36
+ %v:i32 = call %b_S_X, %37
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_Complex, IndexingDualPointers) {
+ Var* S = nullptr;
+ Var* U = nullptr;
+ b.Append(b.ir.root_block,
+ [&] { //
+ S = b.Var<storage, array<array<array<i32, 9>, 9>, 50>, read>("S");
+ S->SetBindingPoint(0, 0);
+ U = b.Var<uniform, array<array<array<vec4<i32>, 9>, 9>, 50>, read>("U");
+ U->SetBindingPoint(0, 0);
+ });
+
+ auto* fn_a = b.Function("a", ty.i32());
+ auto* fn_a_i = b.FunctionParam("i", ty.i32());
+ fn_a->SetParams({fn_a_i});
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, fn_a_i); });
+
+ auto* fn_b = b.Function("b", ty.i32());
+ auto* fn_b_s = b.FunctionParam("s", ty.ptr<storage, array<array<i32, 9>, 9>, read>());
+ auto* fn_b_u = b.FunctionParam("u", ty.ptr<uniform, array<array<vec4<i32>, 9>, 9>, read>());
+ fn_b->SetParams({fn_b_s, fn_b_u});
+ b.Append(fn_b->Block(), [&] {
+ auto access_0 = b.Access(ty.ptr<uniform, vec4<i32>, read>(), fn_b_u, 0_i, 1_i);
+ auto call_0 = b.Call(fn_a, b.LoadVectorElement(access_0, 0_u));
+ auto call_1 = b.Call(fn_a, 3_i);
+
+ auto access_1 = b.Access(ty.ptr<uniform, vec4<i32>, read>(), fn_b_u, call_1, 4_i);
+ auto call_2 = b.Call(fn_a, b.LoadVectorElement(access_1, 1_u));
+
+ auto access_2 = b.Access(ty.ptr<storage, i32, read>(), fn_b_s, call_0, call_2);
+
+ b.Return(fn_b, b.Load(access_2));
+ });
+
+ auto* fn_c = b.Function("c", ty.void_());
+ b.Append(fn_c->Block(), [&] {
+ auto* access_0 = b.Access(ty.ptr<storage, array<array<i32, 9>, 9>, read>(), S, 42_i);
+ auto* access_1 = b.Access(ty.ptr<uniform, array<array<vec4<i32>, 9>, 9>, read>(), U, 24_i);
+ auto* v = b.Call(fn_b, access_0, access_1);
+ b.ir.SetName(v, "v");
+ b.Return(fn_c);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<array<array<i32, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+ %U:ptr<uniform, array<array<array<vec4<i32>, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+}
+
+%a = func(%i:i32):i32 -> %b2 {
+ %b2 = block {
+ ret %i
+ }
+}
+%b = func(%s:ptr<storage, array<array<i32, 9>, 9>, read>, %u:ptr<uniform, array<array<vec4<i32>, 9>, 9>, read>):i32 -> %b3 {
+ %b3 = block {
+ %8:ptr<uniform, vec4<i32>, read> = access %u, 0i, 1i
+ %9:i32 = load_vector_element %8, 0u
+ %10:i32 = call %a, %9
+ %11:i32 = call %a, 3i
+ %12:ptr<uniform, vec4<i32>, read> = access %u, %11, 4i
+ %13:i32 = load_vector_element %12, 1u
+ %14:i32 = call %a, %13
+ %15:ptr<storage, i32, read> = access %s, %10, %14
+ %16:i32 = load %15
+ ret %16
+ }
+}
+%c = func():void -> %b4 {
+ %b4 = block {
+ %18:ptr<storage, array<array<i32, 9>, 9>, read> = access %S, 42i
+ %19:ptr<uniform, array<array<vec4<i32>, 9>, 9>, read> = access %U, 24i
+ %v:i32 = call %b, %18, %19
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %S:ptr<storage, array<array<array<i32, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+ %U:ptr<uniform, array<array<array<vec4<i32>, 9>, 9>, 50>, read> = var @binding_point(0, 0)
+}
+
+%a = func(%i:i32):i32 -> %b2 {
+ %b2 = block {
+ ret %i
+ }
+}
+%b_S_X_U_X = func(%s_indices:array<u32, 1>, %u_indices:array<u32, 1>):i32 -> %b3 {
+ %b3 = block {
+ %8:u32 = access %s_indices, 0u
+ %9:ptr<storage, array<array<i32, 9>, 9>, read> = access %S, %8
+ %10:u32 = access %u_indices, 0u
+ %11:ptr<uniform, array<array<vec4<i32>, 9>, 9>, read> = access %U, %10
+ %12:ptr<uniform, vec4<i32>, read> = access %11, 0i, 1i
+ %13:i32 = load_vector_element %12, 0u
+ %14:i32 = call %a, %13
+ %15:i32 = call %a, 3i
+ %16:ptr<uniform, vec4<i32>, read> = access %11, %15, 4i
+ %17:i32 = load_vector_element %16, 1u
+ %18:i32 = call %a, %17
+ %19:ptr<storage, i32, read> = access %9, %14, %18
+ %20:i32 = load %19
+ ret %20
+ }
+}
+%c = func():void -> %b4 {
+ %b4 = block {
+ %22:u32 = convert 42i
+ %23:array<u32, 1> = construct %22
+ %24:u32 = convert 24i
+ %25:array<u32, 1> = construct %24
+ %v:i32 = call %b_S_X_U_X, %23, %25
+ ret
+ }
+}
+)";
+
+ Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace complex_tests
+
+} // namespace
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc b/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
new file mode 100644
index 0000000..a764250
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
@@ -0,0 +1,2525 @@
+// 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.
+
+// GEN_BUILD:CONDITION(tint_build_wgsl_reader && tint_build_wgsl_writer)
+
+#include "src/tint/lang/core/ir/transform/direct_variable_access.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+#include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h"
+#include "src/tint/lang/wgsl/reader/reader.h"
+#include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h"
+#include "src/tint/lang/wgsl/writer/writer.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace {
+
+static constexpr DirectVariableAccessOptions kTransformPrivate = {
+ /* transform_private */ true,
+ /* transform_function */ false,
+};
+
+static constexpr DirectVariableAccessOptions kTransformFunction = {
+ /* transform_private */ false,
+ /* transform_function */ true,
+};
+
+class DirectVariableAccessTest : public TransformTestBase<testing::Test> {
+ public:
+ std::string Run(std::string in, const DirectVariableAccessOptions& options = {}) {
+ Source::File file{"test", in};
+ auto program = wgsl::reader::Parse(&file);
+ if (!program.IsValid()) {
+ return "wgsl::reader::Parse() failed: \n" + program.Diagnostics().str();
+ }
+
+ auto module = wgsl::reader::ProgramToIR(program);
+ if (!module) {
+ return "ProgramToIR() failed:\n" + module.Failure().reason.str();
+ }
+
+ auto res = DirectVariableAccess(module.Get(), options);
+ if (!res) {
+ return "DirectVariableAccess failed:\n" + res.Failure().reason.str();
+ }
+
+ auto transformed = wgsl::writer::IRToProgram(module.Get());
+ if (!transformed.IsValid()) {
+ return "wgsl::writer::IRToProgram() failed: \n" + transformed.Diagnostics().str() +
+ "\n\nIR:\n" + ir::Disassembler(module.Get()).Disassemble() + //
+ "\n\nAST:\n" + Program::printer(transformed);
+ }
+
+ auto output = wgsl::writer::Generate(transformed, wgsl::writer::Options{});
+ if (!output) {
+ return "wgsl::writer::Generate() failed: \n" + output.Failure().reason.str();
+ }
+
+ return "\n" + output->wgsl;
+ }
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// remove uncalled
+////////////////////////////////////////////////////////////////////////////////
+namespace remove_uncalled {
+
+using IR_DirectVariableAccessWgslTest_RemoveUncalled = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_RemoveUncalled, PtrUniform) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me : i32 = 42i;
+
+fn u(pre : i32, p : ptr<uniform, i32>, post : i32) -> i32 {
+ return *(p);
+}
+
+)";
+
+ auto* expect = R"(
+var<private> keep_me : i32 = 42i;
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_RemoveUncalled, PtrStorage) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me : i32 = 42i;
+
+fn s(pre : i32, p : ptr<storage, i32>, post : i32) -> i32 {
+ return *(p);
+}
+)";
+
+ auto* expect = R"(
+var<private> keep_me : i32 = 42i;
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_RemoveUncalled, PtrWorkgroup) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me : i32 = 42i;
+
+fn w(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+ return *(p);
+}
+
+)";
+
+ auto* expect = R"(
+var<private> keep_me : i32 = 42i;
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_RemoveUncalled, PtrPrivate_Disabled) {
+ auto* src = R"(
+var<private> keep_me : i32 = 42i;
+
+fn f(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+ return *(p);
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_RemoveUncalled, PtrPrivate_Enabled) {
+ auto* src = R"(
+var<private> keep_me : i32 = 42i;
+
+fn f(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+ return *(p);
+}
+)";
+
+ auto* expect = R"(
+var<private> keep_me : i32 = 42i;
+)";
+
+ auto got = Run(src, kTransformPrivate);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_RemoveUncalled, PtrFunction_Disabled) {
+ auto* src = R"(
+var<private> keep_me : i32 = 42i;
+
+fn f(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+ return *(p);
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_RemoveUncalled, PtrFunction_Enabled) {
+ auto* src = R"(
+var<private> keep_me : i32 = 42i;
+
+fn f(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+ return *(p);
+}
+)";
+
+ auto* expect = R"(
+var<private> keep_me : i32 = 42i;
+)";
+
+ auto got = Run(src, kTransformFunction);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace remove_uncalled
+
+////////////////////////////////////////////////////////////////////////////////
+// pointer chains
+////////////////////////////////////////////////////////////////////////////////
+namespace pointer_chains_tests {
+
+using IR_DirectVariableAccessWgslTest_PtrChains = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_PtrChains, ConstantIndices) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let p0 = &U;
+ let p1 = &(*p0)[1];
+ let p2 = &(*p1)[1+1];
+ let p3 = &(*p2)[2*2 - 1];
+ a(10, p3, 20);
+}
+
+fn c(p : ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>) {
+ let p0 = p;
+ let p1 = &(*p0)[1];
+ let p2 = &(*p1)[1+1];
+ let p3 = &(*p2)[2*2 - 1];
+ a(10, p3, 20);
+}
+
+fn d() {
+ c(&U);
+}
+)";
+
+ auto* expect =
+ R"(
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8u>, 8u>, 8u>;
+
+fn a_U_X_X_X(pre : i32, p_indices : array<u32, 3u>, post : i32) -> vec4<i32> {
+ return U[p_indices[0u]][p_indices[1u]][p_indices[2u]];
+}
+
+fn b() {
+ a_U_X_X_X(10i, array<u32, 3u>(u32(1i), u32(2i), u32(3i)), 20i);
+}
+
+fn c_U() {
+ a_U_X_X_X(10i, array<u32, 3u>(u32(1i), u32(2i), u32(3i)), 20i);
+}
+
+fn d() {
+ c_U();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PtrChains, DynamicIndices) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+ i++;
+ return i;
+}
+fn second() -> i32 {
+ i++;
+ return i;
+}
+fn third() -> i32 {
+ i++;
+ return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let p0 = &U;
+ let p1 = &(*p0)[first()];
+ let p2 = &(*p1)[second()][third()];
+ a(10, p2, 20);
+}
+
+fn c(p : ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>) {
+ let p0 = p;
+ let p1 = &(*p0)[first()];
+ let p2 = &(*p1)[second()][third()];
+ a(10, p2, 20);
+}
+
+fn d() {
+ c(&U);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8u>, 8u>, 8u>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn second() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn third() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn a_U_X_X_X(pre : i32, p_indices : array<u32, 3u>, post : i32) -> vec4<i32> {
+ return U[p_indices[0u]][p_indices[1u]][p_indices[2u]];
+}
+
+fn b() {
+ let v = first();
+ let v_1 = second();
+ a_U_X_X_X(10i, array<u32, 3u>(u32(v), u32(v_1), u32(third())), 20i);
+}
+
+fn c_U() {
+ let v_2 = first();
+ let v_3 = second();
+ a_U_X_X_X(10i, array<u32, 3u>(u32(v_2), u32(v_3), u32(third())), 20i);
+}
+
+fn d() {
+ c_U();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PtrChains, DynamicIndicesForLoopInit) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+ i++;
+ return i;
+}
+fn second() -> i32 {
+ i++;
+ return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ for (let p1 = &U[first()]; true; ) {
+ a(10, &(*p1)[second()], 20);
+ }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+ for (let p1 = &(*p)[first()]; true; ) {
+ a(10, &(*p1)[second()], 20);
+ }
+}
+
+fn d() {
+ c(&U);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8u>, 8u>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn second() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn a_U_X_X(pre : i32, p_indices : array<u32, 2u>, post : i32) -> vec4<i32> {
+ return U[p_indices[0u]][p_indices[1u]];
+}
+
+fn b() {
+ for(let v = first(); true; ) {
+ a_U_X_X(10i, array<u32, 2u>(u32(v), u32(second())), 20i);
+ }
+}
+
+fn c_U() {
+ for(let v_1 = first(); true; ) {
+ a_U_X_X(10i, array<u32, 2u>(u32(v_1), u32(second())), 20i);
+ }
+}
+
+fn d() {
+ c_U();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PtrChains, DynamicIndicesForLoopCond) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+ i++;
+ return i;
+}
+fn second() -> i32 {
+ i++;
+ return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let p = &U[first()][second()];
+ for (; a(10, p, 20).x < 4; ) {
+ let body = 1;
+ }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+ let p2 = &(*p)[first()][second()];
+ for (; a(10, p2, 20).x < 4; ) {
+ let body = 1;
+ }
+}
+
+fn d() {
+ c(&U);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8u>, 8u>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn second() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn a_U_X_X(pre : i32, p_indices : array<u32, 2u>, post : i32) -> vec4<i32> {
+ return U[p_indices[0u]][p_indices[1u]];
+}
+
+fn b() {
+ let v = first();
+ let v_1 = second();
+ while((a_U_X_X(10i, array<u32, 2u>(u32(v), u32(v_1)), 20i).x < 4i)) {
+ let body = 1i;
+ }
+}
+
+fn c_U() {
+ let v_2 = first();
+ let v_3 = second();
+ while((a_U_X_X(10i, array<u32, 2u>(u32(v_2), u32(v_3)), 20i).x < 4i)) {
+ let body = 1i;
+ }
+}
+
+fn d() {
+ c_U();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PtrChains, DynamicIndicesForLoopCont) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+ i++;
+ return i;
+}
+fn second() -> i32 {
+ i++;
+ return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let p = &U[first()][second()];
+ for (var i = 0; i < 3; a(10, p, 20)) {
+ i++;
+ }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+ let p2 = &(*p)[first()][second()];
+ for (var i = 0; i < 3; a(10, p2, 20)) {
+ i++;
+ }
+}
+
+fn d() {
+ c(&U);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8u>, 8u>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn second() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn a_U_X_X(pre : i32, p_indices : array<u32, 2u>, post : i32) -> vec4<i32> {
+ return U[p_indices[0u]][p_indices[1u]];
+}
+
+fn b() {
+ let v = first();
+ let v_1 = second();
+ for(var i : i32 = 0i; (i < 3i); a_U_X_X(10i, array<u32, 2u>(u32(v), u32(v_1)), 20i)) {
+ i = (i + 1i);
+ }
+}
+
+fn c_U() {
+ let v_2 = first();
+ let v_3 = second();
+ for(var i : i32; (i < 3i); a_U_X_X(10i, array<u32, 2u>(u32(v_2), u32(v_3)), 20i)) {
+ i = (i + 1i);
+ }
+}
+
+fn d() {
+ c_U();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PtrChains, DynamicIndicesWhileCond) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+ i++;
+ return i;
+}
+fn second() -> i32 {
+ i++;
+ return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let p = &U[first()][second()];
+ while (a(10, p, 20).x < 4) {
+ let body = 1;
+ }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+ let p2 = &(*p)[first()][second()];
+ while (a(10, p2, 20).x < 4) {
+ let body = 1;
+ }
+}
+
+fn d() {
+ c(&U);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8u>, 8u>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn second() -> i32 {
+ i = (i + 1i);
+ return i;
+}
+
+fn a_U_X_X(pre : i32, p_indices : array<u32, 2u>, post : i32) -> vec4<i32> {
+ return U[p_indices[0u]][p_indices[1u]];
+}
+
+fn b() {
+ let v = first();
+ let v_1 = second();
+ while((a_U_X_X(10i, array<u32, 2u>(u32(v), u32(v_1)), 20i).x < 4i)) {
+ let body = 1i;
+ }
+}
+
+fn c_U() {
+ let v_2 = first();
+ let v_3 = second();
+ while((a_U_X_X(10i, array<u32, 2u>(u32(v_2), u32(v_3)), 20i).x < 4i)) {
+ let body = 1i;
+ }
+}
+
+fn d() {
+ c_U();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+} // namespace pointer_chains_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'uniform' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace uniform_as_tests {
+
+using IR_DirectVariableAccessWgslTest_UniformAS = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_UniformAS, Param_ptr_i32_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : i32;
+
+fn a(pre : i32, p : ptr<uniform, i32>, post : i32) -> i32 {
+ return *p;
+}
+
+fn b() {
+ a(10, &U, 20);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : i32;
+
+fn a_U(pre : i32, post : i32) -> i32 {
+ return U;
+}
+
+fn b() {
+ a_U(10i, 20i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_UniformAS, Param_ptr_vec4i32_Via_array_DynamicRead) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let I = 3;
+ a(10, &U[I], 20);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8u>;
+
+fn a_U_X(pre : i32, p_indices : array<u32, 1u>, post : i32) -> vec4<i32> {
+ return U[p_indices[0u]];
+}
+
+fn b() {
+ let I = 3i;
+ a_U_X(10i, array<u32, 1u>(u32(I)), 20i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_UniformAS, CallChaining) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+fn f0(p : ptr<uniform, vec4<f32>>) -> f32 {
+ return (*p).x;
+}
+
+fn f1(p : ptr<uniform, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ {
+ // call f0() with inline usage of p
+ res += f0(&(*p)[1]);
+ }
+ {
+ // call f0() with pointer-let usage of p
+ let p_vec = &(*p)[1];
+ res += f0(p_vec);
+ }
+ {
+ // call f0() with inline usage of U
+ res += f0(&U.arr[2].mat[1]);
+ }
+ {
+ // call f0() with pointer-let usage of U
+ let p_vec = &U.arr[2].mat[1];
+ res += f0(p_vec);
+ }
+ return res;
+}
+
+fn f2(p : ptr<uniform, Inner>) -> f32 {
+ let p_mat = &(*p).mat;
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<uniform, InnerArr>, p1 : ptr<uniform, mat3x4<f32>>) -> f32 {
+ let p0_inner = &(*p0)[3];
+ return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<uniform, Outer>) -> f32 {
+ return f3(&(*p).arr, &U.mat);
+}
+
+fn b() {
+ f4(&U);
+}
+)";
+
+ auto* expect = R"(
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+struct Outer {
+ arr : array<Inner, 4u>,
+ mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+fn f0_U_mat_X(p_indices : array<u32, 1u>) -> f32 {
+ return U.mat[p_indices[0u]].x;
+}
+
+fn f0_U_arr_X_mat_X(p_indices : array<u32, 2u>) -> f32 {
+ return U.arr[p_indices[0u]].mat[p_indices[1u]].x;
+}
+
+fn f1_U_mat() -> f32 {
+ var res : f32;
+ let v = f0_U_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v);
+ let v_1 = f0_U_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v_1);
+ let v_2 = f0_U_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_2);
+ let v_3 = f0_U_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_3);
+ return res;
+}
+
+fn f1_U_arr_X_mat(p_indices : array<u32, 1u>) -> f32 {
+ let v_4 = p_indices[0u];
+ var res : f32;
+ let v_5 = f0_U_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_5);
+ let v_6 = f0_U_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_6);
+ let v_7 = f0_U_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_7);
+ let v_8 = f0_U_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_8);
+ return res;
+}
+
+fn f2_U_arr_X(p_indices : array<u32, 1u>) -> f32 {
+ return f1_U_arr_X_mat(array<u32, 1u>(p_indices[0u]));
+}
+
+fn f3_U_arr_U_mat() -> f32 {
+ return (f2_U_arr_X(array<u32, 1u>(u32(3i))) + f1_U_mat());
+}
+
+fn f4_U() -> f32 {
+ return f3_U_arr_U_mat();
+}
+
+fn b() {
+ f4_U();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace uniform_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'storage' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace storage_as_tests {
+
+using IR_DirectVariableAccessWgslTest_StorageAS = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_StorageAS, Param_ptr_i32_Via_struct_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ i : i32,
+};
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn a(pre : i32, p : ptr<storage, i32>, post : i32) -> i32 {
+ return *p;
+}
+
+fn b() {
+ a(10, &S.i, 20);
+}
+)";
+
+ auto* expect = R"(
+struct str {
+ i : i32,
+}
+
+@group(0) @binding(0) var<storage, read> S : str;
+
+fn a_S_i(pre : i32, post : i32) -> i32 {
+ return S.i;
+}
+
+fn b() {
+ a_S_i(10i, 20i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_StorageAS, Param_ptr_arr_i32_Via_struct_write) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ arr : array<i32, 4>,
+};
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn a(pre : i32, p : ptr<storage, array<i32, 4>, read_write>, post : i32) {
+ *p = array<i32, 4>();
+}
+
+fn b() {
+ a(10, &S.arr, 20);
+}
+)";
+
+ auto* expect = R"(
+struct str {
+ arr : array<i32, 4u>,
+}
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn a_S_arr(pre : i32, post : i32) {
+ S.arr = array<i32, 4u>();
+}
+
+fn b() {
+ a_S_arr(10i, 20i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_StorageAS, Param_ptr_vec4i32_Via_array_DynamicWrite) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<storage, vec4<i32>, read_write>, post : i32) {
+ *p = vec4<i32>();
+}
+
+fn b() {
+ let I = 3;
+ a(10, &S[I], 20);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<storage, read_write> S : array<vec4<i32>, 8u>;
+
+fn a_S_X(pre : i32, p_indices : array<u32, 1u>, post : i32) {
+ S[p_indices[0u]] = vec4<i32>();
+}
+
+fn b() {
+ let I = 3i;
+ a_S_X(10i, array<u32, 1u>(u32(I)), 20i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_StorageAS, CallChaining) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<storage> S : Outer;
+
+fn f0(p : ptr<storage, vec4<f32>>) -> f32 {
+ return (*p).x;
+}
+
+fn f1(p : ptr<storage, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ {
+ // call f0() with inline usage of p
+ res += f0(&(*p)[1]);
+ }
+ {
+ // call f0() with pointer-let usage of p
+ let p_vec = &(*p)[1];
+ res += f0(p_vec);
+ }
+ {
+ // call f0() with inline usage of S
+ res += f0(&S.arr[2].mat[1]);
+ }
+ {
+ // call f0() with pointer-let usage of S
+ let p_vec = &S.arr[2].mat[1];
+ res += f0(p_vec);
+ }
+ return res;
+}
+
+fn f2(p : ptr<storage, Inner>) -> f32 {
+ let p_mat = &(*p).mat;
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<storage, InnerArr>, p1 : ptr<storage, mat3x4<f32>>) -> f32 {
+ let p0_inner = &(*p0)[3];
+ return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<storage, Outer>) -> f32 {
+ return f3(&(*p).arr, &S.mat);
+}
+
+fn b() {
+ f4(&S);
+}
+)";
+
+ auto* expect = R"(
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+struct Outer {
+ arr : array<Inner, 4u>,
+ mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<storage, read> S : Outer;
+
+fn f0_S_mat_X(p_indices : array<u32, 1u>) -> f32 {
+ return S.mat[p_indices[0u]].x;
+}
+
+fn f0_S_arr_X_mat_X(p_indices : array<u32, 2u>) -> f32 {
+ return S.arr[p_indices[0u]].mat[p_indices[1u]].x;
+}
+
+fn f1_S_mat() -> f32 {
+ var res : f32;
+ let v = f0_S_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v);
+ let v_1 = f0_S_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v_1);
+ let v_2 = f0_S_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_2);
+ let v_3 = f0_S_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_3);
+ return res;
+}
+
+fn f1_S_arr_X_mat(p_indices : array<u32, 1u>) -> f32 {
+ let v_4 = p_indices[0u];
+ var res : f32;
+ let v_5 = f0_S_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_5);
+ let v_6 = f0_S_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_6);
+ let v_7 = f0_S_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_7);
+ let v_8 = f0_S_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_8);
+ return res;
+}
+
+fn f2_S_arr_X(p_indices : array<u32, 1u>) -> f32 {
+ return f1_S_arr_X_mat(array<u32, 1u>(p_indices[0u]));
+}
+
+fn f3_S_arr_S_mat() -> f32 {
+ return (f2_S_arr_X(array<u32, 1u>(u32(3i))) + f1_S_mat());
+}
+
+fn f4_S() -> f32 {
+ return f3_S_arr_S_mat();
+}
+
+fn b() {
+ f4_S();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace storage_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'workgroup' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace workgroup_as_tests {
+
+using IR_DirectVariableAccessWgslTest_WorkgroupAS = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_WorkgroupAS, Param_ptr_vec4i32_Via_array_StaticRead) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<workgroup, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ a(10, &W[3], 20);
+}
+)";
+
+ auto* expect = R"(
+var<workgroup> W : array<vec4<i32>, 8u>;
+
+fn a_W_X(pre : i32, p_indices : array<u32, 1u>, post : i32) -> vec4<i32> {
+ return W[p_indices[0u]];
+}
+
+fn b() {
+ a_W_X(10i, array<u32, 1u>(u32(3i)), 20i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_WorkgroupAS, Param_ptr_vec4i32_Via_array_StaticWrite) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<workgroup, vec4<i32>>, post : i32) {
+ *p = vec4<i32>();
+}
+
+fn b() {
+ a(10, &W[3], 20);
+}
+)";
+
+ auto* expect = R"(
+var<workgroup> W : array<vec4<i32>, 8u>;
+
+fn a_W_X(pre : i32, p_indices : array<u32, 1u>, post : i32) {
+ W[p_indices[0u]] = vec4<i32>();
+}
+
+fn b() {
+ a_W_X(10i, array<u32, 1u>(u32(3i)), 20i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_WorkgroupAS, CallChaining) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+};
+
+var<workgroup> W : Outer;
+
+fn f0(p : ptr<workgroup, vec4<f32>>) -> f32 {
+ return (*p).x;
+}
+
+fn f1(p : ptr<workgroup, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ {
+ // call f0() with inline usage of p
+ res += f0(&(*p)[1]);
+ }
+ {
+ // call f0() with pointer-let usage of p
+ let p_vec = &(*p)[1];
+ res += f0(p_vec);
+ }
+ {
+ // call f0() with inline usage of W
+ res += f0(&W.arr[2].mat[1]);
+ }
+ {
+ // call f0() with pointer-let usage of W
+ let p_vec = &W.arr[2].mat[1];
+ res += f0(p_vec);
+ }
+ return res;
+}
+
+fn f2(p : ptr<workgroup, Inner>) -> f32 {
+ let p_mat = &(*p).mat;
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<workgroup, InnerArr>, p1 : ptr<workgroup, mat3x4<f32>>) -> f32 {
+ let p0_inner = &(*p0)[3];
+ return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<workgroup, Outer>) -> f32 {
+ return f3(&(*p).arr, &W.mat);
+}
+
+fn b() {
+ f4(&W);
+}
+)";
+
+ auto* expect = R"(
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+struct Outer {
+ arr : array<Inner, 4u>,
+ mat : mat3x4<f32>,
+}
+
+var<workgroup> W : Outer;
+
+fn f0_W_mat_X(p_indices : array<u32, 1u>) -> f32 {
+ return W.mat[p_indices[0u]].x;
+}
+
+fn f0_W_arr_X_mat_X(p_indices : array<u32, 2u>) -> f32 {
+ return W.arr[p_indices[0u]].mat[p_indices[1u]].x;
+}
+
+fn f1_W_mat() -> f32 {
+ var res : f32;
+ let v = f0_W_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v);
+ let v_1 = f0_W_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v_1);
+ let v_2 = f0_W_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_2);
+ let v_3 = f0_W_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_3);
+ return res;
+}
+
+fn f1_W_arr_X_mat(p_indices : array<u32, 1u>) -> f32 {
+ let v_4 = p_indices[0u];
+ var res : f32;
+ let v_5 = f0_W_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_5);
+ let v_6 = f0_W_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_6);
+ let v_7 = f0_W_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_7);
+ let v_8 = f0_W_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_8);
+ return res;
+}
+
+fn f2_W_arr_X(p_indices : array<u32, 1u>) -> f32 {
+ return f1_W_arr_X_mat(array<u32, 1u>(p_indices[0u]));
+}
+
+fn f3_W_arr_W_mat() -> f32 {
+ return (f2_W_arr_X(array<u32, 1u>(u32(3i))) + f1_W_mat());
+}
+
+fn f4_W() -> f32 {
+ return f3_W_arr_W_mat();
+}
+
+fn b() {
+ f4_W();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace workgroup_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'private' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace private_as_tests {
+
+using IR_DirectVariableAccessWgslTest_PrivateAS = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Enabled_Param_ptr_i32_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+ return *(p);
+}
+
+var<private> P : i32;
+
+fn b() {
+ a(10, &(P), 20);
+}
+)";
+
+ auto* expect = R"(
+var<private> P : i32;
+
+fn a_P(pre : i32, post : i32) -> i32 {
+ return P;
+}
+
+fn b() {
+ a_P(10i, 20i);
+}
+)";
+
+ auto got = Run(src, kTransformPrivate);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Enabled_Param_ptr_i32_write) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) {
+ *(p) = 42;
+}
+
+var<private> P : i32;
+
+fn b() {
+ a(10, &(P), 20);
+}
+)";
+
+ auto* expect = R"(
+var<private> P : i32;
+
+fn a_P(pre : i32, post : i32) {
+ P = 42i;
+}
+
+fn b() {
+ a_P(10i, 20i);
+}
+)";
+
+ auto got = Run(src, kTransformPrivate);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Enabled_Param_ptr_i32_Via_struct_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ i : i32,
+};
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+ return *p;
+}
+
+var<private> P : str;
+
+fn b() {
+ a(10, &P.i, 20);
+}
+)";
+
+ auto* expect = R"(
+struct str {
+ i : i32,
+}
+
+var<private> P : str;
+
+fn a_P_i(pre : i32, post : i32) -> i32 {
+ return P.i;
+}
+
+fn b() {
+ a_P_i(10i, 20i);
+}
+)";
+
+ auto got = Run(src, kTransformPrivate);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Disabled_Param_ptr_i32_Via_struct_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ i : i32,
+}
+
+var<private> P : str;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+ return *(p);
+}
+
+fn b() {
+ a(10i, &(P.i), 20i);
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ arr : array<i32, 4>,
+};
+
+fn a(pre : i32, p : ptr<private, array<i32, 4>>, post : i32) {
+ *p = array<i32, 4>();
+}
+
+var<private> P : str;
+
+fn b() {
+ a(10, &P.arr, 20);
+}
+)";
+
+ auto* expect = R"(
+struct str {
+ arr : array<i32, 4u>,
+}
+
+var<private> P : str;
+
+fn a_P_arr(pre : i32, post : i32) {
+ P.arr = array<i32, 4u>();
+}
+
+fn b() {
+ a_P_arr(10i, 20i);
+}
+)";
+
+ auto got = Run(src, kTransformPrivate);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ arr : array<i32, 4u>,
+}
+
+var<private> P : str;
+
+fn a(pre : i32, p : ptr<private, array<i32, 4u>>, post : i32) {
+ *(p) = array<i32, 4u>();
+}
+
+fn b() {
+ a(10i, &(P.arr), 20i);
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Enabled_Param_ptr_i32_mixed) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ i : i32,
+};
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+ return *p;
+}
+
+var<private> Pi : i32;
+var<private> Ps : str;
+var<private> Pa : array<i32, 4>;
+
+fn b() {
+ a(10, &Pi, 20);
+ a(30, &Ps.i, 40);
+ a(50, &Pa[2], 60);
+}
+)";
+
+ auto* expect = R"(
+var<private> Pi : i32;
+
+struct str {
+ i : i32,
+}
+
+var<private> Ps : str;
+
+var<private> Pa : array<i32, 4u>;
+
+fn a_Pi(pre : i32, post : i32) -> i32 {
+ return Pi;
+}
+
+fn a_Ps_i(pre : i32, post : i32) -> i32 {
+ return Ps.i;
+}
+
+fn a_Pa_X(pre : i32, p_indices : array<u32, 1u>, post : i32) -> i32 {
+ return Pa[p_indices[0u]];
+}
+
+fn b() {
+ a_Pi(10i, 20i);
+ a_Ps_i(30i, 40i);
+ a_Pa_X(50i, array<u32, 1u>(u32(2i)), 60i);
+}
+)";
+
+ auto got = Run(src, kTransformPrivate);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Disabled_Param_ptr_i32_mixed) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> Pi : i32;
+
+struct str {
+ i : i32,
+}
+
+var<private> Ps : str;
+
+var<private> Pa : array<i32, 4u>;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+ return *(p);
+}
+
+fn b() {
+ a(10i, &(Pi), 20i);
+ a(30i, &(Ps.i), 40i);
+ a(50i, &(Pa[2i]), 60i);
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Enabled_CallChaining) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+};
+
+var<private> P : Outer;
+
+fn f0(p : ptr<private, vec4<f32>>) -> f32 {
+ return (*p).x;
+}
+
+fn f1(p : ptr<private, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ {
+ // call f0() with inline usage of p
+ res += f0(&(*p)[1]);
+ }
+ {
+ // call f0() with pointer-let usage of p
+ let p_vec = &(*p)[1];
+ res += f0(p_vec);
+ }
+ {
+ // call f0() with inline usage of P
+ res += f0(&P.arr[2].mat[1]);
+ }
+ {
+ // call f0() with pointer-let usage of P
+ let p_vec = &P.arr[2].mat[1];
+ res += f0(p_vec);
+ }
+ return res;
+}
+
+fn f2(p : ptr<private, Inner>) -> f32 {
+ let p_mat = &(*p).mat;
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<private, InnerArr>, p1 : ptr<private, mat3x4<f32>>) -> f32 {
+ let p0_inner = &(*p0)[3];
+ return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<private, Outer>) -> f32 {
+ return f3(&(*p).arr, &P.mat);
+}
+
+fn b() {
+ f4(&P);
+}
+)";
+
+ auto* expect = R"(
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+struct Outer {
+ arr : array<Inner, 4u>,
+ mat : mat3x4<f32>,
+}
+
+var<private> P : Outer;
+
+fn f0_P_mat_X(p_indices : array<u32, 1u>) -> f32 {
+ return P.mat[p_indices[0u]].x;
+}
+
+fn f0_P_arr_X_mat_X(p_indices : array<u32, 2u>) -> f32 {
+ return P.arr[p_indices[0u]].mat[p_indices[1u]].x;
+}
+
+fn f1_P_mat() -> f32 {
+ var res : f32;
+ let v = f0_P_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v);
+ let v_1 = f0_P_mat_X(array<u32, 1u>(u32(1i)));
+ res = (res + v_1);
+ let v_2 = f0_P_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_2);
+ let v_3 = f0_P_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_3);
+ return res;
+}
+
+fn f1_P_arr_X_mat(p_indices : array<u32, 1u>) -> f32 {
+ let v_4 = p_indices[0u];
+ var res : f32;
+ let v_5 = f0_P_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_5);
+ let v_6 = f0_P_arr_X_mat_X(array<u32, 2u>(v_4, u32(1i)));
+ res = (res + v_6);
+ let v_7 = f0_P_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_7);
+ let v_8 = f0_P_arr_X_mat_X(array<u32, 2u>(u32(2i), u32(1i)));
+ res = (res + v_8);
+ return res;
+}
+
+fn f2_P_arr_X(p_indices : array<u32, 1u>) -> f32 {
+ return f1_P_arr_X_mat(array<u32, 1u>(p_indices[0u]));
+}
+
+fn f3_P_arr_P_mat() -> f32 {
+ return (f2_P_arr_X(array<u32, 1u>(u32(3i))) + f1_P_mat());
+}
+
+fn f4_P() -> f32 {
+ return f3_P_arr_P_mat();
+}
+
+fn b() {
+ f4_P();
+}
+)";
+
+ auto got = Run(src, kTransformPrivate);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_PrivateAS, Disabled_CallChaining) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+struct Outer {
+ arr : array<Inner, 4u>,
+ mat : mat3x4<f32>,
+}
+
+var<private> P : Outer;
+
+fn f0(p : ptr<private, vec4<f32>>) -> f32 {
+ return (*(p)).x;
+}
+
+fn f1(p : ptr<private, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ let v = f0(&((*(p))[1i]));
+ res = (res + v);
+ let p_vec = &((*(p))[1i]);
+ let v_1 = f0(p_vec);
+ res = (res + v_1);
+ let v_2 = f0(&(P.arr[2i].mat[1i]));
+ res = (res + v_2);
+ let p_vec_1 = &(P.arr[2i].mat[1i]);
+ let v_3 = f0(p_vec_1);
+ res = (res + v_3);
+ return res;
+}
+
+fn f2(p : ptr<private, Inner>) -> f32 {
+ let p_mat = &((*(p)).mat);
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<private, array<Inner, 4u>>, p1 : ptr<private, mat3x4<f32>>) -> f32 {
+ let p0_inner = &((*(p0))[3i]);
+ return (f2(p0_inner) + f1(p1));
+}
+
+fn f4(p : ptr<private, Outer>) -> f32 {
+ return f3(&((*(p)).arr), &(P.mat));
+}
+
+fn b() {
+ f4(&(P));
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace private_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'function' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace function_as_tests {
+
+using IR_DirectVariableAccessWgslTest_FunctionAS = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Enabled_LocalPtr) {
+ auto* src = R"(
+fn f() {
+ var v : i32;
+ let p = &(v);
+ var x : i32 = *(p);
+}
+)";
+
+ auto* expect = src; // Nothing changes
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Enabled_Param_ptr_i32_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+ return *(p);
+}
+
+fn b() {
+ var F : i32;
+ a(10, &(F), 20);
+}
+)";
+
+ auto* expect = R"(
+fn a_P(pre : i32, p_root : ptr<function, i32>, post : i32) -> i32 {
+ return *(p_root);
+}
+
+fn b() {
+ var F : i32;
+ a_P(10i, &(F), 20i);
+}
+)";
+
+ auto got = Run(src, kTransformFunction);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Enabled_Param_ptr_i32_write) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) {
+ *(p) = 42;
+}
+
+fn b() {
+ var F : i32;
+ a(10, &(F), 20);
+}
+)";
+
+ auto* expect = R"(
+fn a_P(pre : i32, p_root : ptr<function, i32>, post : i32) {
+ *(p_root) = 42i;
+}
+
+fn b() {
+ var F : i32;
+ a_P(10i, &(F), 20i);
+}
+)";
+
+ auto got = Run(src, kTransformFunction);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Enabled_Param_ptr_i32_Via_struct_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ i : i32,
+};
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+ return *p;
+}
+
+fn b() {
+ var F : str;
+ a(10, &F.i, 20);
+}
+)";
+
+ auto* expect = R"(
+struct str {
+ i : i32,
+}
+
+fn a_P_i(pre : i32, p_root : ptr<function, str>, post : i32) -> i32 {
+ return (*(p_root)).i;
+}
+
+fn b() {
+ var F : str;
+ a_P_i(10i, &(F), 20i);
+}
+)";
+
+ auto got = Run(src, kTransformFunction);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ arr : array<i32, 4>,
+};
+
+fn a(pre : i32, p : ptr<function, array<i32, 4>>, post : i32) {
+ *p = array<i32, 4>();
+}
+
+fn b() {
+ var F : str;
+ a(10, &F.arr, 20);
+}
+)";
+
+ auto* expect = R"(
+struct str {
+ arr : array<i32, 4u>,
+}
+
+fn a_P_arr(pre : i32, p_root : ptr<function, str>, post : i32) {
+ (*(p_root)).arr = array<i32, 4u>();
+}
+
+fn b() {
+ var F : str;
+ a_P_arr(10i, &(F), 20i);
+}
+)";
+
+ auto got = Run(src, kTransformFunction);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Enabled_Param_ptr_i32_mixed) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ i : i32,
+};
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+ return *p;
+}
+
+fn b() {
+ var Fi : i32;
+ var Fs : str;
+ var Fa : array<i32, 4>;
+
+ a(10, &Fi, 20);
+ a(30, &Fs.i, 40);
+ a(50, &Fa[2], 60);
+}
+)";
+
+ auto* expect = R"(
+fn a_P(pre : i32, p_root : ptr<function, i32>, post : i32) -> i32 {
+ return *(p_root);
+}
+
+struct str {
+ i : i32,
+}
+
+fn a_P_i(pre : i32, p_root : ptr<function, str>, post : i32) -> i32 {
+ return (*(p_root)).i;
+}
+
+fn a_P_X(pre : i32, p_root : ptr<function, array<i32, 4u>>, p_indices : array<u32, 1u>, post : i32) -> i32 {
+ return (*(p_root))[p_indices[0u]];
+}
+
+fn b() {
+ var Fi : i32;
+ var Fs : str;
+ var Fa : array<i32, 4u>;
+ a_P(10i, &(Fi), 20i);
+ a_P_i(30i, &(Fs), 40i);
+ a_P_X(50i, &(Fa), array<u32, 1u>(u32(2i)), 60i);
+}
+)";
+
+ auto got = Run(src, kTransformFunction);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Disabled_Param_ptr_i32_Via_struct_read) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+ return *(p);
+}
+
+struct str {
+ i : i32,
+}
+
+fn b() {
+ var F : str;
+ a(10i, &(F.i), 20i);
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_FunctionAS, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, array<i32, 4u>>, post : i32) {
+ *(p) = array<i32, 4u>();
+}
+
+struct str {
+ arr : array<i32, 4u>,
+}
+
+fn b() {
+ var F : str;
+ a(10i, &(F.arr), 20i);
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace function_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// builtin function calls
+////////////////////////////////////////////////////////////////////////////////
+namespace builtin_fn_calls {
+
+using IR_DirectVariableAccessWgslTest_BuiltinFn = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_BuiltinFn, ArrayLength) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<f32>;
+
+fn len(p : ptr<storage, array<f32>>) -> u32 {
+ return arrayLength(p);
+}
+
+fn f() {
+ let n = len(&S);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<storage, read> S : array<f32>;
+
+fn len_S() -> u32 {
+ return arrayLength(&(S));
+}
+
+fn f() {
+ len_S();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_BuiltinFn, WorkgroupUniformLoad) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : f32;
+
+fn load(p : ptr<workgroup, f32>) -> f32 {
+ return workgroupUniformLoad(p);
+}
+
+fn f() {
+ let v = load(&W);
+}
+)";
+
+ auto* expect = R"(
+var<workgroup> W : f32;
+
+fn load_W() -> f32 {
+ return workgroupUniformLoad(&(W));
+}
+
+fn f() {
+ load_W();
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace builtin_fn_calls
+
+////////////////////////////////////////////////////////////////////////////////
+// complex tests
+////////////////////////////////////////////////////////////////////////////////
+namespace complex_tests {
+
+using IR_DirectVariableAccessWgslTest_Complex = DirectVariableAccessTest;
+
+TEST_F(IR_DirectVariableAccessWgslTest_Complex, Param_ptr_mixed_vec4i32_ViaMultiple) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+ i : vec4<i32>,
+};
+
+@group(0) @binding(0) var<uniform> U : vec4<i32>;
+@group(0) @binding(1) var<uniform> U_str : str;
+@group(0) @binding(2) var<uniform> U_arr : array<vec4<i32>, 8>;
+@group(0) @binding(3) var<uniform> U_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+@group(1) @binding(0) var<storage> S : vec4<i32>;
+@group(1) @binding(1) var<storage> S_str : str;
+@group(1) @binding(2) var<storage> S_arr : array<vec4<i32>, 8>;
+@group(1) @binding(3) var<storage> S_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+ var<workgroup> W : vec4<i32>;
+ var<workgroup> W_str : str;
+ var<workgroup> W_arr : array<vec4<i32>, 8>;
+ var<workgroup> W_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+fn fn_u(p : ptr<uniform, vec4<i32>>) -> vec4<i32> {
+ return *p;
+}
+
+fn fn_s(p : ptr<storage, vec4<i32>>) -> vec4<i32> {
+ return *p;
+}
+
+fn fn_w(p : ptr<workgroup, vec4<i32>>) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let I = 3;
+ let J = 4;
+
+ let u = fn_u(&U);
+ let u_str = fn_u(&U_str.i);
+ let u_arr0 = fn_u(&U_arr[0]);
+ let u_arr1 = fn_u(&U_arr[1]);
+ let u_arrI = fn_u(&U_arr[I]);
+ let u_arr1_arr0 = fn_u(&U_arr_arr[1][0]);
+ let u_arr2_arrI = fn_u(&U_arr_arr[2][I]);
+ let u_arrI_arr2 = fn_u(&U_arr_arr[I][2]);
+ let u_arrI_arrJ = fn_u(&U_arr_arr[I][J]);
+
+ let s = fn_s(&S);
+ let s_str = fn_s(&S_str.i);
+ let s_arr0 = fn_s(&S_arr[0]);
+ let s_arr1 = fn_s(&S_arr[1]);
+ let s_arrI = fn_s(&S_arr[I]);
+ let s_arr1_arr0 = fn_s(&S_arr_arr[1][0]);
+ let s_arr2_arrI = fn_s(&S_arr_arr[2][I]);
+ let s_arrI_arr2 = fn_s(&S_arr_arr[I][2]);
+ let s_arrI_arrJ = fn_s(&S_arr_arr[I][J]);
+
+ let w = fn_w(&W);
+ let w_str = fn_w(&W_str.i);
+ let w_arr0 = fn_w(&W_arr[0]);
+ let w_arr1 = fn_w(&W_arr[1]);
+ let w_arrI = fn_w(&W_arr[I]);
+ let w_arr1_arr0 = fn_w(&W_arr_arr[1][0]);
+ let w_arr2_arrI = fn_w(&W_arr_arr[2][I]);
+ let w_arrI_arr2 = fn_w(&W_arr_arr[I][2]);
+ let w_arrI_arrJ = fn_w(&W_arr_arr[I][J]);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<uniform> U : vec4<i32>;
+
+struct str {
+ i : vec4<i32>,
+}
+
+@group(0) @binding(1) var<uniform> U_str : str;
+
+@group(0) @binding(2) var<uniform> U_arr : array<vec4<i32>, 8u>;
+
+@group(0) @binding(3) var<uniform> U_arr_arr : array<array<vec4<i32>, 8u>, 4u>;
+
+@group(1) @binding(0) var<storage, read> S : vec4<i32>;
+
+@group(1) @binding(1) var<storage, read> S_str : str;
+
+@group(1) @binding(2) var<storage, read> S_arr : array<vec4<i32>, 8u>;
+
+@group(1) @binding(3) var<storage, read> S_arr_arr : array<array<vec4<i32>, 8u>, 4u>;
+
+var<workgroup> W : vec4<i32>;
+
+var<workgroup> W_str : str;
+
+var<workgroup> W_arr : array<vec4<i32>, 8u>;
+
+var<workgroup> W_arr_arr : array<array<vec4<i32>, 8u>, 4u>;
+
+fn fn_u_U() -> vec4<i32> {
+ return U;
+}
+
+fn fn_u_U_str_i() -> vec4<i32> {
+ return U_str.i;
+}
+
+fn fn_u_U_arr_X(p_indices : array<u32, 1u>) -> vec4<i32> {
+ return U_arr[p_indices[0u]];
+}
+
+fn fn_u_U_arr_arr_X_X(p_indices : array<u32, 2u>) -> vec4<i32> {
+ return U_arr_arr[p_indices[0u]][p_indices[1u]];
+}
+
+fn fn_s_S() -> vec4<i32> {
+ return S;
+}
+
+fn fn_s_S_str_i() -> vec4<i32> {
+ return S_str.i;
+}
+
+fn fn_s_S_arr_X(p_indices : array<u32, 1u>) -> vec4<i32> {
+ return S_arr[p_indices[0u]];
+}
+
+fn fn_s_S_arr_arr_X_X(p_indices : array<u32, 2u>) -> vec4<i32> {
+ return S_arr_arr[p_indices[0u]][p_indices[1u]];
+}
+
+fn fn_w_W() -> vec4<i32> {
+ return W;
+}
+
+fn fn_w_W_str_i() -> vec4<i32> {
+ return W_str.i;
+}
+
+fn fn_w_W_arr_X(p_indices : array<u32, 1u>) -> vec4<i32> {
+ return W_arr[p_indices[0u]];
+}
+
+fn fn_w_W_arr_arr_X_X(p_indices : array<u32, 2u>) -> vec4<i32> {
+ return W_arr_arr[p_indices[0u]][p_indices[1u]];
+}
+
+fn b() {
+ let I = 3i;
+ let J = 4i;
+ fn_u_U();
+ fn_u_U_str_i();
+ fn_u_U_arr_X(array<u32, 1u>(u32(0i)));
+ fn_u_U_arr_X(array<u32, 1u>(u32(1i)));
+ fn_u_U_arr_X(array<u32, 1u>(u32(I)));
+ fn_u_U_arr_arr_X_X(array<u32, 2u>(u32(1i), u32(0i)));
+ fn_u_U_arr_arr_X_X(array<u32, 2u>(u32(2i), u32(I)));
+ fn_u_U_arr_arr_X_X(array<u32, 2u>(u32(I), u32(2i)));
+ fn_u_U_arr_arr_X_X(array<u32, 2u>(u32(I), u32(J)));
+ fn_s_S();
+ fn_s_S_str_i();
+ fn_s_S_arr_X(array<u32, 1u>(u32(0i)));
+ fn_s_S_arr_X(array<u32, 1u>(u32(1i)));
+ fn_s_S_arr_X(array<u32, 1u>(u32(I)));
+ fn_s_S_arr_arr_X_X(array<u32, 2u>(u32(1i), u32(0i)));
+ fn_s_S_arr_arr_X_X(array<u32, 2u>(u32(2i), u32(I)));
+ fn_s_S_arr_arr_X_X(array<u32, 2u>(u32(I), u32(2i)));
+ fn_s_S_arr_arr_X_X(array<u32, 2u>(u32(I), u32(J)));
+ fn_w_W();
+ fn_w_W_str_i();
+ fn_w_W_arr_X(array<u32, 1u>(u32(0i)));
+ fn_w_W_arr_X(array<u32, 1u>(u32(1i)));
+ fn_w_W_arr_X(array<u32, 1u>(u32(I)));
+ fn_w_W_arr_arr_X_X(array<u32, 2u>(u32(1i), u32(0i)));
+ fn_w_W_arr_arr_X_X(array<u32, 2u>(u32(2i), u32(I)));
+ fn_w_W_arr_arr_X_X(array<u32, 2u>(u32(I), u32(2i)));
+ fn_w_W_arr_arr_X_X(array<u32, 2u>(u32(I), u32(J)));
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_Complex, Indexing) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 { return i; }
+
+fn b(p : ptr<storage, array<array<array<i32, 9>, 9>, 9>>) -> i32 {
+ return (*p) [ a( (*p)[0][1][2] )]
+ [ a( (*p)[a(3)][4][5] )]
+ [ a( (*p)[6][a(7)][8] )];
+}
+
+fn c() {
+ let v = b(&S[42]);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<storage, read> S : array<array<array<array<i32, 9u>, 9u>, 9u>, 50u>;
+
+fn a(i : i32) -> i32 {
+ return i;
+}
+
+fn b_S_X(p_indices : array<u32, 1u>) -> i32 {
+ let v = &(S[p_indices[0u]]);
+ return (*(v))[a((*(v))[0i][1i][2i])][a((*(v))[a(3i)][4i][5i])][a((*(v))[6i][a(7i)][8i])];
+}
+
+fn c() {
+ b_S_X(array<u32, 1u>(u32(42i)));
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_Complex, IndexingInPtrCall) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(pre : i32, i : ptr<storage, i32>, post : i32) -> i32 {
+ return *i;
+}
+
+fn b(p : ptr<storage, array<array<array<i32, 9>, 9>, 9>>) -> i32 {
+ return a(10, &(*p)[ a( 20, &(*p)[0][1][2], 30 )]
+ [ a( 40, &(*p)[3][4][5], 50 )]
+ [ a( 60, &(*p)[6][7][8], 70 )], 80);
+}
+
+fn c() {
+ let v = b(&S[42]);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<storage, read> S : array<array<array<array<i32, 9u>, 9u>, 9u>, 50u>;
+
+fn a_S_X_X_X_X(pre : i32, i_indices : array<u32, 4u>, post : i32) -> i32 {
+ return S[i_indices[0u]][i_indices[1u]][i_indices[2u]][i_indices[3u]];
+}
+
+fn b_S_X(p_indices : array<u32, 1u>) -> i32 {
+ let v = p_indices[0u];
+ let v_1 = a_S_X_X_X_X(20i, array<u32, 4u>(v, u32(0i), u32(1i), u32(2i)), 30i);
+ let v_2 = a_S_X_X_X_X(40i, array<u32, 4u>(v, u32(3i), u32(4i), u32(5i)), 50i);
+ return a_S_X_X_X_X(10i, array<u32, 4u>(v, u32(v_1), u32(v_2), u32(a_S_X_X_X_X(60i, array<u32, 4u>(v, u32(6i), u32(7i), u32(8i)), 70i))), 80i);
+}
+
+fn c() {
+ b_S_X(array<u32, 1u>(u32(42i)));
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+TEST_F(IR_DirectVariableAccessWgslTest_Complex, IndexingDualPointers) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<i32, 9>, 9>, 50>;
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 { return i; }
+
+fn b(s : ptr<storage, array<array<i32, 9>, 9>>,
+ u : ptr<uniform, array<array<vec4<i32>, 9>, 9>>) -> i32 {
+ return (*s) [ a( (*u)[0][1].x )]
+ [ a( (*u)[a(3)][4].y )];
+}
+
+fn c() {
+ let v = b(&S[42], &U[24]);
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<storage, read> S : array<array<array<i32, 9u>, 9u>, 50u>;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 9u>, 9u>, 50u>;
+
+fn a(i : i32) -> i32 {
+ return i;
+}
+
+fn b_S_X_U_X(s_indices : array<u32, 1u>, u_indices : array<u32, 1u>) -> i32 {
+ let v = &(U[u_indices[0u]]);
+ return S[s_indices[0u]][a((*(v))[0i][1i].x)][a((*(v))[a(3i)][4i].y)];
+}
+
+fn c() {
+ b_S_X_U_X(array<u32, 1u>(u32(42i)), array<u32, 1u>(u32(24i)));
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, got);
+}
+
+} // namespace complex_tests
+
+} // namespace
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/user_call.cc b/src/tint/lang/core/ir/user_call.cc
index 282a5ee..641dc20 100644
--- a/src/tint/lang/core/ir/user_call.cc
+++ b/src/tint/lang/core/ir/user_call.cc
@@ -39,4 +39,11 @@
return ctx.ir.instructions.Create<UserCall>(new_result, target, args);
}
+void UserCall::SetArgs(VectorRef<Value*> arguments) {
+ auto* fn = Target();
+ ClearOperands();
+ AddOperand(UserCall::kFunctionOperandOffset, fn);
+ AddOperands(UserCall::kArgsOperandOffset, std::move(arguments));
+}
+
} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/user_call.h b/src/tint/lang/core/ir/user_call.h
index f6688e2..21d2416 100644
--- a/src/tint/lang/core/ir/user_call.h
+++ b/src/tint/lang/core/ir/user_call.h
@@ -45,12 +45,16 @@
/// @returns the call arguments
tint::Slice<Value*> Args() override { return operands_.Slice().Offset(kArgsOperandOffset); }
+ /// Replaces the call arguments to @p arguments
+ /// @param arguments the new call arguments
+ void SetArgs(VectorRef<Value*> arguments);
+
/// @returns the called function
Function* Target() { return operands_[kFunctionOperandOffset]->As<ir::Function>(); }
/// Sets called function
/// @param target the new target of the call
- void SetTarget(Function* target) { operands_[kFunctionOperandOffset] = target; }
+ void SetTarget(Function* target) { SetOperand(kFunctionOperandOffset, target); }
/// @returns the friendly name for the instruction
std::string FriendlyName() override { return "call"; }
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index 7115650..8160549 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -1428,7 +1428,9 @@
Impl b(program);
auto r = b.Build();
if (!r) {
- return r.Failure();
+ diag::List err = std::move(r.Failure().reason);
+ err.add_note(diag::System::IR, "AST:\n" + Program::printer(program), Source{});
+ return Failure{err};
}
return r.Move();