/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 |