Chromium Code Reviews| 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 |