1 // Copyright (c) 2017 Google Inc.
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/private_to_local_pass.h"
16
17 #include <memory>
18 #include <utility>
19 #include <vector>
20
21 #include "source/opt/ir_context.h"
22 #include "source/spirv_constant.h"
23
24 namespace spvtools {
25 namespace opt {
26 namespace {
27
28 const uint32_t kVariableStorageClassInIdx = 0;
29 const uint32_t kSpvTypePointerTypeIdInIdx = 1;
30
31 } // namespace
32
Process()33 Pass::Status PrivateToLocalPass::Process() {
34 bool modified = false;
35
36 // Private variables require the shader capability. If this is not a shader,
37 // there is no work to do.
38 if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
39 return Status::SuccessWithoutChange;
40
41 std::vector<std::pair<Instruction*, Function*>> variables_to_move;
42 std::unordered_set<uint32_t> localized_variables;
43 for (auto& inst : context()->types_values()) {
44 if (inst.opcode() != SpvOpVariable) {
45 continue;
46 }
47
48 if (inst.GetSingleWordInOperand(kVariableStorageClassInIdx) !=
49 SpvStorageClassPrivate) {
50 continue;
51 }
52
53 Function* target_function = FindLocalFunction(inst);
54 if (target_function != nullptr) {
55 variables_to_move.push_back({&inst, target_function});
56 }
57 }
58
59 modified = !variables_to_move.empty();
60 for (auto p : variables_to_move) {
61 if (!MoveVariable(p.first, p.second)) {
62 return Status::Failure;
63 }
64 localized_variables.insert(p.first->result_id());
65 }
66
67 if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
68 // In SPIR-V 1.4 and later entry points must list private storage class
69 // variables that are statically used by the entry point. Go through the
70 // entry points and remove any references to variables that were localized.
71 for (auto& entry : get_module()->entry_points()) {
72 std::vector<Operand> new_operands;
73 for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
74 // Execution model, function id and name are always kept.
75 if (i < 3 ||
76 !localized_variables.count(entry.GetSingleWordInOperand(i))) {
77 new_operands.push_back(entry.GetInOperand(i));
78 }
79 }
80 if (new_operands.size() != entry.NumInOperands()) {
81 entry.SetInOperands(std::move(new_operands));
82 context()->AnalyzeUses(&entry);
83 }
84 }
85 }
86
87 return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
88 }
89
FindLocalFunction(const Instruction & inst) const90 Function* PrivateToLocalPass::FindLocalFunction(const Instruction& inst) const {
91 bool found_first_use = false;
92 Function* target_function = nullptr;
93 context()->get_def_use_mgr()->ForEachUser(
94 inst.result_id(),
95 [&target_function, &found_first_use, this](Instruction* use) {
96 BasicBlock* current_block = context()->get_instr_block(use);
97 if (current_block == nullptr) {
98 return;
99 }
100
101 if (!IsValidUse(use)) {
102 found_first_use = true;
103 target_function = nullptr;
104 return;
105 }
106 Function* current_function = current_block->GetParent();
107 if (!found_first_use) {
108 found_first_use = true;
109 target_function = current_function;
110 } else if (target_function != current_function) {
111 target_function = nullptr;
112 }
113 });
114 return target_function;
115 } // namespace opt
116
MoveVariable(Instruction * variable,Function * function)117 bool PrivateToLocalPass::MoveVariable(Instruction* variable,
118 Function* function) {
119 // The variable needs to be removed from the global section, and placed in the
120 // header of the function. First step remove from the global list.
121 variable->RemoveFromList();
122 std::unique_ptr<Instruction> var(variable); // Take ownership.
123 context()->ForgetUses(variable);
124
125 // Update the storage class of the variable.
126 variable->SetInOperand(kVariableStorageClassInIdx, {SpvStorageClassFunction});
127
128 // Update the type as well.
129 uint32_t new_type_id = GetNewType(variable->type_id());
130 if (new_type_id == 0) {
131 return false;
132 }
133 variable->SetResultType(new_type_id);
134
135 // Place the variable at the start of the first basic block.
136 context()->AnalyzeUses(variable);
137 context()->set_instr_block(variable, &*function->begin());
138 function->begin()->begin()->InsertBefore(move(var));
139
140 // Update uses where the type may have changed.
141 return UpdateUses(variable);
142 }
143
GetNewType(uint32_t old_type_id)144 uint32_t PrivateToLocalPass::GetNewType(uint32_t old_type_id) {
145 auto type_mgr = context()->get_type_mgr();
146 Instruction* old_type_inst = get_def_use_mgr()->GetDef(old_type_id);
147 uint32_t pointee_type_id =
148 old_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
149 uint32_t new_type_id =
150 type_mgr->FindPointerToType(pointee_type_id, SpvStorageClassFunction);
151 if (new_type_id != 0) {
152 context()->UpdateDefUse(context()->get_def_use_mgr()->GetDef(new_type_id));
153 }
154 return new_type_id;
155 }
156
IsValidUse(const Instruction * inst) const157 bool PrivateToLocalPass::IsValidUse(const Instruction* inst) const {
158 // The cases in this switch have to match the cases in |UpdateUse|.
159 // If we don't know how to update it, it is not valid.
160 if (inst->GetOpenCL100DebugOpcode() ==
161 OpenCLDebugInfo100DebugGlobalVariable) {
162 return true;
163 }
164 switch (inst->opcode()) {
165 case SpvOpLoad:
166 case SpvOpStore:
167 case SpvOpImageTexelPointer: // Treat like a load
168 return true;
169 case SpvOpAccessChain:
170 return context()->get_def_use_mgr()->WhileEachUser(
171 inst, [this](const Instruction* user) {
172 if (!IsValidUse(user)) return false;
173 return true;
174 });
175 case SpvOpName:
176 return true;
177 default:
178 return spvOpcodeIsDecoration(inst->opcode());
179 }
180 }
181
UpdateUse(Instruction * inst,Instruction * user)182 bool PrivateToLocalPass::UpdateUse(Instruction* inst, Instruction* user) {
183 // The cases in this switch have to match the cases in |IsValidUse|. If we
184 // don't think it is valid, the optimization will not view the variable as a
185 // candidate, and therefore the use will not be updated.
186 if (inst->GetOpenCL100DebugOpcode() ==
187 OpenCLDebugInfo100DebugGlobalVariable) {
188 context()->get_debug_info_mgr()->ConvertDebugGlobalToLocalVariable(inst,
189 user);
190 return true;
191 }
192 switch (inst->opcode()) {
193 case SpvOpLoad:
194 case SpvOpStore:
195 case SpvOpImageTexelPointer: // Treat like a load
196 // The type is fine because it is the type pointed to, and that does not
197 // change.
198 break;
199 case SpvOpAccessChain: {
200 context()->ForgetUses(inst);
201 uint32_t new_type_id = GetNewType(inst->type_id());
202 if (new_type_id == 0) {
203 return false;
204 }
205 inst->SetResultType(new_type_id);
206 context()->AnalyzeUses(inst);
207
208 // Update uses where the type may have changed.
209 if (!UpdateUses(inst)) {
210 return false;
211 }
212 } break;
213 case SpvOpName:
214 case SpvOpEntryPoint: // entry points will be updated separately.
215 break;
216 default:
217 assert(spvOpcodeIsDecoration(inst->opcode()) &&
218 "Do not know how to update the type for this instruction.");
219 break;
220 }
221 return true;
222 }
223
UpdateUses(Instruction * inst)224 bool PrivateToLocalPass::UpdateUses(Instruction* inst) {
225 uint32_t id = inst->result_id();
226 std::vector<Instruction*> uses;
227 context()->get_def_use_mgr()->ForEachUser(
228 id, [&uses](Instruction* use) { uses.push_back(use); });
229
230 for (Instruction* use : uses) {
231 if (!UpdateUse(use, inst)) {
232 return false;
233 }
234 }
235 return true;
236 }
237
238 } // namespace opt
239 } // namespace spvtools
240