spirv-opt: Add LegalizeMultidimArrayPass (#6535)
This pass transforms multidimensional arrays of resources (e.g.,
Texture2D g_Textures[2][3]) into single-dimensional arrays (e.g.,
Texture2D g_Textures[6]) and updates all access chains that reference
them.
This transformation is required for Vulkan compatibility, as
multidimensional
resource arrays are not allowed in some storage classes.
Specifically, it helps avoid:
- VUID-StandaloneSpirv-Uniform-06807
- VUID-StandaloneSpirv-UniformConstant-04655
Fixes https://github.com/microsoft/DirectXShaderCompiler/issues/7922
diff --git a/Android.mk b/Android.mk
index f9e2360..9e200f8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -144,6 +144,7 @@
source/opt/invocation_interlock_placement_pass.cpp \
source/opt/ir_context.cpp \
source/opt/ir_loader.cpp \
+ source/opt/legalize_multidim_array_pass.cpp \
source/opt/licm_pass.cpp \
source/opt/liveness.cpp \
source/opt/local_access_chain_convert_pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 7e0aa44..c16755d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -520,6 +520,8 @@
"source/opt/ir_loader.cpp",
"source/opt/ir_loader.h",
"source/opt/iterator.h",
+ "source/opt/legalize_multidim_array_pass.cpp",
+ "source/opt/legalize_multidim_array_pass.h",
"source/opt/licm_pass.cpp",
"source/opt/licm_pass.h",
"source/opt/liveness.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index b25ad47..fd4527b 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -645,6 +645,11 @@
// Works best after LICM and local multi store elimination pass.
Optimizer::PassToken CreateLoopUnswitchPass();
+// Creates a pass to legalize multidimensional arrays for Vulkan.
+// This pass will replace multidimensional arrays of resources with a single
+// dimensional array. Combine-access-chains should be run before this pass.
+Optimizer::PassToken CreateLegalizeMultidimArrayPass();
+
// Create global value numbering pass.
// This pass will look for instructions where the same value is computed on all
// paths leading to the instruction. Those instructions are deleted.
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index ff32dfa..a0ca5b8 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -75,6 +75,7 @@
ir_context.h
ir_loader.h
licm_pass.h
+ legalize_multidim_array_pass.h
liveness.h
local_access_chain_convert_pass.h
local_redundancy_elimination.h
@@ -197,6 +198,7 @@
ir_context.cpp
ir_loader.cpp
licm_pass.cpp
+ legalize_multidim_array_pass.cpp
liveness.cpp
local_access_chain_convert_pass.cpp
local_redundancy_elimination.cpp
diff --git a/source/opt/legalize_multidim_array_pass.cpp b/source/opt/legalize_multidim_array_pass.cpp
new file mode 100644
index 0000000..cd207b0
--- /dev/null
+++ b/source/opt/legalize_multidim_array_pass.cpp
@@ -0,0 +1,275 @@
+// Copyright (c) 2026 Google LLC
+//
+// 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 "source/opt/legalize_multidim_array_pass.h"
+
+#include "source/opt/constants.h"
+#include "source/opt/desc_sroa_util.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/type_manager.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status LegalizeMultidimArrayPass::Process() {
+ std::vector<Instruction*> vars_to_legalize;
+
+ for (auto& var : context()->types_values()) {
+ if (var.opcode() != spv::Op::OpVariable) continue;
+ if (!IsMultidimArrayOfResources(&var)) continue;
+ if (!CanLegalize(&var)) {
+ context()->EmitErrorMessage("Unable to legalize multidimensional array: ",
+ &var);
+ return Status::Failure;
+ }
+ vars_to_legalize.push_back(&var);
+ }
+
+ if (vars_to_legalize.empty()) return Status::SuccessWithoutChange;
+
+ for (auto* var : vars_to_legalize) {
+ uint32_t old_ptr_type_id = var->type_id();
+ uint32_t new_ptr_type_id = FlattenArrayType(var);
+ if (new_ptr_type_id == 0) return Status::Failure;
+ if (!RewriteAccessChains(var, old_ptr_type_id)) return Status::Failure;
+ }
+
+ return Status::SuccessWithChange;
+}
+
+bool LegalizeMultidimArrayPass::IsMultidimArrayOfResources(Instruction* var) {
+ if (!descsroautil::IsDescriptorArray(context(), var)) return false;
+
+ uint32_t type_id = var->type_id();
+ Instruction* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
+ uint32_t pointee_type_id = type_inst->GetSingleWordInOperand(1);
+ std::vector<uint32_t> dims;
+ uint32_t element_type_id = 0;
+ GetArrayDimensions(pointee_type_id, &dims, &element_type_id);
+
+ return dims.size() > 1;
+}
+
+void LegalizeMultidimArrayPass::GetArrayDimensions(uint32_t type_id,
+ std::vector<uint32_t>* dims,
+ uint32_t* element_type_id) {
+ assert(dims != nullptr && "dims cannot be null.");
+ dims->clear();
+
+ Instruction* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
+ while (type_inst->opcode() == spv::Op::OpTypeArray) {
+ uint32_t length_id = type_inst->GetSingleWordInOperand(1);
+ Instruction* length_inst = context()->get_def_use_mgr()->GetDef(length_id);
+ // Assume OpConstant. According to the spec the length could also be an
+ // OpSpecConstantOp. However, DXC will not generate that type of code. The
+ // code to handle spec constants will be much more complicated.
+ assert(length_inst->opcode() == spv::Op::OpConstant);
+ uint32_t length = length_inst->GetSingleWordInOperand(0);
+ dims->push_back(length);
+ type_id = type_inst->GetSingleWordInOperand(0);
+ type_inst = context()->get_def_use_mgr()->GetDef(type_id);
+ }
+ *element_type_id = type_id;
+}
+
+uint32_t LegalizeMultidimArrayPass::FlattenArrayType(Instruction* var) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::ConstantManager* constant_mgr = context()->get_constant_mgr();
+
+ uint32_t ptr_type_id = var->type_id();
+ Instruction* ptr_type_inst =
+ context()->get_def_use_mgr()->GetDef(ptr_type_id);
+ uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
+
+ std::vector<uint32_t> dims;
+ uint32_t element_type_id = 0;
+ GetArrayDimensions(pointee_type_id, &dims, &element_type_id);
+
+ uint32_t total_elements = 1;
+ for (uint32_t dim : dims) {
+ total_elements *= dim;
+ }
+
+ const analysis::Constant* total_elements_const =
+ constant_mgr->GetIntConst(total_elements, 32, false);
+
+ Instruction* total_elements_inst =
+ constant_mgr->GetDefiningInstruction(total_elements_const);
+ uint32_t total_elements_id = total_elements_inst->result_id();
+
+ // Create new OpTypeArray.
+ analysis::Type* element_type = type_mgr->GetType(element_type_id);
+ analysis::Array::LengthInfo length_info = {
+ total_elements_id,
+ {analysis::Array::LengthInfo::kConstant, total_elements}};
+ analysis::Array new_array_type(element_type, length_info);
+ uint32_t new_array_type_id = type_mgr->GetTypeInstruction(&new_array_type);
+
+ // Create new OpTypePointer.
+ spv::StorageClass sc =
+ static_cast<spv::StorageClass>(ptr_type_inst->GetSingleWordInOperand(0));
+ analysis::Pointer new_ptr_type(type_mgr->GetType(new_array_type_id), sc);
+ uint32_t new_ptr_type_id = type_mgr->GetTypeInstruction(&new_ptr_type);
+
+ var->SetResultType(new_ptr_type_id);
+ context()->UpdateDefUse(var);
+
+ // Move the var after the new pointer type to avoid a def-before-use.
+ var->InsertAfter(get_def_use_mgr()->GetDef(new_ptr_type_id));
+
+ return new_ptr_type_id;
+}
+
+bool LegalizeMultidimArrayPass::RewriteAccessChains(Instruction* var,
+ uint32_t old_ptr_type_id) {
+ uint32_t var_id = var->result_id();
+ std::vector<Instruction*> users;
+ // Use a worklist to handle transitive uses (e.g. through OpCopyObject)
+ std::vector<Instruction*> worklist;
+
+ context()->get_def_use_mgr()->ForEachUser(
+ var_id, [&worklist](Instruction* user) { worklist.push_back(user); });
+
+ Instruction* old_ptr_type_inst =
+ context()->get_def_use_mgr()->GetDef(old_ptr_type_id);
+ uint32_t old_pointee_type_id = old_ptr_type_inst->GetSingleWordInOperand(1);
+ std::vector<uint32_t> dims;
+ uint32_t element_type_id = 0;
+ GetArrayDimensions(old_pointee_type_id, &dims, &element_type_id);
+ assert(dims.size() != 0 &&
+ "This variable should have been rejected earlier.");
+
+ // Calculate strides once
+ std::vector<uint32_t> strides(dims.size());
+ strides[dims.size() - 1] = 1;
+ for (int i = static_cast<int>(dims.size()) - 2; i >= 0; --i) {
+ strides[i] = strides[i + 1] * dims[i + 1];
+ }
+
+ // Pre-calculate uint type id
+ uint32_t uint_type_id = context()->get_type_mgr()->GetUIntTypeId();
+ if (uint_type_id == 0) return false;
+
+ while (!worklist.empty()) {
+ Instruction* user = worklist.back();
+ worklist.pop_back();
+
+ if (user->opcode() == spv::Op::OpAccessChain ||
+ user->opcode() == spv::Op::OpInBoundsAccessChain) {
+ uint32_t num_indices = user->NumInOperands() - 1;
+ assert(num_indices >= dims.size());
+
+ InstructionBuilder builder(context(), user, IRContext::kAnalysisDefUse);
+
+ uint32_t linearized_idx_id = 0;
+ for (uint32_t i = 0; i < dims.size(); ++i) {
+ uint32_t idx_id = user->GetSingleWordInOperand(i + 1);
+
+ uint32_t term_id = idx_id;
+ if (strides[i] != 1) {
+ const analysis::Constant* stride_const =
+ context()->get_constant_mgr()->GetConstant(
+ context()->get_type_mgr()->GetType(uint_type_id),
+ {strides[i]});
+ Instruction* stride_inst =
+ context()->get_constant_mgr()->GetDefiningInstruction(
+ stride_const);
+
+ Instruction* mul_inst = builder.AddBinaryOp(
+ uint_type_id, spv::Op::OpIMul, idx_id, stride_inst->result_id());
+ if (mul_inst == nullptr) return false;
+ term_id = mul_inst->result_id();
+ }
+
+ if (linearized_idx_id == 0) {
+ linearized_idx_id = term_id;
+ } else {
+ Instruction* add_inst = builder.AddBinaryOp(
+ uint_type_id, spv::Op::OpIAdd, linearized_idx_id, term_id);
+ if (add_inst == nullptr) return false;
+ linearized_idx_id = add_inst->result_id();
+ }
+ }
+
+ // Create new AccessChain.
+ Instruction::OperandList new_operands;
+ new_operands.push_back(user->GetInOperand(0));
+ new_operands.push_back({SPV_OPERAND_TYPE_ID, {linearized_idx_id}});
+ for (uint32_t i = static_cast<uint32_t>(dims.size()); i < num_indices;
+ ++i) {
+ new_operands.push_back(user->GetInOperand(i + 1));
+ }
+ user->SetInOperands(std::move(new_operands));
+ context()->UpdateDefUse(user);
+ } else if (user->opcode() == spv::Op::OpCopyObject) {
+ // The type of the variable has changed so the result type of the
+ // OpCopyObject will change as well.
+
+ uint32_t operand_id = user->GetSingleWordInOperand(0);
+ Instruction* operand_inst =
+ context()->get_def_use_mgr()->GetDef(operand_id);
+ user->SetResultType(operand_inst->type_id());
+ context()->UpdateDefUse(user);
+
+ // Add users of this copy to worklist
+ context()->get_def_use_mgr()->ForEachUser(
+ user->result_id(),
+ [&worklist](Instruction* u) { worklist.push_back(u); });
+ }
+ }
+ return true;
+}
+
+bool LegalizeMultidimArrayPass::CheckUse(Instruction* inst,
+ uint32_t max_depth) {
+ if (inst->opcode() == spv::Op::OpAccessChain ||
+ inst->opcode() == spv::Op::OpInBoundsAccessChain) {
+ uint32_t num_indices = inst->NumInOperands() - 1;
+ return num_indices >= max_depth;
+ } else if (inst->opcode() == spv::Op::OpCopyObject) {
+ bool ok = true;
+ return !context()->get_def_use_mgr()->WhileEachUser(
+ inst->result_id(),
+ [&](Instruction* u) { return !CheckUse(u, max_depth); });
+ return ok;
+ } else if (inst->IsDecoration() || inst->opcode() == spv::Op::OpName ||
+ inst->opcode() == spv::Op::OpMemberName) {
+ // Metadata is fine.
+ return true;
+ }
+
+ // Direct use of array or partial array without AccessChain is not allowed.
+ return false;
+}
+
+bool LegalizeMultidimArrayPass::CanLegalize(Instruction* var) {
+ bool ok = true;
+ uint32_t ptr_type_id = var->type_id();
+ Instruction* ptr_type_inst =
+ context()->get_def_use_mgr()->GetDef(ptr_type_id);
+ uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
+ std::vector<uint32_t> dims;
+ uint32_t element_type_id = 0;
+ GetArrayDimensions(pointee_type_id, &dims, &element_type_id);
+
+ context()->get_def_use_mgr()->ForEachUser(
+ var->result_id(), [&](Instruction* u) {
+ if (!CheckUse(u, static_cast<uint32_t>(dims.size()))) ok = false;
+ });
+ return ok;
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/legalize_multidim_array_pass.h b/source/opt/legalize_multidim_array_pass.h
new file mode 100644
index 0000000..61c912d
--- /dev/null
+++ b/source/opt/legalize_multidim_array_pass.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2026 Google LLC
+//
+// 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 SOURCE_OPT_LEGALIZE_MULTIDIM_ARRAY_PASS_H_
+#define SOURCE_OPT_LEGALIZE_MULTIDIM_ARRAY_PASS_H_
+
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Pass to legalize multidimensional arrays of resources for Vulkan.
+// It transforms multidimensional arrays into single-dimensional ones.
+class LegalizeMultidimArrayPass : public Pass {
+ public:
+ const char* name() const override { return "legalize-multidim-array"; }
+ Status Process() override;
+
+ private:
+ // Returns true if |var| is a multidimensional array of resources.
+ bool IsMultidimArrayOfResources(Instruction* var);
+
+ // Flattens the multidimensional array type of |var| and returns the new type
+ // id.
+ uint32_t FlattenArrayType(Instruction* var);
+
+ // Rewrites all access chains that use |var|.
+ bool RewriteAccessChains(Instruction* var, uint32_t old_ptr_type_id);
+
+ // Returns true if all uses of |var| can be legalized.
+ bool CanLegalize(Instruction* var);
+
+ // Recursively checks if the uses of |inst| can be legalized.
+ bool CheckUse(Instruction* inst, uint32_t max_depth);
+
+ // Returns the dimensions of the array type |type_id|.
+ void GetArrayDimensions(uint32_t type_id, std::vector<uint32_t>* dims,
+ uint32_t* element_type_id);
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_LEGALIZE_MULTIDIM_ARRAY_PASS_H_
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 1380204..e8f64d5 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -171,7 +171,9 @@
.RegisterPass(CreateRemoveUnusedInterfaceVariablesPass())
.RegisterPass(CreateInterpolateFixupPass())
.RegisterPass(CreateInvocationInterlockPlacementPass())
- .RegisterPass(CreateOpExtInstWithForwardReferenceFixupPass());
+ .RegisterPass(CreateOpExtInstWithForwardReferenceFixupPass())
+ .RegisterPass(CreateCombineAccessChainsPass())
+ .RegisterPass(CreateLegalizeMultidimArrayPass());
}
Optimizer& Optimizer::RegisterLegalizationPasses() {
@@ -399,6 +401,8 @@
RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
} else if (pass_name == "loop-unswitch") {
RegisterPass(CreateLoopUnswitchPass());
+ } else if (pass_name == "legalize-multidim-array") {
+ RegisterPass(CreateLegalizeMultidimArrayPass());
} else if (pass_name == "scalar-replacement") {
if (pass_args.size() == 0) {
RegisterPass(CreateScalarReplacementPass(0));
@@ -961,6 +965,11 @@
MakeUnique<opt::LoopUnswitchPass>());
}
+Optimizer::PassToken CreateLegalizeMultidimArrayPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::LegalizeMultidimArrayPass>());
+}
+
Optimizer::PassToken CreateRedundancyEliminationPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::RedundancyEliminationPass>());
diff --git a/source/opt/passes.h b/source/opt/passes.h
index d005377..533fc21 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -52,6 +52,7 @@
#include "source/opt/interface_var_sroa.h"
#include "source/opt/interp_fixup_pass.h"
#include "source/opt/invocation_interlock_placement_pass.h"
+#include "source/opt/legalize_multidim_array_pass.h"
#include "source/opt/licm_pass.h"
#include "source/opt/local_access_chain_convert_pass.h"
#include "source/opt/local_redundancy_elimination.h"
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 9df0bb7..12096f5 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -70,6 +70,7 @@
ir_loader_test.cpp
iterator_test.cpp
line_debug_info_test.cpp
+ legalize_multidim_array_test.cpp
local_access_chain_convert_test.cpp
local_redundancy_elimination_test.cpp
local_single_block_elim.cpp
diff --git a/test/opt/legalize_multidim_array_test.cpp b/test/opt/legalize_multidim_array_test.cpp
new file mode 100644
index 0000000..c7bb6b2
--- /dev/null
+++ b/test/opt/legalize_multidim_array_test.cpp
@@ -0,0 +1,639 @@
+// Copyright (c) 2026 Google LLC
+//
+// 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 <string>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using LegalizeMultidimArrayTest = PassTest<::testing::Test>;
+
+TEST_F(LegalizeMultidimArrayTest, Flatten2DResourceArray) {
+ // HLSL:
+ // Texture2D g_Textures[2][3];
+ // SamplerState g_Sampler;
+ // float4 main(float2 uv : TEXCOORD) : SV_Target {
+ // return g_Textures[0][1].Sample(g_Sampler, uv);
+ // }
+ const std::string text = R"(
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %_arr_type_2d_image_uint_6 = OpTypeArray %type_2d_image %uint_6
+; CHECK: %_ptr_UniformConstant__arr_type_2d_image_uint_6 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_6
+; CHECK: %g_Textures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_6 UniformConstant
+; CHECK: [[mul:%\w+]] = OpIMul %uint %int_0 %uint_3
+; CHECK: [[idx:%\w+]] = OpIAdd %uint [[mul]] %int_1
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures [[idx]]
+; CHECK: OpLoad %type_2d_image [[ptr]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %in_var_TEXCOORD %out_var_SV_Target
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 600
+ OpName %type_2d_image "type.2d.image"
+ OpName %g_Textures "g_Textures"
+ OpName %type_sampler "type.sampler"
+ OpName %g_Sampler "g_Sampler"
+ OpName %main "main"
+ OpName %src_main "src.main"
+ OpDecorate %in_var_TEXCOORD Location 0
+ OpDecorate %out_var_SV_Target Location 0
+ OpDecorate %g_Textures DescriptorSet 0
+ OpDecorate %g_Textures Binding 0
+ OpDecorate %g_Sampler DescriptorSet 0
+ OpDecorate %g_Sampler Binding 1
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_arr__arr_type_2d_image_uint_3_uint_2 = OpTypeArray %_arr_type_2d_image_uint_3 %uint_2
+%_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 = OpTypePointer UniformConstant %_arr__arr_type_2d_image_uint_3_uint_2
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+ %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %void = OpTypeVoid
+ %24 = OpTypeFunction %void
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+ %31 = OpTypeFunction %v4float %_ptr_Function_v2float
+%_ptr_UniformConstant__arr_type_2d_image_uint_3 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_3
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+ %g_Textures = OpVariable %_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 UniformConstant
+ %g_Sampler = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%in_var_TEXCOORD = OpVariable %_ptr_Input_v2float Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+ %main = OpFunction %void None %24
+ %25 = OpLabel
+%param_var_uv = OpVariable %_ptr_Function_v2float Function
+ %28 = OpLoad %v2float %in_var_TEXCOORD
+ OpStore %param_var_uv %28
+ %29 = OpFunctionCall %v4float %src_main %param_var_uv
+ OpStore %out_var_SV_Target %29
+ OpReturn
+ OpFunctionEnd
+ %src_main = OpFunction %v4float None %31
+ %uv = OpFunctionParameter %_ptr_Function_v2float
+ %bb_entry = OpLabel
+ %37 = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures %int_0 %int_1
+ %38 = OpLoad %type_2d_image %37
+ %39 = OpLoad %type_sampler %g_Sampler
+ %40 = OpLoad %v2float %uv
+ %42 = OpSampledImage %type_sampled_image %38 %39
+ %43 = OpImageSampleImplicitLod %v4float %42 %40 None
+ OpReturnValue %43
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(
+)";
+
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text,
+ /*do_validation=*/true);
+}
+
+// Test that the pass fails when the access chain is split into multiple access
+// chains. We expect CombineAccessChains to be run before this pass to avoid
+// this.
+TEST_F(LegalizeMultidimArrayTest, IndirectUseViaPartialAccessChain) {
+ // HLSL source approximation:
+ // Texture2D g_Textures[2][3];
+ // ...
+ // Texture2D row[3] = g_Textures[0];
+ // return row[1].Sample(...);
+ //
+ // In SPIR-V, this often looks like:
+ // %ptr_row = OpAccessChain %_ptr_UniformConstant_arr_type_2d_image_uint_3
+ // %g_Textures %int_0 %ptr_tex = OpAccessChain
+ // %_ptr_UniformConstant_type_2d_image %ptr_row %int_1 OpLoad %type_2d_image
+ // %ptr_tex
+
+ const std::string text = R"(
+ ; CHECK: Unable to legalize multidimensional array
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %out_var_SV_Target
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 600
+ OpName %type_2d_image "type.2d.image"
+ OpName %g_Textures "g_Textures"
+ OpName %main "main"
+ OpDecorate %out_var_SV_Target Location 0
+ OpDecorate %g_Textures DescriptorSet 0
+ OpDecorate %g_Textures Binding 0
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_arr__arr_type_2d_image_uint_3_uint_2 = OpTypeArray %_arr_type_2d_image_uint_3 %uint_2
+%_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 = OpTypePointer UniformConstant %_arr__arr_type_2d_image_uint_3_uint_2
+%_ptr_UniformConstant__arr_type_2d_image_uint_3 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_3
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %void = OpTypeVoid
+ %24 = OpTypeFunction %void
+ %g_Textures = OpVariable %_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 UniformConstant
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+ %main = OpFunction %void None %24
+ %25 = OpLabel
+ %37 = OpAccessChain %_ptr_UniformConstant__arr_type_2d_image_uint_3 %g_Textures %int_0
+ %38 = OpAccessChain %_ptr_UniformConstant_type_2d_image %37 %int_1
+ %39 = OpLoad %type_2d_image %38
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndFail<LegalizeMultidimArrayPass>(text);
+}
+
+TEST_F(LegalizeMultidimArrayTest, Flatten3DResourceArray) {
+ // Texture2D g_Textures[2][3][4];
+ // Access: g_Textures[0][1][2]
+ const std::string text = R"(
+; CHECK: %uint_24 = OpConstant %uint 24
+; CHECK: %_arr_type_2d_image_uint_24 = OpTypeArray %type_2d_image %uint_24
+; CHECK: %_ptr_UniformConstant__arr_type_2d_image_uint_24 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_24
+; CHECK: %g_Textures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_24 UniformConstant
+; CHECK: [[mul1:%\w+]] = OpIMul %uint %int_0 %uint_12
+; CHECK: [[mul2:%\w+]] = OpIMul %uint %int_1 %uint_4
+; CHECK: [[add1:%\w+]] = OpIAdd %uint [[mul1]] [[mul2]]
+; CHECK: [[final_idx:%\w+]] = OpIAdd %uint [[add1]] %int_2
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures [[final_idx]]
+; CHECK: OpLoad %type_2d_image [[ptr]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %type_2d_image "type.2d.image"
+ OpName %g_Textures "g_Textures"
+ OpDecorate %g_Textures DescriptorSet 0
+ OpDecorate %g_Textures Binding 0
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %uint_4 = OpConstant %uint 4
+ %uint_12 = OpConstant %uint 12
+ %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_4 = OpTypeArray %type_2d_image %uint_4
+%_arr_arr_type_2d_image_uint_4_uint_3 = OpTypeArray %_arr_type_2d_image_uint_4 %uint_3
+%_arr_arr_arr_type_2d_image_uint_4_uint_3_uint_2 = OpTypeArray %_arr_arr_type_2d_image_uint_4_uint_3 %uint_2
+%_ptr_UniformConstant_arr_3d = OpTypePointer UniformConstant %_arr_arr_arr_type_2d_image_uint_4_uint_3_uint_2
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Textures = OpVariable %_ptr_UniformConstant_arr_3d UniformConstant
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %ptr = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures %int_0 %int_1 %int_2
+ %val = OpLoad %type_2d_image %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, FlattenSamplerArray) {
+ // SamplerState g_Samplers[2][2];
+ const std::string text = R"(
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %_arr_type_sampler_uint_4 = OpTypeArray %type_sampler %uint_4
+; CHECK: %_ptr_UniformConstant__arr_type_sampler_uint_4 = OpTypePointer UniformConstant %_arr_type_sampler_uint_4
+; CHECK: %g_Samplers = OpVariable %_ptr_UniformConstant__arr_type_sampler_uint_4 UniformConstant
+; CHECK: [[mul:%\w+]] = OpIMul %uint %int_0 %uint_2
+; CHECK: [[idx:%\w+]] = OpIAdd %uint [[mul]] %int_1
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %g_Samplers [[idx]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %type_sampler "type.sampler"
+ OpName %g_Samplers "g_Samplers"
+ OpDecorate %g_Samplers DescriptorSet 0
+ OpDecorate %g_Samplers Binding 0
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+%type_sampler = OpTypeSampler
+%_arr_type_sampler_uint_2 = OpTypeArray %type_sampler %uint_2
+%_arr_arr_type_sampler_uint_2_uint_2 = OpTypeArray %_arr_type_sampler_uint_2 %uint_2
+%_ptr_UniformConstant_arr_2d = OpTypePointer UniformConstant %_arr_arr_type_sampler_uint_2_uint_2
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Samplers = OpVariable %_ptr_UniformConstant_arr_2d UniformConstant
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %ptr = OpAccessChain %_ptr_UniformConstant_type_sampler %g_Samplers %int_0 %int_1
+ %val = OpLoad %type_sampler %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, FlattenStorageBufferArray) {
+ // struct S { float f; };
+ // S buffers[2][3];
+ // buffers[0][1].f
+ const std::string text = R"(
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %_arr_S_uint_6 = OpTypeArray %S %uint_6
+; CHECK: %_ptr_StorageBuffer__arr_S_uint_6 = OpTypePointer StorageBuffer %_arr_S_uint_6
+; CHECK: %g_Buffers = OpVariable %_ptr_StorageBuffer__arr_S_uint_6 StorageBuffer
+; CHECK: [[mul:%\w+]] = OpIMul %uint %int_0 %uint_3
+; CHECK: [[idx:%\w+]] = OpIAdd %uint [[mul]] %int_1
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_StorageBuffer_float %g_Buffers [[idx]] %int_0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %S "S"
+ OpName %g_Buffers "g_Buffers"
+ OpDecorate %g_Buffers DescriptorSet 0
+ OpDecorate %g_Buffers Binding 0
+ OpMemberDecorate %S 0 Offset 0
+ OpDecorate %S Block
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+ %S = OpTypeStruct %float
+%_arr_S_uint_3 = OpTypeArray %S %uint_3
+%_arr__arr_S_uint_3_uint_2 = OpTypeArray %_arr_S_uint_3 %uint_2
+%_ptr_StorageBuffer_arr_2d = OpTypePointer StorageBuffer %_arr__arr_S_uint_3_uint_2
+%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Buffers = OpVariable %_ptr_StorageBuffer_arr_2d StorageBuffer
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %ptr = OpAccessChain %_ptr_StorageBuffer_float %g_Buffers %int_0 %int_1 %int_0
+ %val = OpLoad %float %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, FlattenUniformArray) {
+ // Uniform buffer array: MyBlock buffers[2][3];
+ // Access: buffers[0][1].member
+ const std::string text = R"(
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %_arr_MyBlock_uint_6 = OpTypeArray %MyBlock %uint_6
+; CHECK: %_ptr_Uniform__arr_MyBlock_uint_6 = OpTypePointer Uniform %_arr_MyBlock_uint_6
+; CHECK: %g_Uniforms = OpVariable %_ptr_Uniform__arr_MyBlock_uint_6 Uniform
+; CHECK: [[mul:%\w+]] = OpIMul %uint %int_0 %uint_3
+; CHECK: [[idx:%\w+]] = OpIAdd %uint [[mul]] %int_1
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Uniform_float %g_Uniforms [[idx]] %int_0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %MyBlock "MyBlock"
+ OpName %g_Uniforms "g_Uniforms"
+ OpDecorate %g_Uniforms DescriptorSet 0
+ OpDecorate %g_Uniforms Binding 0
+ OpMemberDecorate %MyBlock 0 Offset 0
+ OpDecorate %MyBlock Block
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+ %MyBlock = OpTypeStruct %float
+%_arr_MyBlock_uint_3 = OpTypeArray %MyBlock %uint_3
+%_arr__arr_MyBlock_uint_3_uint_2 = OpTypeArray %_arr_MyBlock_uint_3 %uint_2
+%_ptr_Uniform__arr__arr_MyBlock_uint_3_uint_2 = OpTypePointer Uniform %_arr__arr_MyBlock_uint_3_uint_2
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Uniforms = OpVariable %_ptr_Uniform__arr__arr_MyBlock_uint_3_uint_2 Uniform
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %ptr = OpAccessChain %_ptr_Uniform_float %g_Uniforms %int_0 %int_1 %int_0
+ %val = OpLoad %float %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, AccessChainThroughCopyObject) {
+ // Texture2D g_Textures[2][3];
+ // %copy = OpCopyObject %ptr_type %g_Textures
+ // %ptr = OpAccessChain %... %copy %int_0 %int_1
+ const std::string text = R"(
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %_arr_type_2d_image_uint_6 = OpTypeArray %type_2d_image %uint_6
+; CHECK: %_ptr_UniformConstant__arr_type_2d_image_uint_6 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_6
+; CHECK: %g_Textures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_6 UniformConstant
+; CHECK: [[copy:%\w+]] = OpCopyObject %_ptr_UniformConstant__arr_type_2d_image_uint_6 %g_Textures
+; CHECK: [[mul:%\w+]] = OpIMul %uint %int_0 %uint_3
+; CHECK: [[idx:%\w+]] = OpIAdd %uint [[mul]] %int_1
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image [[copy]] [[idx]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %type_2d_image "type.2d.image"
+ OpName %g_Textures "g_Textures"
+ OpDecorate %g_Textures DescriptorSet 0
+ OpDecorate %g_Textures Binding 0
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_arr__arr_type_2d_image_uint_3_uint_2 = OpTypeArray %_arr_type_2d_image_uint_3 %uint_2
+%_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 = OpTypePointer UniformConstant %_arr__arr_type_2d_image_uint_3_uint_2
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Textures = OpVariable %_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 UniformConstant
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %copy = OpCopyObject %_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 %g_Textures
+ %ptr = OpAccessChain %_ptr_UniformConstant_type_2d_image %copy %int_0 %int_1
+ %val = OpLoad %type_2d_image %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, DynamicIndices) {
+ // Access with non-constant indices.
+ // g_Textures[var_i][var_j]
+ const std::string text = R"(
+; CHECK: [[idx1:%\w+]] = OpLoad %int %idx_var_1
+; CHECK: [[idx2:%\w+]] = OpLoad %int %idx_var_2
+; CHECK: [[mul:%\w+]] = OpIMul %uint [[idx1]] %uint_3
+; CHECK: [[add:%\w+]] = OpIAdd %uint [[mul]] [[idx2]]
+; CHECK: OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures [[add]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %type_2d_image "type.2d.image"
+ OpName %g_Textures "g_Textures"
+ OpName %idx_var_1 "idx_var_1"
+ OpName %idx_var_2 "idx_var_2"
+ OpDecorate %g_Textures DescriptorSet 0
+ OpDecorate %g_Textures Binding 0
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_arr__arr_type_2d_image_uint_3_uint_2 = OpTypeArray %_arr_type_2d_image_uint_3 %uint_2
+%_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 = OpTypePointer UniformConstant %_arr__arr_type_2d_image_uint_3_uint_2
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%_ptr_Function_int = OpTypePointer Function %int
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Textures = OpVariable %_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 UniformConstant
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %idx_var_1 = OpVariable %_ptr_Function_int Function
+ %idx_var_2 = OpVariable %_ptr_Function_int Function
+ %i = OpLoad %int %idx_var_1
+ %j = OpLoad %int %idx_var_2
+ %ptr = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures %i %j
+ %val = OpLoad %type_2d_image %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, IgnoreFunctionScopeArray) {
+ // Function scope array [2][3] should NOT be legalized.
+ const std::string text = R"(
+; CHECK: %_arr__arr_float_uint_3_uint_2 = OpTypeArray %_arr_float_uint_3 %uint_2
+; CHECK: %_ptr_Function__arr__arr_float_uint_3_uint_2 = OpTypePointer Function %_arr__arr_float_uint_3_uint_2
+; CHECK: %var = OpVariable %_ptr_Function__arr__arr_float_uint_3_uint_2 Function
+; CHECK: OpAccessChain %_ptr_Function_float %var %int_0 %int_1
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %var "var"
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%_arr_float_uint_3 = OpTypeArray %float %uint_3
+%_arr__arr_float_uint_3_uint_2 = OpTypeArray %_arr_float_uint_3 %uint_2
+%_ptr_Function__arr__arr_float_uint_3_uint_2 = OpTypePointer Function %_arr__arr_float_uint_3_uint_2
+%_ptr_Function_float = OpTypePointer Function %float
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %var = OpVariable %_ptr_Function__arr__arr_float_uint_3_uint_2 Function
+ %ptr = OpAccessChain %_ptr_Function_float %var %int_0 %int_1
+ %val = OpLoad %float %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, IgnoreWorkgroupScopeArray) {
+ // Workgroup scope array [2][3] should NOT be legalized.
+ const std::string text = R"(
+; CHECK: %_arr__arr_float_uint_3_uint_2 = OpTypeArray %_arr_float_uint_3 %uint_2
+; CHECK: %_ptr_Workgroup__arr__arr_float_uint_3_uint_2 = OpTypePointer Workgroup %_arr__arr_float_uint_3_uint_2
+; CHECK: %var = OpVariable %_ptr_Workgroup__arr__arr_float_uint_3_uint_2 Workgroup
+; CHECK: OpAccessChain %_ptr_Workgroup_float %var %int_0 %int_1
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %var "var"
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%_arr_float_uint_3 = OpTypeArray %float %uint_3
+%_arr__arr_float_uint_3_uint_2 = OpTypeArray %_arr_float_uint_3 %uint_2
+%_ptr_Workgroup__arr__arr_float_uint_3_uint_2 = OpTypePointer Workgroup %_arr__arr_float_uint_3_uint_2
+%_ptr_Workgroup_float = OpTypePointer Workgroup %float
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %var = OpVariable %_ptr_Workgroup__arr__arr_float_uint_3_uint_2 Workgroup
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %ptr = OpAccessChain %_ptr_Workgroup_float %var %int_0 %int_1
+ %val = OpLoad %float %ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, MultipleAccessChains) {
+ // Access g_Textures[0][1] and g_Textures[1][2] in the same function.
+ const std::string text = R"(
+; CHECK: [[mul1:%\w+]] = OpIMul %uint %int_0 %uint_3
+; CHECK: [[idx1:%\w+]] = OpIAdd %uint [[mul1]] %int_1
+; CHECK: [[ptr1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures [[idx1]]
+; CHECK: OpLoad %type_2d_image [[ptr1]]
+; CHECK: [[mul2:%\w+]] = OpIMul %uint %int_1 %uint_3
+; CHECK: [[idx2:%\w+]] = OpIAdd %uint [[mul2]] %int_2
+; CHECK: [[ptr2:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures [[idx2]]
+; CHECK: OpLoad %type_2d_image [[ptr2]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %type_2d_image "type.2d.image"
+ OpName %g_Textures "g_Textures"
+ OpDecorate %g_Textures DescriptorSet 0
+ OpDecorate %g_Textures Binding 0
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_arr__arr_type_2d_image_uint_3_uint_2 = OpTypeArray %_arr_type_2d_image_uint_3 %uint_2
+%_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 = OpTypePointer UniformConstant %_arr__arr_type_2d_image_uint_3_uint_2
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Textures = OpVariable %_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 UniformConstant
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %ptr1 = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures %int_0 %int_1
+ %val1 = OpLoad %type_2d_image %ptr1
+ %ptr2 = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures %int_1 %int_2
+ %val2 = OpLoad %type_2d_image %ptr2
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+TEST_F(LegalizeMultidimArrayTest, MultipleResources) {
+ // Two different resource arrays:
+ // Texture2D g_Textures[2][3];
+ // SamplerState g_Samplers[2][2];
+ const std::string text = R"(
+; CHECK: %g_Textures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_6 UniformConstant
+; CHECK: %g_Samplers = OpVariable %_ptr_UniformConstant__arr_type_sampler_uint_4 UniformConstant
+; CHECK: [[mul1:%\w+]] = OpIMul %uint %int_0 %uint_3
+; CHECK: [[idx1:%\w+]] = OpIAdd %uint [[mul1]] %int_1
+; CHECK: [[ptr1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures [[idx1]]
+; CHECK: OpLoad %type_2d_image [[ptr1]]
+; CHECK: [[mul2:%\w+]] = OpIMul %uint %int_1 %uint_2
+; CHECK: [[idx2:%\w+]] = OpIAdd %uint [[mul2]] %int_1
+; CHECK: [[ptr2:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %g_Samplers [[idx2]]
+; CHECK: OpLoad %type_sampler [[ptr2]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %type_2d_image "type.2d.image"
+ OpName %g_Textures "g_Textures"
+ OpName %type_sampler "type.sampler"
+ OpName %g_Samplers "g_Samplers"
+ OpDecorate %g_Textures DescriptorSet 0
+ OpDecorate %g_Textures Binding 0
+ OpDecorate %g_Samplers DescriptorSet 0
+ OpDecorate %g_Samplers Binding 1
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_arr__arr_type_2d_image_uint_3_uint_2 = OpTypeArray %_arr_type_2d_image_uint_3 %uint_2
+%_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 = OpTypePointer UniformConstant %_arr__arr_type_2d_image_uint_3_uint_2
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%type_sampler = OpTypeSampler
+%_arr_type_sampler_uint_2 = OpTypeArray %type_sampler %uint_2
+%_arr_arr_type_sampler_uint_2_uint_2 = OpTypeArray %_arr_type_sampler_uint_2 %uint_2
+%_ptr_UniformConstant_arr_2d_sampler = OpTypePointer UniformConstant %_arr_arr_type_sampler_uint_2_uint_2
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+ %void = OpTypeVoid
+ %main_func = OpTypeFunction %void
+ %g_Textures = OpVariable %_ptr_UniformConstant__arr__arr_type_2d_image_uint_3_uint_2 UniformConstant
+ %g_Samplers = OpVariable %_ptr_UniformConstant_arr_2d_sampler UniformConstant
+ %main = OpFunction %void None %main_func
+ %label = OpLabel
+ %ptr1 = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_Textures %int_0 %int_1
+ %val1 = OpLoad %type_2d_image %ptr1
+ %ptr2 = OpAccessChain %_ptr_UniformConstant_type_sampler %g_Samplers %int_1 %int_1
+ %val2 = OpLoad %type_sampler %ptr2
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LegalizeMultidimArrayPass>(text, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
\ No newline at end of file
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index d67d364..a578cc4 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -220,14 +220,14 @@
// messages.
template <typename PassT, typename... Args>
void SinglePassRunAndFail(const std::string& original, Args&&... args) {
- context_ = BuildModule(env_, consumer_, original, assemble_options_);
- EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
- << original << std::endl;
std::ostringstream errs;
auto error_consumer = [&errs](spv_message_level_t, const char*,
const spv_position_t&, const char* message) {
errs << message << std::endl;
};
+ context_ = BuildModule(env_, error_consumer, original, assemble_options_);
+ EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
+ << original << std::endl;
auto pass = MakeUnique<PassT>(std::forward<Args>(args)...);
pass->SetMessageConsumer(error_consumer);
const auto status = pass->Run(context());
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 1b0b372..19852e6 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -279,6 +279,10 @@
option --relax-logical-pointer to the validator.)",
GetLegalizationPasses().c_str());
printf(R"(
+ --legalize-multidim-array
+ Replace multidimensional arrays of resources with single-dimensional
+ arrays. Run combine-access-chains before this pass.)");
+ printf(R"(
--local-redundancy-elimination
Looks for instructions in the same basic block that compute the
same value, and deletes the redundant ones.)");