OLD | NEW |
---|---|
1 // Copyright 2014 the V8 project authors. All rights reserved. | 1 // Copyright 2016 the V8 project authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "src/compiler/load-elimination.h" | 5 #include "src/compiler/load-elimination.h" |
6 | 6 |
7 #include "src/compiler/graph.h" | 7 #include "src/compiler/js-graph.h" |
8 #include "src/compiler/node-marker.h" | |
8 #include "src/compiler/node-properties.h" | 9 #include "src/compiler/node-properties.h" |
10 #include "src/compiler/node.h" | |
9 #include "src/compiler/simplified-operator.h" | 11 #include "src/compiler/simplified-operator.h" |
10 #include "src/types.h" | |
11 | 12 |
12 namespace v8 { | 13 namespace v8 { |
13 namespace internal { | 14 namespace internal { |
14 namespace compiler { | 15 namespace compiler { |
15 | 16 |
16 LoadElimination::~LoadElimination() {} | 17 #ifdef DEBUG |
17 | 18 #define TRACE(...) \ |
18 Reduction LoadElimination::Reduce(Node* node) { | 19 do { \ |
20 if (FLAG_trace_turbo_load_elimination) PrintF(__VA_ARGS__); \ | |
21 } while (false) | |
22 #else | |
23 #define TRACE(...) | |
24 #endif | |
25 | |
26 namespace { | |
27 | |
28 const size_t kMaxTrackedFields = 16; | |
29 | |
30 Node* ActualValue(Node* node) { | |
19 switch (node->opcode()) { | 31 switch (node->opcode()) { |
20 case IrOpcode::kLoadField: | 32 case IrOpcode::kCheckBounds: |
21 return ReduceLoadField(node); | 33 case IrOpcode::kCheckNumber: |
34 case IrOpcode::kCheckTaggedPointer: | |
35 case IrOpcode::kCheckTaggedSigned: | |
36 case IrOpcode::kFinishRegion: | |
37 return ActualValue(NodeProperties::GetValueInput(node, 0)); | |
22 default: | 38 default: |
23 break; | 39 return node; |
24 } | 40 } |
25 return NoChange(); | |
26 } | 41 } |
27 | 42 |
28 Reduction LoadElimination::ReduceLoadField(Node* node) { | 43 enum Aliasing { kNoAlias, kMayAlias, kMustAlias }; |
29 DCHECK_EQ(IrOpcode::kLoadField, node->opcode()); | 44 |
30 FieldAccess const access = FieldAccessOf(node->op()); | 45 Aliasing QueryAlias(Node* a, Node* b) { |
31 Node* object = NodeProperties::GetValueInput(node, 0); | 46 if (a == b) return kMustAlias; |
32 for (Node* effect = NodeProperties::GetEffectInput(node);; | 47 if (b->opcode() == IrOpcode::kAllocate) { |
33 effect = NodeProperties::GetEffectInput(effect)) { | 48 switch (a->opcode()) { |
34 switch (effect->opcode()) { | 49 case IrOpcode::kAllocate: |
35 case IrOpcode::kLoadField: { | 50 case IrOpcode::kHeapConstant: |
36 FieldAccess const effect_access = FieldAccessOf(effect->op()); | 51 case IrOpcode::kParameter: |
37 if (object == NodeProperties::GetValueInput(effect, 0) && | 52 return kNoAlias; |
38 access == effect_access && effect_access.type->Is(access.type)) { | 53 default: |
39 Node* const value = effect; | 54 break; |
40 ReplaceWithValue(node, value); | 55 } |
41 return Replace(value); | 56 } |
57 if (a->opcode() == IrOpcode::kAllocate) { | |
58 switch (b->opcode()) { | |
59 case IrOpcode::kHeapConstant: | |
60 case IrOpcode::kParameter: | |
61 return kNoAlias; | |
62 default: | |
63 break; | |
64 } | |
65 } | |
66 return kMayAlias; | |
67 } | |
68 | |
69 bool MayAlias(Node* a, Node* b) { return QueryAlias(a, b) == kMayAlias; } | |
Jarin
2016/07/04 13:18:07
How about QueryAlias(a, b) == kMayAlias || QueryAl
Benedikt Meurer
2016/07/05 11:29:09
Done.
| |
70 | |
71 bool MustAlias(Node* a, Node* b) { return QueryAlias(a, b) == kMustAlias; } | |
72 | |
73 class AbstractField final : public ZoneObject { | |
Jarin
2016/07/04 13:18:07
Please explain in a comment what this is (+ the re
Benedikt Meurer
2016/07/05 11:29:09
Done.
| |
74 public: | |
75 explicit AbstractField(Zone* zone) : info_for_node_(zone) {} | |
76 AbstractField(Node* object, Node* value, Zone* zone) : info_for_node_(zone) { | |
77 info_for_node_.insert(std::make_pair(object, value)); | |
78 } | |
79 | |
80 AbstractField const* Extend(Node* object, Node* value, Zone* zone) const { | |
81 AbstractField* that = new (zone) AbstractField(zone); | |
82 that->info_for_node_ = this->info_for_node_; | |
83 that->info_for_node_.insert(std::make_pair(object, value)); | |
84 return that; | |
85 } | |
86 Node* Lookup(Node* object) const { | |
87 for (auto pair : info_for_node_) { | |
88 if (MustAlias(object, pair.first)) return pair.second; | |
89 } | |
90 return nullptr; | |
91 } | |
92 AbstractField const* Kill(Node* object, Zone* zone) const { | |
93 for (auto pair : this->info_for_node_) { | |
94 if (MayAlias(object, pair.first)) { | |
95 AbstractField* that = new (zone) AbstractField(zone); | |
96 for (auto pair : this->info_for_node_) { | |
97 if (!MayAlias(object, pair.first)) that->info_for_node_.insert(pair); | |
42 } | 98 } |
99 return that; | |
100 } | |
101 } | |
102 return this; | |
103 } | |
104 bool Equals(AbstractField const* that) const { | |
105 return this == that || this->info_for_node_ == that->info_for_node_; | |
106 } | |
107 AbstractField const* Merge(AbstractField const* that, Zone* zone) const { | |
108 if (this->Equals(that)) return this; | |
109 AbstractField* copy = new (zone) AbstractField(zone); | |
110 for (auto this_it : this->info_for_node_) { | |
111 Node* this_object = this_it.first; | |
112 Node* this_value = this_it.second; | |
113 auto that_it = that->info_for_node_.find(this_object); | |
114 if (that_it != that->info_for_node_.end() && | |
115 that_it->second == this_value) { | |
116 copy->info_for_node_.insert(this_it); | |
117 } | |
118 } | |
119 return copy; | |
120 } | |
121 | |
122 private: | |
123 ZoneMap<Node*, Node*> info_for_node_; | |
124 }; | |
125 | |
126 class AbstractState final : public ZoneObject { | |
127 public: | |
128 AbstractState() { | |
129 for (size_t i = 0; i < kMaxTrackedFields; ++i) fields_[i] = nullptr; | |
130 } | |
131 | |
132 AbstractState const* Extend(Node* object, size_t index, Node* value, | |
133 Zone* zone) const { | |
134 AbstractState* that = new (zone) AbstractState(*this); | |
135 AbstractField const* that_field = that->fields_[index]; | |
136 if (that_field) { | |
137 that_field = that_field->Extend(object, value, zone); | |
138 } else { | |
139 that_field = new (zone) AbstractField(object, value, zone); | |
140 } | |
141 that->fields_[index] = that_field; | |
142 return that; | |
143 } | |
144 AbstractState const* Kill(Node* object, size_t index, Zone* zone) const { | |
145 if (!this->fields_[index]) return this; | |
146 AbstractState* that = new (zone) AbstractState(*this); | |
147 that->fields_[index] = nullptr; | |
148 return that; | |
149 } | |
150 AbstractState const* Merge(AbstractState const* that, Zone* zone) const { | |
151 if (this->Equals(that)) return this; | |
152 AbstractState* copy = new (zone) AbstractState(); | |
153 for (size_t i = 0; i < kMaxTrackedFields; ++i) { | |
154 AbstractField const* this_field = this->fields_[i]; | |
155 AbstractField const* that_field = that->fields_[i]; | |
156 if (this_field && that_field) { | |
157 copy->fields_[i] = this_field->Merge(that_field, zone); | |
158 } | |
159 } | |
160 return copy; | |
161 } | |
162 Node* Lookup(Node* object, size_t index) const { | |
163 AbstractField const* this_field = this->fields_[index]; | |
164 if (this_field) return this_field->Lookup(object); | |
165 return nullptr; | |
166 } | |
167 bool Equals(AbstractState const* that) const { | |
168 if (this == that) return true; | |
169 for (size_t i = 0; i < kMaxTrackedFields; ++i) { | |
170 AbstractField const* this_field = this->fields_[i]; | |
171 AbstractField const* that_field = that->fields_[i]; | |
172 if (this_field) { | |
173 if (!that_field || !this_field->Equals(that_field)) return false; | |
174 } else if (that_field) { | |
175 return false; | |
176 } | |
177 DCHECK(this_field == that_field); | |
178 } | |
179 return true; | |
180 } | |
181 | |
182 private: | |
183 AbstractField const* fields_[kMaxTrackedFields]; | |
184 }; | |
185 | |
186 class LoadEliminationAnalysis final { | |
187 public: | |
188 LoadEliminationAnalysis(Graph* graph, Zone* zone) | |
189 : candidates_(zone), | |
190 empty_state_(), | |
191 queue_(zone), | |
192 node_states_(graph->NodeCount(), zone) {} | |
193 | |
194 void Run(Node* start) { | |
195 TRACE("--{Analysis phase}--\n"); | |
196 UpdateState(start, empty_state()); | |
197 while (!queue_.empty()) { | |
198 Node* const node = queue_.top(); | |
199 queue_.pop(); | |
200 VisitNode(node); | |
201 } | |
202 TRACE("--{Replacement phase}--\n"); | |
203 ZoneMap<Node*, Node*> replacements(zone()); | |
204 for (Node* const node : candidates_) { | |
205 switch (node->opcode()) { | |
206 case IrOpcode::kLoadField: { | |
207 FieldAccess const& access = FieldAccessOf(node->op()); | |
208 Node* const object = | |
209 ActualValue(NodeProperties::GetValueInput(node, 0)); | |
210 Node* const effect = NodeProperties::GetEffectInput(node); | |
211 AbstractState const* state = GetState(effect); | |
212 int field_index = FieldIndexOf(access); | |
213 DCHECK_LE(0, field_index); | |
214 if (Node* value = state->Lookup(object, field_index)) { | |
215 auto it = replacements.find(value); | |
216 if (it != replacements.end()) value = it->second; | |
217 replacements.insert(std::make_pair(node, value)); | |
218 TRACE(" - Replacing redundant #%d:LoadField with #%d:%s\n", | |
219 node->id(), value->id(), value->op()->mnemonic()); | |
220 NodeProperties::ReplaceUses(node, value, effect); | |
221 node->Kill(); | |
222 } | |
223 break; | |
224 } | |
225 case IrOpcode::kStoreField: { | |
226 FieldAccess const& access = FieldAccessOf(node->op()); | |
227 Node* const object = | |
228 ActualValue(NodeProperties::GetValueInput(node, 0)); | |
229 Node* const value = | |
230 ActualValue(NodeProperties::GetValueInput(node, 1)); | |
231 Node* const effect = NodeProperties::GetEffectInput(node); | |
232 AbstractState const* state = GetState(effect); | |
233 int field_index = FieldIndexOf(access); | |
234 DCHECK_LE(0, field_index); | |
Jarin
2016/07/04 13:18:07
It is a shame that we cannot learn from control fl
Benedikt Meurer
2016/07/05 11:29:09
Yeah... compiler hall of shame.
| |
235 if (value == state->Lookup(object, field_index)) { | |
236 TRACE(" - Killing redundant #%d:StoreField\n", node->id()); | |
237 NodeProperties::ReplaceUses(node, value, effect); | |
238 node->Kill(); | |
239 } | |
240 break; | |
241 } | |
242 default: | |
243 UNREACHABLE(); | |
244 } | |
245 } | |
246 } | |
247 | |
248 private: | |
249 void VisitNode(Node* node) { | |
250 TRACE(" - Visiting node #%d:%s\n", node->id(), node->op()->mnemonic()); | |
251 switch (node->opcode()) { | |
252 case IrOpcode::kEffectPhi: | |
253 return VisitEffectPhi(node); | |
254 case IrOpcode::kLoadField: | |
255 return VisitLoadField(node); | |
256 case IrOpcode::kStoreElement: | |
257 return VisitStoreElement(node); | |
258 case IrOpcode::kStoreField: | |
259 return VisitStoreField(node); | |
260 case IrOpcode::kDeoptimize: | |
261 case IrOpcode::kReturn: | |
262 case IrOpcode::kTerminate: | |
263 case IrOpcode::kThrow: | |
43 break; | 264 break; |
44 } | 265 default: |
45 case IrOpcode::kStoreField: { | 266 return VisitOtherNode(node); |
46 if (access == FieldAccessOf(effect->op())) { | 267 } |
47 if (object == NodeProperties::GetValueInput(effect, 0)) { | 268 } |
48 Node* const value = NodeProperties::GetValueInput(effect, 1); | 269 |
49 Type* value_type = NodeProperties::GetType(value); | 270 void VisitEffectPhi(Node* node) { |
50 Type* node_type = NodeProperties::GetType(node); | 271 int const input_count = node->InputCount() - 1; |
51 // Make sure the replacement's type is a subtype of the node's | 272 DCHECK_LT(0, input_count); |
52 // type. Otherwise we could confuse optimizations that were | 273 Node* const control = NodeProperties::GetControlInput(node); |
53 // based on the original type. | 274 Node* const effect0 = NodeProperties::GetEffectInput(node, 0); |
54 if (value_type->Is(node_type)) { | 275 AbstractState const* state = GetState(effect0); |
55 ReplaceWithValue(node, value); | 276 if (state == nullptr) return; |
56 return Replace(value); | 277 if (control->opcode() == IrOpcode::kMerge) { |
57 } else { | 278 for (int i = 1; i < input_count; ++i) { |
58 // This LoadField has stronger guarantees than the stored value | 279 Node* const effecti = NodeProperties::GetEffectInput(node, i); |
59 // can give us, which suggests that we are probably in unreachable | 280 if (GetState(effecti) == nullptr) return; |
60 // code, guarded by some Check, so don't bother trying to optimize | 281 } |
61 // this LoadField {node}. | 282 } |
62 return NoChange(); | 283 for (int i = 1; i < input_count; ++i) { |
63 } | 284 Node* const effecti = NodeProperties::GetEffectInput(node, i); |
64 } | 285 if (AbstractState const* statei = GetState(effecti)) { |
65 // TODO(turbofan): Alias analysis to the rescue? | 286 state = state->Merge(statei, zone()); |
66 return NoChange(); | 287 } |
67 } | 288 } |
68 break; | 289 UpdateState(node, state); |
69 } | 290 } |
70 case IrOpcode::kBeginRegion: | 291 |
71 case IrOpcode::kStoreBuffer: | 292 int FieldIndexOf(FieldAccess const& access) const { |
72 case IrOpcode::kStoreElement: { | 293 if (access.offset % kPointerSize != 0) return -1; |
73 // These can never interfere with field loads. | 294 int field_index = access.offset / kPointerSize; |
74 break; | 295 if (field_index >= static_cast<int>(kMaxTrackedFields)) return -1; |
75 } | 296 return field_index; |
76 case IrOpcode::kFinishRegion: { | 297 } |
77 // "Look through" FinishRegion nodes to make LoadElimination capable | 298 AbstractState const* GetState(Node* node) const { |
78 // of looking into atomic regions. | 299 return node_states_[node->id()]; |
79 if (object == effect) object = NodeProperties::GetValueInput(effect, 0); | 300 } |
80 break; | 301 void SetState(Node* node, AbstractState const* state) { |
81 } | 302 node_states_[node->id()] = state; |
82 case IrOpcode::kAllocate: { | 303 } |
83 // Allocations don't interfere with field loads. In case we see the | 304 |
84 // actual allocation for the {object} we can abort. | 305 void VisitLoadField(Node* node) { |
85 if (object == effect) return NoChange(); | 306 FieldAccess const& access = FieldAccessOf(node->op()); |
86 break; | 307 Node* const object = ActualValue(NodeProperties::GetValueInput(node, 0)); |
87 } | 308 Node* const effect = NodeProperties::GetEffectInput(node); |
88 default: { | 309 AbstractState const* state = GetState(effect); |
89 if (!effect->op()->HasProperty(Operator::kNoWrite) || | 310 int field_index = FieldIndexOf(access); |
Jarin
2016/07/04 13:18:07
Should not we also check the size? Either we only
Benedikt Meurer
2016/07/05 11:29:09
Done.
| |
90 effect->op()->EffectInputCount() != 1) { | 311 if (field_index >= 0) { |
91 return NoChange(); | 312 Node* const value = state->Lookup(object, field_index); |
92 } | 313 if (!value) { |
93 break; | 314 TRACE(" Node #%d:LoadField is not redundant\n", node->id()); |
94 } | 315 state = state->Extend(object, field_index, node, zone()); |
95 } | 316 } else if (!NodeProperties::GetType(value)->Is(access.type)) { |
96 } | 317 TRACE( |
97 UNREACHABLE(); | 318 " Node #%d:LoadField is redundant for #%d:%s, but" |
98 return NoChange(); | 319 " types don't agree\n", |
320 node->id(), value->id(), value->op()->mnemonic()); | |
321 state = state->Extend(object, field_index, node, zone()); | |
322 } else if (value) { | |
323 TRACE(" Node #%d:LoadField is fully redundant for #%d:%s\n", | |
324 node->id(), value->id(), value->op()->mnemonic()); | |
325 candidates_.insert(node); | |
326 } | |
327 } | |
328 UpdateState(node, state); | |
329 } | |
330 | |
331 void VisitStoreField(Node* node) { | |
332 FieldAccess const& access = FieldAccessOf(node->op()); | |
333 Node* const object = ActualValue(NodeProperties::GetValueInput(node, 0)); | |
334 Node* const new_value = NodeProperties::GetValueInput(node, 1); | |
335 Node* const effect = NodeProperties::GetEffectInput(node); | |
336 AbstractState const* state = GetState(effect); | |
337 int field_index = FieldIndexOf(access); | |
338 if (field_index >= 0) { | |
339 Node* const old_value = state->Lookup(object, field_index); | |
340 if (old_value == new_value) { | |
341 TRACE(" Node #%d:StoreField is fully redundant, storing #%d:%s\n", | |
342 node->id(), new_value->id(), new_value->op()->mnemonic()); | |
343 candidates_.insert(node); | |
344 } | |
345 TRACE(" Killing all potentially aliasing stores for %d on #%d:%s\n", | |
346 field_index, object->id(), object->op()->mnemonic()); | |
347 state = state->Kill(object, field_index, zone()); | |
348 TRACE(" Node #%d:StoreField provides #%d:%s for %d on #%d:%s\n", | |
349 node->id(), new_value->id(), new_value->op()->mnemonic(), | |
350 field_index, object->id(), object->op()->mnemonic()); | |
351 state = state->Extend(object, field_index, new_value, zone()); | |
352 } else { | |
353 TRACE(" Node #%d:StoreField is misaligned\n", node->id()); | |
354 state = empty_state(); | |
355 } | |
356 UpdateState(node, state); | |
357 } | |
358 | |
359 void VisitStoreElement(Node* node) { | |
360 Node* const effect = NodeProperties::GetEffectInput(node); | |
361 AbstractState const* state = GetState(effect); | |
362 UpdateState(node, state); | |
363 } | |
364 | |
365 void VisitOtherNode(Node* node) { | |
366 DCHECK_EQ(1, node->op()->EffectInputCount()); | |
367 DCHECK_EQ(1, node->op()->EffectOutputCount()); | |
368 Node* const effect = NodeProperties::GetEffectInput(node); | |
369 AbstractState const* state = node->op()->HasProperty(Operator::kNoWrite) | |
370 ? GetState(effect) | |
371 : empty_state(); | |
372 UpdateState(node, state); | |
373 } | |
374 | |
375 void UpdateState(Node* node, AbstractState const* new_state) { | |
376 AbstractState const* old_state = GetState(node); | |
377 if (old_state && old_state->Equals(new_state)) return; | |
378 SetState(node, new_state); | |
379 EnqueueUses(node); | |
380 } | |
381 | |
382 void EnqueueUses(Node* node) { | |
383 for (Edge const edge : node->use_edges()) { | |
384 if (NodeProperties::IsEffectEdge(edge)) { | |
385 queue_.push(edge.from()); | |
386 } | |
387 } | |
388 } | |
389 | |
390 AbstractState const* empty_state() const { return &empty_state_; } | |
391 Zone* zone() const { return node_states_.get_allocator().zone(); } | |
392 | |
393 ZoneSet<Node*> candidates_; | |
394 AbstractState const empty_state_; | |
395 ZoneStack<Node*> queue_; | |
396 ZoneVector<AbstractState const*> node_states_; | |
397 | |
398 DISALLOW_COPY_AND_ASSIGN(LoadEliminationAnalysis); | |
399 }; | |
400 | |
401 } // namespace | |
402 | |
403 void LoadElimination::Run() { | |
404 LoadEliminationAnalysis analysis(graph(), zone()); | |
405 analysis.Run(graph()->start()); | |
99 } | 406 } |
100 | 407 |
408 Graph* LoadElimination::graph() const { return jsgraph()->graph(); } | |
409 | |
101 } // namespace compiler | 410 } // namespace compiler |
102 } // namespace internal | 411 } // namespace internal |
103 } // namespace v8 | 412 } // namespace v8 |
OLD | NEW |