7 #include "src/compiler/store-store-elimination.h" 9 #include "src/compiler/all-nodes.h" 10 #include "src/compiler/js-graph.h" 11 #include "src/compiler/node-properties.h" 12 #include "src/compiler/simplified-operator.h" 18 #define TRACE(fmt, ...) \ 20 if (FLAG_trace_store_elimination) { \ 21 PrintF("RedundantStoreFinder: " fmt "\n", ##__VA_ARGS__); \ 30 #define CHECK_EXTRA(condition, fmt, ...) \ 32 if (V8_UNLIKELY(!(condition))) { \ 33 FATAL("Check failed: %s. Extra info: " fmt, #condition, ##__VA_ARGS__); \ 38 #define DCHECK_EXTRA(condition, fmt, ...) \ 39 CHECK_EXTRA(condition, fmt, ##__VA_ARGS__) 41 #define DCHECK_EXTRA(condition, fmt, ...) ((void)0) 76 struct UnobservableStore {
80 bool operator==(
const UnobservableStore)
const;
81 bool operator<(
const UnobservableStore)
const;
98 class UnobservablesSet final {
100 static UnobservablesSet Unvisited();
101 static UnobservablesSet VisitedEmpty(Zone* zone);
103 UnobservablesSet(
const UnobservablesSet& other) =
default;
105 UnobservablesSet Intersect(
const UnobservablesSet& other, Zone* zone)
const;
106 UnobservablesSet Add(UnobservableStore obs, Zone* zone)
const;
107 UnobservablesSet RemoveSameOffset(StoreOffset off, Zone* zone)
const;
109 const ZoneSet<UnobservableStore>*
set()
const {
return set_; }
111 bool IsUnvisited()
const {
return set_ ==
nullptr; }
112 bool IsEmpty()
const {
return set_ ==
nullptr || set_->empty(); }
113 bool Contains(UnobservableStore obs)
const {
114 return set_ !=
nullptr && (set_->find(obs) != set_->end());
117 bool operator==(
const UnobservablesSet&)
const;
118 bool operator!=(
const UnobservablesSet&)
const;
121 explicit UnobservablesSet(
const ZoneSet<UnobservableStore>*
set)
123 const ZoneSet<UnobservableStore>* set_;
130 class RedundantStoreFinder final {
132 RedundantStoreFinder(JSGraph* js_graph, Zone* temp_zone);
136 const ZoneSet<Node*>& to_remove_const() {
return to_remove_; }
138 void Visit(Node* node);
141 void VisitEffectfulNode(Node* node);
142 UnobservablesSet RecomputeUseIntersection(Node* node);
143 UnobservablesSet RecomputeSet(Node* node,
const UnobservablesSet& uses);
144 static bool CannotObserveStoreField(Node* node);
146 void MarkForRevisit(Node* node);
147 bool HasBeenVisited(Node* node);
149 JSGraph* jsgraph()
const {
return jsgraph_; }
150 Isolate* isolate() {
return jsgraph()->isolate(); }
151 Zone* temp_zone()
const {
return temp_zone_; }
152 ZoneVector<UnobservablesSet>& unobservable() {
return unobservable_; }
153 UnobservablesSet& unobservable_for_id(NodeId
id) {
154 DCHECK_LT(
id, unobservable().size());
155 return unobservable()[id];
157 ZoneSet<Node*>& to_remove() {
return to_remove_; }
159 JSGraph*
const jsgraph_;
160 Zone*
const temp_zone_;
162 ZoneStack<Node*> revisit_;
163 ZoneVector<bool> in_revisit_;
165 ZoneVector<UnobservablesSet> unobservable_;
166 ZoneSet<Node*> to_remove_;
167 const UnobservablesSet unobservables_visited_empty_;
172 StoreOffset ToOffset(
int offset) {
174 return static_cast<StoreOffset
>(offset);
177 StoreOffset ToOffset(
const FieldAccess& access) {
178 return ToOffset(access.offset);
181 unsigned int RepSizeOf(MachineRepresentation rep) {
182 return 1u << ElementSizeLog2Of(rep);
184 unsigned int RepSizeOf(FieldAccess access) {
185 return RepSizeOf(access.machine_type.representation());
188 bool AtMostTagged(FieldAccess access) {
189 return RepSizeOf(access) <= RepSizeOf(MachineRepresentation::kTagged);
192 bool AtLeastTagged(FieldAccess access) {
193 return RepSizeOf(access) >= RepSizeOf(MachineRepresentation::kTagged);
198 void RedundantStoreFinder::Find() {
199 Visit(jsgraph()->graph()->end());
201 while (!revisit_.empty()) {
202 Node* next = revisit_.top();
204 DCHECK_LT(next->id(), in_revisit_.size());
205 in_revisit_[next->id()] =
false;
211 AllNodes all(temp_zone(), jsgraph()->graph());
212 for (Node* node : all.reachable) {
213 if (node->op()->opcode() == IrOpcode::kStoreField) {
214 DCHECK_EXTRA(HasBeenVisited(node),
"#%d:%s", node->id(),
215 node->op()->mnemonic());
221 void RedundantStoreFinder::MarkForRevisit(Node* node) {
222 DCHECK_LT(node->id(), in_revisit_.size());
223 if (!in_revisit_[node->id()]) {
225 in_revisit_[node->id()] =
true;
229 bool RedundantStoreFinder::HasBeenVisited(Node* node) {
230 return !unobservable_for_id(node->id()).IsUnvisited();
233 void StoreStoreElimination::Run(JSGraph* js_graph, Zone* temp_zone) {
235 RedundantStoreFinder finder(js_graph, temp_zone);
240 for (Node* node : finder.to_remove_const()) {
241 if (FLAG_trace_store_elimination) {
242 PrintF(
"StoreStoreElimination::Run: Eliminating node #%d:%s\n",
243 node->id(), node->op()->mnemonic());
245 Node* previous_effect = NodeProperties::GetEffectInput(node);
246 NodeProperties::ReplaceUses(node,
nullptr, previous_effect,
nullptr,
255 UnobservablesSet RedundantStoreFinder::RecomputeSet(
256 Node* node,
const UnobservablesSet& uses) {
257 switch (node->op()->opcode()) {
258 case IrOpcode::kStoreField: {
259 Node* stored_to = node->InputAt(0);
260 const FieldAccess& access = FieldAccessOf(node->op());
261 StoreOffset offset = ToOffset(access);
263 UnobservableStore observation = {stored_to->id(), offset};
264 bool isNotObservable = uses.Contains(observation);
266 if (isNotObservable && AtMostTagged(access)) {
267 TRACE(
" #%d is StoreField[+%d,%s](#%d), unobservable", node->id(),
268 offset, MachineReprToString(access.machine_type.representation()),
270 to_remove().insert(node);
272 }
else if (isNotObservable && !AtMostTagged(access)) {
274 " #%d is StoreField[+%d,%s](#%d), repeated in future but too " 275 "big to optimize away",
277 MachineReprToString(access.machine_type.representation()),
280 }
else if (!isNotObservable && AtLeastTagged(access)) {
281 TRACE(
" #%d is StoreField[+%d,%s](#%d), observable, recording in set",
283 MachineReprToString(access.machine_type.representation()),
285 return uses.Add(observation, temp_zone());
286 }
else if (!isNotObservable && !AtLeastTagged(access)) {
288 " #%d is StoreField[+%d,%s](#%d), observable but too small to " 291 MachineReprToString(access.machine_type.representation()),
299 case IrOpcode::kLoadField: {
300 Node* loaded_from = node->InputAt(0);
301 const FieldAccess& access = FieldAccessOf(node->op());
302 StoreOffset offset = ToOffset(access);
305 " #%d is LoadField[+%d,%s](#%d), removing all offsets [+%d] from " 308 MachineReprToString(access.machine_type.representation()),
309 loaded_from->id(), offset);
311 return uses.RemoveSameOffset(offset, temp_zone());
315 if (CannotObserveStoreField(node)) {
316 TRACE(
" #%d:%s can observe nothing, set stays unchanged", node->id(),
317 node->op()->mnemonic());
320 TRACE(
" #%d:%s might observe anything, recording empty set",
321 node->id(), node->op()->mnemonic());
322 return unobservables_visited_empty_;
328 bool RedundantStoreFinder::CannotObserveStoreField(Node* node) {
329 return node->opcode() == IrOpcode::kLoadElement ||
330 node->opcode() == IrOpcode::kLoad ||
331 node->opcode() == IrOpcode::kStore ||
332 node->opcode() == IrOpcode::kEffectPhi ||
333 node->opcode() == IrOpcode::kStoreElement ||
334 node->opcode() == IrOpcode::kUnsafePointerAdd ||
335 node->opcode() == IrOpcode::kRetain;
339 RedundantStoreFinder::RedundantStoreFinder(JSGraph* js_graph, Zone* temp_zone)
340 : jsgraph_(js_graph),
341 temp_zone_(temp_zone),
343 in_revisit_(js_graph->graph()->NodeCount(), temp_zone),
344 unobservable_(js_graph->graph()->NodeCount(),
345 UnobservablesSet::Unvisited(), temp_zone),
346 to_remove_(temp_zone),
347 unobservables_visited_empty_(UnobservablesSet::VisitedEmpty(temp_zone)) {}
349 void RedundantStoreFinder::Visit(Node* node) {
355 if (!HasBeenVisited(node)) {
356 for (
int i = 0;
i < node->op()->ControlInputCount();
i++) {
357 Node* control_input = NodeProperties::GetControlInput(node,
i);
358 if (!HasBeenVisited(control_input)) {
359 MarkForRevisit(control_input);
364 bool isEffectful = (node->op()->EffectInputCount() >= 1);
366 VisitEffectfulNode(node);
367 DCHECK(HasBeenVisited(node));
370 if (!HasBeenVisited(node)) {
372 unobservable_for_id(node->id()) = unobservables_visited_empty_;
376 void RedundantStoreFinder::VisitEffectfulNode(Node* node) {
377 if (HasBeenVisited(node)) {
378 TRACE(
"- Revisiting: #%d:%s", node->id(), node->op()->mnemonic());
380 UnobservablesSet after_set = RecomputeUseIntersection(node);
381 UnobservablesSet before_set = RecomputeSet(node, after_set);
382 DCHECK(!before_set.IsUnvisited());
384 UnobservablesSet stored_for_node = unobservable_for_id(node->id());
385 bool cur_set_changed =
386 (stored_for_node.IsUnvisited() || stored_for_node != before_set);
387 if (!cur_set_changed) {
390 TRACE(
"+ No change: stabilized. Not visiting effect inputs.");
392 unobservable_for_id(node->id()) = before_set;
395 for (
int i = 0;
i < node->op()->EffectInputCount();
i++) {
396 Node* input = NodeProperties::GetEffectInput(node,
i);
397 TRACE(
" marking #%d:%s for revisit", input->id(),
398 input->op()->mnemonic());
399 MarkForRevisit(input);
408 UnobservablesSet RedundantStoreFinder::RecomputeUseIntersection(Node* node) {
414 UnobservablesSet cur_set = UnobservablesSet::Unvisited();
416 for (Edge edge : node->use_edges()) {
418 if (!NodeProperties::IsEffectEdge(edge)) {
422 Node* use = edge.from();
423 UnobservablesSet new_set = unobservable_for_id(use->id());
431 cur_set = cur_set.Intersect(new_set, temp_zone());
437 auto opcode = node->op()->opcode();
444 opcode == IrOpcode::kReturn || opcode == IrOpcode::kTerminate ||
445 opcode == IrOpcode::kDeoptimize || opcode == IrOpcode::kThrow,
446 "for #%d:%s", node->id(), node->op()->mnemonic());
449 return unobservables_visited_empty_;
451 if (cur_set.IsUnvisited()) {
452 cur_set = unobservables_visited_empty_;
459 UnobservablesSet UnobservablesSet::Unvisited() {
return UnobservablesSet(); }
461 UnobservablesSet::UnobservablesSet() : set_(nullptr) {}
463 UnobservablesSet UnobservablesSet::VisitedEmpty(Zone* zone) {
466 ZoneSet<UnobservableStore>* empty_set =
467 new (zone->New(
sizeof(ZoneSet<UnobservableStore>)))
468 ZoneSet<UnobservableStore>(zone);
469 return UnobservablesSet(empty_set);
475 UnobservablesSet UnobservablesSet::Intersect(
const UnobservablesSet& other,
477 if (IsEmpty() || other.IsEmpty()) {
480 ZoneSet<UnobservableStore>* intersection =
481 new (zone->New(
sizeof(ZoneSet<UnobservableStore>)))
482 ZoneSet<UnobservableStore>(zone);
484 set_intersection(
set()->begin(),
set()->end(), other.set()->begin(),
486 std::inserter(*intersection, intersection->end()));
488 return UnobservablesSet(intersection);
492 UnobservablesSet UnobservablesSet::Add(UnobservableStore obs,
494 bool present = (
set()->find(obs) !=
set()->end());
499 ZoneSet<UnobservableStore>* new_set =
500 new (zone->New(
sizeof(ZoneSet<UnobservableStore>)))
501 ZoneSet<UnobservableStore>(zone);
505 bool inserted = new_set->insert(obs).second;
509 return UnobservablesSet(new_set);
513 UnobservablesSet UnobservablesSet::RemoveSameOffset(StoreOffset offset,
516 ZoneSet<UnobservableStore>* new_set =
517 new (zone->New(
sizeof(ZoneSet<UnobservableStore>)))
518 ZoneSet<UnobservableStore>(zone);
520 for (
auto obs : *
set()) {
521 if (obs.offset_ != offset) {
522 new_set->insert(obs);
526 return UnobservablesSet(new_set);
530 bool UnobservablesSet::operator==(
const UnobservablesSet& other)
const {
531 if (IsUnvisited() || other.IsUnvisited()) {
532 return IsEmpty() && other.IsEmpty();
535 return *
set() == *other.set();
539 bool UnobservablesSet::operator!=(
const UnobservablesSet& other)
const {
540 return !(*
this == other);
543 bool UnobservableStore::operator==(
const UnobservableStore other)
const {
544 return (id_ == other.id_) && (offset_ == other.offset_);
548 bool UnobservableStore::operator<(
const UnobservableStore other)
const {
549 return (id_ < other.id_) || (id_ == other.id_ && offset_ < other.offset_);