/src/spirv-tools/source/opt/fix_storage_class.cpp
Line | Count | Source |
1 | | // Copyright (c) 2019 Google LLC |
2 | | // Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights |
3 | | // reserved. |
4 | | // |
5 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
6 | | // you may not use this file except in compliance with the License. |
7 | | // You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, software |
12 | | // distributed under the License is distributed on an "AS IS" BASIS, |
13 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 | | // See the License for the specific language governing permissions and |
15 | | // limitations under the License. |
16 | | |
17 | | #include "fix_storage_class.h" |
18 | | |
19 | | #include <set> |
20 | | |
21 | | #include "source/opt/instruction.h" |
22 | | #include "source/opt/ir_context.h" |
23 | | |
24 | | namespace spvtools { |
25 | | namespace opt { |
26 | | |
27 | 1.18k | Pass::Status FixStorageClass::Process() { |
28 | 1.18k | bool modified = false; |
29 | | |
30 | 3.98M | get_module()->ForEachInst([this, &modified](Instruction* inst) { |
31 | 3.98M | if (inst->opcode() == spv::Op::OpVariable) { |
32 | 65.2k | std::set<uint32_t> seen; |
33 | 65.2k | std::vector<std::pair<Instruction*, uint32_t>> uses; |
34 | 65.2k | get_def_use_mgr()->ForEachUse(inst, |
35 | 298k | [&uses](Instruction* use, uint32_t op_idx) { |
36 | 298k | uses.push_back({use, op_idx}); |
37 | 298k | }); |
38 | | |
39 | 298k | for (auto& use : uses) { |
40 | 298k | modified |= PropagateStorageClass( |
41 | 298k | use.first, |
42 | 298k | static_cast<spv::StorageClass>(inst->GetSingleWordInOperand(0)), |
43 | 298k | &seen); |
44 | 298k | assert(seen.empty() && "Seen was not properly reset."); |
45 | 298k | modified |= |
46 | 298k | PropagateType(use.first, inst->type_id(), use.second, &seen); |
47 | 298k | assert(seen.empty() && "Seen was not properly reset."); |
48 | 298k | } |
49 | 65.2k | } |
50 | 3.98M | }); |
51 | 1.18k | if (context()->id_overflow()) { |
52 | 0 | return Status::Failure; |
53 | 0 | } |
54 | 1.18k | return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; |
55 | 1.18k | } |
56 | | |
57 | | bool FixStorageClass::PropagateStorageClass(Instruction* inst, |
58 | | spv::StorageClass storage_class, |
59 | 371k | std::set<uint32_t>* seen) { |
60 | 371k | if (!IsPointerResultType(inst)) { |
61 | 299k | return false; |
62 | 299k | } |
63 | | |
64 | 72.1k | if (inst->opcode() == spv::Op::OpPhi) { |
65 | 0 | if (!seen->insert(inst->result_id()).second) { |
66 | 0 | return false; |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | 72.1k | bool modified = false; |
71 | 72.1k | if (IsPointerToStorageClass(inst, storage_class)) { |
72 | 72.1k | std::vector<Instruction*> uses; |
73 | 72.1k | get_def_use_mgr()->ForEachUser( |
74 | 73.2k | inst, [&uses](Instruction* use) { uses.push_back(use); }); |
75 | 73.2k | for (Instruction* use : uses) { |
76 | 73.2k | modified |= PropagateStorageClass(use, storage_class, seen); |
77 | 73.2k | } |
78 | 72.1k | } else { |
79 | 0 | switch (inst->opcode()) { |
80 | 0 | case spv::Op::OpAccessChain: |
81 | 0 | case spv::Op::OpPtrAccessChain: |
82 | 0 | case spv::Op::OpInBoundsAccessChain: |
83 | 0 | case spv::Op::OpCopyObject: |
84 | 0 | case spv::Op::OpPhi: |
85 | 0 | case spv::Op::OpSelect: |
86 | 0 | FixInstructionStorageClass(inst, storage_class, seen); |
87 | 0 | modified = true; |
88 | 0 | break; |
89 | 0 | case spv::Op::OpFunctionCall: |
90 | | // We cannot be sure of the actual connection between the storage class |
91 | | // of the parameter and the storage class of the result, so we should |
92 | | // not do anything. If the result type needs to be fixed, the function |
93 | | // call should be inlined. |
94 | 0 | break; |
95 | 0 | case spv::Op::OpImageTexelPointer: |
96 | 0 | case spv::Op::OpLoad: |
97 | 0 | case spv::Op::OpStore: |
98 | 0 | case spv::Op::OpCopyMemory: |
99 | 0 | case spv::Op::OpCopyMemorySized: |
100 | 0 | case spv::Op::OpVariable: |
101 | 0 | case spv::Op::OpBitcast: |
102 | 0 | case spv::Op::OpAllocateNodePayloadsAMDX: |
103 | | // Nothing to change for these opcode. The result type is the same |
104 | | // regardless of the storage class of the operand. |
105 | 0 | break; |
106 | 0 | default: |
107 | 0 | assert(false && |
108 | 0 | "Not expecting instruction to have a pointer result type."); |
109 | 0 | break; |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | 72.1k | if (inst->opcode() == spv::Op::OpPhi) { |
114 | 0 | seen->erase(inst->result_id()); |
115 | 0 | } |
116 | 72.1k | return modified; |
117 | 72.1k | } |
118 | | |
119 | | void FixStorageClass::FixInstructionStorageClass( |
120 | | Instruction* inst, spv::StorageClass storage_class, |
121 | 0 | std::set<uint32_t>* seen) { |
122 | 0 | assert(IsPointerResultType(inst) && |
123 | 0 | "The result type of the instruction must be a pointer."); |
124 | | |
125 | 0 | ChangeResultStorageClass(inst, storage_class); |
126 | |
|
127 | 0 | std::vector<Instruction*> uses; |
128 | 0 | get_def_use_mgr()->ForEachUser( |
129 | 0 | inst, [&uses](Instruction* use) { uses.push_back(use); }); |
130 | 0 | for (Instruction* use : uses) { |
131 | 0 | PropagateStorageClass(use, storage_class, seen); |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | | void FixStorageClass::ChangeResultStorageClass( |
136 | 0 | Instruction* inst, spv::StorageClass storage_class) const { |
137 | 0 | analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
138 | 0 | Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst->type_id()); |
139 | 0 | assert(result_type_inst->opcode() == spv::Op::OpTypePointer); |
140 | 0 | uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1); |
141 | 0 | uint32_t new_result_type_id = |
142 | 0 | type_mgr->FindPointerToType(pointee_type_id, storage_class); |
143 | 0 | if (new_result_type_id == 0) { |
144 | 0 | return; |
145 | 0 | } |
146 | 0 | inst->SetResultType(new_result_type_id); |
147 | 0 | context()->UpdateDefUse(inst); |
148 | 0 | } |
149 | | |
150 | 371k | bool FixStorageClass::IsPointerResultType(Instruction* inst) { |
151 | 371k | if (inst->type_id() == 0) { |
152 | 133k | return false; |
153 | 133k | } |
154 | | |
155 | 237k | Instruction* type_def = get_def_use_mgr()->GetDef(inst->type_id()); |
156 | 237k | return type_def->opcode() == spv::Op::OpTypePointer; |
157 | 371k | } |
158 | | |
159 | | bool FixStorageClass::IsPointerToStorageClass(Instruction* inst, |
160 | 72.1k | spv::StorageClass storage_class) { |
161 | 72.1k | if (inst->type_id() == 0) { |
162 | 0 | return false; |
163 | 0 | } |
164 | | |
165 | 72.1k | Instruction* type_def = get_def_use_mgr()->GetDef(inst->type_id()); |
166 | 72.1k | if (type_def->opcode() != spv::Op::OpTypePointer) { |
167 | 0 | return false; |
168 | 0 | } |
169 | | |
170 | 72.1k | const uint32_t kPointerTypeStorageClassIndex = 0; |
171 | 72.1k | spv::StorageClass pointer_storage_class = static_cast<spv::StorageClass>( |
172 | 72.1k | type_def->GetSingleWordInOperand(kPointerTypeStorageClassIndex)); |
173 | 72.1k | return pointer_storage_class == storage_class; |
174 | 72.1k | } |
175 | | |
176 | | bool FixStorageClass::ChangeResultType(Instruction* inst, |
177 | 235k | uint32_t new_type_id) { |
178 | 235k | if (new_type_id == 0 || inst->type_id() == new_type_id) { |
179 | 235k | return false; |
180 | 235k | } |
181 | | |
182 | 0 | context()->ForgetUses(inst); |
183 | 0 | inst->SetResultType(new_type_id); |
184 | 0 | context()->AnalyzeUses(inst); |
185 | 0 | return true; |
186 | 235k | } |
187 | | |
188 | | bool FixStorageClass::PropagateType(Instruction* inst, uint32_t type_id, |
189 | 572k | uint32_t op_idx, std::set<uint32_t>* seen) { |
190 | 572k | assert(type_id != 0 && "Not given a valid type in PropagateType"); |
191 | 572k | bool modified = false; |
192 | | |
193 | | // If the type of operand |op_idx| forces the result type of |inst| to a |
194 | | // particular type, then we want find that type. |
195 | 572k | uint32_t new_type_id = 0; |
196 | 572k | switch (inst->opcode()) { |
197 | 96.9k | case spv::Op::OpAccessChain: |
198 | 96.9k | case spv::Op::OpPtrAccessChain: |
199 | 97.3k | case spv::Op::OpInBoundsAccessChain: |
200 | 97.3k | case spv::Op::OpInBoundsPtrAccessChain: |
201 | 97.3k | if (op_idx == 2) { |
202 | 72.1k | new_type_id = WalkAccessChainType(inst, type_id); |
203 | 72.1k | } |
204 | 97.3k | break; |
205 | 19 | case spv::Op::OpCopyObject: |
206 | 19 | new_type_id = type_id; |
207 | 19 | break; |
208 | 615 | case spv::Op::OpPhi: |
209 | 615 | if (seen->insert(inst->result_id()).second) { |
210 | 527 | new_type_id = type_id; |
211 | 527 | } |
212 | 615 | break; |
213 | 0 | case spv::Op::OpSelect: |
214 | 0 | if (op_idx > 2) { |
215 | 0 | new_type_id = type_id; |
216 | 0 | } |
217 | 0 | break; |
218 | 2.09k | case spv::Op::OpFunctionCall: |
219 | | // We cannot be sure of the actual connection between the type |
220 | | // of the parameter and the type of the result, so we should not |
221 | | // do anything. If the result type needs to be fixed, the function call |
222 | | // should be inlined. |
223 | 2.09k | return false; |
224 | 163k | case spv::Op::OpLoad: { |
225 | 163k | Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); |
226 | 163k | new_type_id = type_inst->GetSingleWordInOperand(1); |
227 | 163k | break; |
228 | 97.3k | } |
229 | 160k | case spv::Op::OpStore: { |
230 | 160k | uint32_t obj_id = inst->GetSingleWordInOperand(1); |
231 | 160k | Instruction* obj_inst = get_def_use_mgr()->GetDef(obj_id); |
232 | 160k | uint32_t obj_type_id = obj_inst->type_id(); |
233 | | |
234 | 160k | uint32_t ptr_id = inst->GetSingleWordInOperand(0); |
235 | 160k | Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id); |
236 | 160k | uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst); |
237 | | |
238 | 160k | if (obj_type_id != pointee_type_id) { |
239 | 0 | if (context()->get_type_mgr()->GetType(obj_type_id)->AsImage() && |
240 | 0 | context()->get_type_mgr()->GetType(pointee_type_id)->AsImage()) { |
241 | | // When storing an image, allow the type mismatch |
242 | | // and let the later legalization passes eliminate the OpStore. |
243 | | // This is to support assigning an image to a variable, |
244 | | // where the assigned image does not have a pre-defined |
245 | | // image format. |
246 | 0 | return false; |
247 | 0 | } |
248 | | |
249 | 0 | uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst); |
250 | 0 | if (copy_id == 0) { |
251 | 0 | return false; |
252 | 0 | } |
253 | 0 | inst->SetInOperand(1, {copy_id}); |
254 | 0 | context()->UpdateDefUse(inst); |
255 | 0 | } |
256 | 160k | } break; |
257 | 160k | case spv::Op::OpCopyMemory: |
258 | 0 | case spv::Op::OpCopyMemorySized: |
259 | | // TODO: May need to expand the copy as we do with the stores. |
260 | 0 | break; |
261 | 8.43k | case spv::Op::OpCompositeConstruct: |
262 | 11.0k | case spv::Op::OpCompositeExtract: |
263 | 11.1k | case spv::Op::OpCompositeInsert: |
264 | | // TODO: DXC does not seem to generate code that will require changes to |
265 | | // these opcode. The can be implemented when they come up. |
266 | 11.1k | break; |
267 | 0 | case spv::Op::OpImageTexelPointer: |
268 | 4 | case spv::Op::OpBitcast: |
269 | | // Nothing to change for these opcode. The result type is the same |
270 | | // regardless of the type of the operand. |
271 | 4 | return false; |
272 | 137k | default: |
273 | | // I expect the remaining instructions to act on types that are guaranteed |
274 | | // to be unique, so no change will be necessary. |
275 | 137k | break; |
276 | 572k | } |
277 | | |
278 | | // If the operand forces the result type, then make sure the result type |
279 | | // matches, and update the uses of |inst|. We do not have to check the uses |
280 | | // of |inst| in the result type is not forced because we are only looking for |
281 | | // issue that come from mismatches between function formal and actual |
282 | | // parameters after the function has been inlined. These parameters are |
283 | | // pointers. Once the type no longer depends on the type of the parameter, |
284 | | // then the types should have be correct. |
285 | 569k | if (new_type_id != 0) { |
286 | 235k | modified = ChangeResultType(inst, new_type_id); |
287 | | |
288 | 235k | std::vector<std::pair<Instruction*, uint32_t>> uses; |
289 | 235k | get_def_use_mgr()->ForEachUse(inst, |
290 | 274k | [&uses](Instruction* use, uint32_t idx) { |
291 | 274k | uses.push_back({use, idx}); |
292 | 274k | }); |
293 | | |
294 | 274k | for (auto& use : uses) { |
295 | 274k | PropagateType(use.first, new_type_id, use.second, seen); |
296 | 274k | } |
297 | | |
298 | 235k | if (inst->opcode() == spv::Op::OpPhi) { |
299 | 527 | seen->erase(inst->result_id()); |
300 | 527 | } |
301 | 235k | } |
302 | 569k | return modified; |
303 | 572k | } |
304 | | |
305 | 72.1k | uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) { |
306 | 72.1k | uint32_t start_idx = 0; |
307 | 72.1k | switch (inst->opcode()) { |
308 | 71.8k | case spv::Op::OpAccessChain: |
309 | 72.1k | case spv::Op::OpInBoundsAccessChain: |
310 | 72.1k | start_idx = 1; |
311 | 72.1k | break; |
312 | 0 | case spv::Op::OpPtrAccessChain: |
313 | 0 | case spv::Op::OpInBoundsPtrAccessChain: |
314 | 0 | start_idx = 2; |
315 | 0 | break; |
316 | 0 | default: |
317 | 0 | assert(false); |
318 | 0 | break; |
319 | 72.1k | } |
320 | | |
321 | 72.1k | Instruction* id_type_inst = get_def_use_mgr()->GetDef(id); |
322 | 72.1k | assert(id_type_inst->opcode() == spv::Op::OpTypePointer); |
323 | 72.1k | id = id_type_inst->GetSingleWordInOperand(1); |
324 | 72.1k | spv::StorageClass input_storage_class = |
325 | 72.1k | static_cast<spv::StorageClass>(id_type_inst->GetSingleWordInOperand(0)); |
326 | | |
327 | 163k | for (uint32_t i = start_idx; i < inst->NumInOperands(); ++i) { |
328 | 91.2k | Instruction* type_inst = get_def_use_mgr()->GetDef(id); |
329 | 91.2k | switch (type_inst->opcode()) { |
330 | 29.3k | case spv::Op::OpTypeArray: |
331 | 29.3k | case spv::Op::OpTypeRuntimeArray: |
332 | 29.3k | case spv::Op::OpTypeNodePayloadArrayAMDX: |
333 | 29.9k | case spv::Op::OpTypeMatrix: |
334 | 55.5k | case spv::Op::OpTypeVector: |
335 | 55.5k | case spv::Op::OpTypeVectorIdEXT: |
336 | 55.5k | case spv::Op::OpTypeCooperativeMatrixKHR: |
337 | 55.5k | case spv::Op::OpTypeCooperativeMatrixNV: |
338 | 55.5k | id = type_inst->GetSingleWordInOperand(0); |
339 | 55.5k | break; |
340 | 35.6k | case spv::Op::OpTypeStruct: { |
341 | 35.6k | const analysis::Constant* index_const = |
342 | 35.6k | context()->get_constant_mgr()->FindDeclaredConstant( |
343 | 35.6k | inst->GetSingleWordInOperand(i)); |
344 | | // It is highly unlikely that any type would have more fields than could |
345 | | // be indexed by a 32-bit integer, and GetSingleWordInOperand only takes |
346 | | // a 32-bit value, so we would not be able to handle it anyway. But the |
347 | | // specification does allow any scalar integer type, treated as signed, |
348 | | // so we simply downcast the index to 32-bits. |
349 | 35.6k | uint32_t index = |
350 | 35.6k | static_cast<uint32_t>(index_const->GetSignExtendedValue()); |
351 | 35.6k | id = type_inst->GetSingleWordInOperand(index); |
352 | 35.6k | break; |
353 | 55.5k | } |
354 | 0 | default: |
355 | 0 | break; |
356 | 91.2k | } |
357 | 91.2k | assert(id != 0 && |
358 | 91.2k | "Tried to extract from an object where it cannot be done."); |
359 | 91.2k | } |
360 | | |
361 | 72.1k | Instruction* orig_type_inst = get_def_use_mgr()->GetDef(inst->type_id()); |
362 | 72.1k | spv::StorageClass orig_storage_class = |
363 | 72.1k | static_cast<spv::StorageClass>(orig_type_inst->GetSingleWordInOperand(0)); |
364 | 72.1k | assert(orig_type_inst->opcode() == spv::Op::OpTypePointer); |
365 | 72.1k | if (orig_type_inst->GetSingleWordInOperand(1) == id && |
366 | 72.1k | input_storage_class == orig_storage_class) { |
367 | | // The existing type is correct. Avoid the search for the type. Note that if |
368 | | // there is a duplicate type, the search below could return a different type |
369 | | // forcing more changes to the code than necessary. |
370 | 72.1k | return inst->type_id(); |
371 | 72.1k | } |
372 | | |
373 | 0 | return context()->get_type_mgr()->FindPointerToType(id, input_storage_class); |
374 | 72.1k | } |
375 | | |
376 | | // namespace opt |
377 | | |
378 | | } // namespace opt |
379 | | } // namespace spvtools |