Coverage Report

Created: 2026-08-14 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/combine_access_chains.cpp
Line
Count
Source
1
// Copyright (c) 2018 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "source/opt/combine_access_chains.h"
16
17
#include <utility>
18
19
#include "source/opt/constants.h"
20
#include "source/opt/ir_builder.h"
21
#include "source/opt/ir_context.h"
22
23
namespace spvtools {
24
namespace opt {
25
26
11.3k
Pass::Status CombineAccessChains::Process() {
27
11.3k
  bool modified = false;
28
29
11.3k
  for (auto& function : *get_module()) {
30
10.8k
    auto status = ProcessFunction(function);
31
10.8k
    if (status == Status::Failure) return Status::Failure;
32
10.8k
    if (status == Status::SuccessWithChange) modified = true;
33
10.8k
  }
34
35
11.3k
  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
36
11.3k
}
37
38
10.8k
Pass::Status CombineAccessChains::ProcessFunction(Function& function) {
39
10.8k
  if (function.IsDeclaration()) {
40
0
    return Status::SuccessWithoutChange;
41
0
  }
42
43
10.8k
  bool modified = false;
44
10.8k
  bool failure = false;
45
46
10.8k
  cfg()->ForEachBlockInReversePostOrder(
47
339k
      function.entry().get(), [&modified, &failure, this](BasicBlock* block) {
48
339k
        if (failure) return;
49
1.75M
        block->ForEachInst([&modified, &failure, this](Instruction* inst) {
50
1.75M
          if (failure) return;
51
1.75M
          switch (inst->opcode()) {
52
91.2k
            case spv::Op::OpAccessChain:
53
91.5k
            case spv::Op::OpInBoundsAccessChain:
54
91.5k
            case spv::Op::OpPtrAccessChain:
55
91.5k
            case spv::Op::OpInBoundsPtrAccessChain: {
56
91.5k
              auto status = CombineAccessChain(inst);
57
91.5k
              if (status == Status::Failure) {
58
0
                failure = true;
59
91.5k
              } else if (status == Status::SuccessWithChange) {
60
3
                modified = true;
61
3
              }
62
91.5k
              break;
63
91.5k
            }
64
1.65M
            default:
65
1.65M
              break;
66
1.75M
          }
67
1.75M
        });
68
339k
      });
69
70
10.8k
  if (failure) return Status::Failure;
71
10.8k
  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
72
10.8k
}
73
74
uint32_t CombineAccessChains::GetConstantValue(
75
0
    const analysis::Constant* constant_inst) {
76
0
  if (constant_inst->type()->AsInteger()->width() <= 32) {
77
0
    if (constant_inst->type()->AsInteger()->IsSigned()) {
78
0
      return static_cast<uint32_t>(constant_inst->GetS32());
79
0
    } else {
80
0
      return constant_inst->GetU32();
81
0
    }
82
0
  } else {
83
0
    assert(false);
84
0
    return 0u;
85
0
  }
86
0
}
87
88
3
uint32_t CombineAccessChains::GetArrayStride(const Instruction* inst) {
89
3
  uint32_t array_stride = 0;
90
3
  context()->get_decoration_mgr()->WhileEachDecoration(
91
3
      inst->type_id(), uint32_t(spv::Decoration::ArrayStride),
92
3
      [&array_stride](const Instruction& decoration) {
93
0
        assert(decoration.opcode() != spv::Op::OpDecorateId);
94
0
        if (decoration.opcode() == spv::Op::OpDecorate) {
95
0
          array_stride = decoration.GetSingleWordInOperand(1);
96
0
        } else {
97
0
          array_stride = decoration.GetSingleWordInOperand(2);
98
0
        }
99
0
        return false;
100
0
      });
101
3
  return array_stride;
102
3
}
103
104
0
const analysis::Type* CombineAccessChains::GetIndexedType(Instruction* inst) {
105
0
  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
106
0
  analysis::TypeManager* type_mgr = context()->get_type_mgr();
107
108
0
  Instruction* base_ptr = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
109
0
  const analysis::Type* type = type_mgr->GetType(base_ptr->type_id());
110
0
  assert(type->AsPointer());
111
0
  type = type->AsPointer()->pointee_type();
112
0
  std::vector<uint32_t> element_indices;
113
0
  uint32_t starting_index = 1;
114
0
  if (IsPtrAccessChain(inst->opcode())) {
115
    // Skip the first index of OpPtrAccessChain as it does not affect type
116
    // resolution.
117
0
    starting_index = 2;
118
0
  }
119
0
  for (uint32_t i = starting_index; i < inst->NumInOperands(); ++i) {
120
0
    Instruction* index_inst =
121
0
        def_use_mgr->GetDef(inst->GetSingleWordInOperand(i));
122
0
    const analysis::Constant* index_constant =
123
0
        context()->get_constant_mgr()->GetConstantFromInst(index_inst);
124
0
    if (index_constant) {
125
0
      uint32_t index_value = GetConstantValue(index_constant);
126
0
      element_indices.push_back(index_value);
127
0
    } else {
128
      // This index must not matter to resolve the type in valid SPIR-V.
129
0
      element_indices.push_back(0);
130
0
    }
131
0
  }
132
0
  type = type_mgr->GetMemberType(type, element_indices);
133
0
  return type;
134
0
}
135
136
Pass::Status CombineAccessChains::CombineIndices(
137
    Instruction* ptr_input, Instruction* inst,
138
0
    std::vector<Operand>* new_operands) {
139
0
  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
140
0
  analysis::ConstantManager* constant_mgr = context()->get_constant_mgr();
141
142
0
  Instruction* last_index_inst = def_use_mgr->GetDef(
143
0
      ptr_input->GetSingleWordInOperand(ptr_input->NumInOperands() - 1));
144
0
  const analysis::Constant* last_index_constant =
145
0
      constant_mgr->GetConstantFromInst(last_index_inst);
146
147
0
  Instruction* element_inst =
148
0
      def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
149
0
  const analysis::Constant* element_constant =
150
0
      constant_mgr->GetConstantFromInst(element_inst);
151
152
  // Combine the last index of the AccessChain (|ptr_inst|) with the element
153
  // operand of the PtrAccessChain (|inst|).
154
0
  const bool combining_element_operands =
155
0
      IsPtrAccessChain(inst->opcode()) &&
156
0
      IsPtrAccessChain(ptr_input->opcode()) && ptr_input->NumInOperands() == 2;
157
0
  uint32_t new_value_id = 0;
158
0
  const analysis::Type* type = GetIndexedType(ptr_input);
159
0
  if (last_index_constant && element_constant) {
160
    // Combine the constants.
161
0
    uint32_t new_value = GetConstantValue(last_index_constant) +
162
0
                         GetConstantValue(element_constant);
163
0
    const analysis::Constant* new_value_constant =
164
0
        constant_mgr->GetConstant(last_index_constant->type(), {new_value});
165
0
    if (!new_value_constant) return Status::Failure;
166
0
    Instruction* new_value_inst =
167
0
        constant_mgr->GetDefiningInstruction(new_value_constant);
168
0
    if (!new_value_inst) return Status::Failure;
169
0
    new_value_id = new_value_inst->result_id();
170
0
  } else if (!type->AsStruct() || combining_element_operands) {
171
    // Generate an addition of the two indices.
172
0
    InstructionBuilder builder(
173
0
        context(), inst,
174
0
        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
175
0
    Instruction* addition = builder.AddIAdd(last_index_inst->type_id(),
176
0
                                            last_index_inst->result_id(),
177
0
                                            element_inst->result_id());
178
0
    if (!addition) return Status::Failure;
179
0
    new_value_id = addition->result_id();
180
0
  } else {
181
    // Indexing into structs must be constant, so bail out here.
182
0
    return Status::SuccessWithoutChange;
183
0
  }
184
0
  new_operands->push_back({SPV_OPERAND_TYPE_ID, {new_value_id}});
185
0
  return Status::SuccessWithChange;
186
0
}
187
188
Pass::Status CombineAccessChains::CreateNewInputOperands(
189
    Instruction* ptr_input, Instruction* inst,
190
0
    std::vector<Operand>* new_operands) {
191
  // Start by copying all the input operands of the feeder access chain.
192
0
  for (uint32_t i = 0; i != ptr_input->NumInOperands() - 1; ++i) {
193
0
    new_operands->push_back(ptr_input->GetInOperand(i));
194
0
  }
195
196
  // Deal with the last index of the feeder access chain.
197
0
  if (IsPtrAccessChain(inst->opcode())) {
198
    // The last index of the feeder should be combined with the element operand
199
    // of |inst|.
200
0
    auto status = CombineIndices(ptr_input, inst, new_operands);
201
0
    if (status != Status::SuccessWithChange) return status;
202
0
  } else {
203
    // The indices aren't being combined so now add the last index operand of
204
    // |ptr_input|.
205
0
    new_operands->push_back(
206
0
        ptr_input->GetInOperand(ptr_input->NumInOperands() - 1));
207
0
  }
208
209
  // Copy the remaining index operands.
210
0
  uint32_t starting_index = IsPtrAccessChain(inst->opcode()) ? 2 : 1;
211
0
  for (uint32_t i = starting_index; i < inst->NumInOperands(); ++i) {
212
0
    new_operands->push_back(inst->GetInOperand(i));
213
0
  }
214
215
0
  return Status::SuccessWithChange;
216
0
}
217
218
91.5k
Pass::Status CombineAccessChains::CombineAccessChain(Instruction* inst) {
219
91.5k
  assert((inst->opcode() == spv::Op::OpPtrAccessChain ||
220
91.5k
          inst->opcode() == spv::Op::OpAccessChain ||
221
91.5k
          inst->opcode() == spv::Op::OpInBoundsAccessChain ||
222
91.5k
          inst->opcode() == spv::Op::OpInBoundsPtrAccessChain) &&
223
91.5k
         "Wrong opcode. Expected an access chain.");
224
225
91.5k
  Instruction* ptr_input =
226
91.5k
      context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
227
91.5k
  if (ptr_input->opcode() != spv::Op::OpAccessChain &&
228
91.5k
      ptr_input->opcode() != spv::Op::OpInBoundsAccessChain &&
229
91.5k
      ptr_input->opcode() != spv::Op::OpPtrAccessChain &&
230
91.5k
      ptr_input->opcode() != spv::Op::OpInBoundsPtrAccessChain) {
231
91.5k
    return Status::SuccessWithoutChange;
232
91.5k
  }
233
234
3
  if (Has64BitIndices(inst) || Has64BitIndices(ptr_input))
235
0
    return Status::SuccessWithoutChange;
236
237
  // Handles the following cases:
238
  // 1. |ptr_input| is an index-less access chain. Replace the pointer
239
  //    in |inst| with |ptr_input|'s pointer.
240
  // 2. |inst| is a index-less access chain. Change |inst| to an
241
  //    OpCopyObject.
242
  // 3. |inst| is not a pointer access chain.
243
  //    |inst|'s indices are appended to |ptr_input|'s indices.
244
  // 4. |ptr_input| is not pointer access chain.
245
  //    |inst| is a pointer access chain.
246
  //    |inst|'s element operand is combined with the last index in
247
  //    |ptr_input| to form a new operand.
248
  // 5. |ptr_input| is a pointer access chain.
249
  //    Like the above scenario, |inst|'s element operand is combined
250
  //    with |ptr_input|'s last index. This results is either a
251
  //    combined element operand or combined regular index.
252
253
  // TODO(alan-baker): Support this properly. Requires analyzing the
254
  // size/alignment of the type and converting the stride into an element
255
  // index.
256
3
  uint32_t array_stride = GetArrayStride(ptr_input);
257
3
  if (array_stride != 0) return Status::SuccessWithoutChange;
258
259
3
  if (ptr_input->NumInOperands() == 1) {
260
    // The input is effectively a no-op.
261
2
    inst->SetInOperand(0, {ptr_input->GetSingleWordInOperand(0)});
262
2
    context()->AnalyzeUses(inst);
263
2
  } else if (inst->NumInOperands() == 1) {
264
    // |inst| is a no-op, change it to a copy. Instruction simplification will
265
    // clean it up.
266
1
    inst->SetOpcode(spv::Op::OpCopyObject);
267
1
  } else {
268
0
    std::vector<Operand> new_operands;
269
0
    auto status = CreateNewInputOperands(ptr_input, inst, &new_operands);
270
0
    if (status != Status::SuccessWithChange) return status;
271
272
    // Update the instruction.
273
0
    inst->SetOpcode(UpdateOpcode(inst->opcode(), ptr_input->opcode()));
274
0
    inst->SetInOperands(std::move(new_operands));
275
0
    context()->AnalyzeUses(inst);
276
0
  }
277
3
  return Status::SuccessWithChange;
278
3
}
279
280
spv::Op CombineAccessChains::UpdateOpcode(spv::Op base_opcode,
281
0
                                          spv::Op input_opcode) {
282
0
  auto IsInBounds = [](spv::Op opcode) {
283
0
    return opcode == spv::Op::OpInBoundsPtrAccessChain ||
284
0
           opcode == spv::Op::OpInBoundsAccessChain;
285
0
  };
286
287
0
  if (input_opcode == spv::Op::OpInBoundsPtrAccessChain) {
288
0
    if (!IsInBounds(base_opcode)) return spv::Op::OpPtrAccessChain;
289
0
  } else if (input_opcode == spv::Op::OpInBoundsAccessChain) {
290
0
    if (!IsInBounds(base_opcode)) return spv::Op::OpAccessChain;
291
0
  }
292
293
0
  return input_opcode;
294
0
}
295
296
0
bool CombineAccessChains::IsPtrAccessChain(spv::Op opcode) {
297
0
  return opcode == spv::Op::OpPtrAccessChain ||
298
0
         opcode == spv::Op::OpInBoundsPtrAccessChain;
299
0
}
300
301
6
bool CombineAccessChains::Has64BitIndices(Instruction* inst) {
302
7
  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
303
1
    Instruction* index_inst =
304
1
        context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(i));
305
1
    const analysis::Type* index_type =
306
1
        context()->get_type_mgr()->GetType(index_inst->type_id());
307
1
    if (!index_type->AsInteger() || index_type->AsInteger()->width() != 32)
308
0
      return true;
309
1
  }
310
6
  return false;
311
6
}
312
313
}  // namespace opt
314
}  // namespace spvtools