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
3 // found in the LICENSE file.
4
5 #include "src/compiler/redundancy-elimination.h"
6
7 #include "src/compiler/node-properties.h"
8 #include "src/compiler/simplified-operator.h"
9
10 namespace v8 {
11 namespace internal {
12 namespace compiler {
13
RedundancyElimination(Editor * editor,Zone * zone)14 RedundancyElimination::RedundancyElimination(Editor* editor, Zone* zone)
15 : AdvancedReducer(editor), node_checks_(zone), zone_(zone) {}
16
17 RedundancyElimination::~RedundancyElimination() = default;
18
Reduce(Node * node)19 Reduction RedundancyElimination::Reduce(Node* node) {
20 if (node_checks_.Get(node)) return NoChange();
21 switch (node->opcode()) {
22 case IrOpcode::kCheckBigInt:
23 case IrOpcode::kCheckBounds:
24 case IrOpcode::kCheckClosure:
25 case IrOpcode::kCheckEqualsInternalizedString:
26 case IrOpcode::kCheckEqualsSymbol:
27 case IrOpcode::kCheckFloat64Hole:
28 case IrOpcode::kCheckHeapObject:
29 case IrOpcode::kCheckIf:
30 case IrOpcode::kCheckInternalizedString:
31 case IrOpcode::kCheckNotTaggedHole:
32 case IrOpcode::kCheckNumber:
33 case IrOpcode::kCheckReceiver:
34 case IrOpcode::kCheckReceiverOrNullOrUndefined:
35 case IrOpcode::kCheckSmi:
36 case IrOpcode::kCheckString:
37 case IrOpcode::kCheckSymbol:
38 #define SIMPLIFIED_CHECKED_OP(Opcode) case IrOpcode::k##Opcode:
39 SIMPLIFIED_CHECKED_OP_LIST(SIMPLIFIED_CHECKED_OP)
40 #undef SIMPLIFIED_CHECKED_OP
41 return ReduceCheckNode(node);
42 case IrOpcode::kSpeculativeNumberEqual:
43 case IrOpcode::kSpeculativeNumberLessThan:
44 case IrOpcode::kSpeculativeNumberLessThanOrEqual:
45 return ReduceSpeculativeNumberComparison(node);
46 case IrOpcode::kSpeculativeNumberAdd:
47 case IrOpcode::kSpeculativeNumberSubtract:
48 case IrOpcode::kSpeculativeSafeIntegerAdd:
49 case IrOpcode::kSpeculativeSafeIntegerSubtract:
50 case IrOpcode::kSpeculativeToNumber:
51 return ReduceSpeculativeNumberOperation(node);
52 case IrOpcode::kEffectPhi:
53 return ReduceEffectPhi(node);
54 case IrOpcode::kDead:
55 break;
56 case IrOpcode::kStart:
57 return ReduceStart(node);
58 default:
59 return ReduceOtherNode(node);
60 }
61 return NoChange();
62 }
63
64 // static
65 RedundancyElimination::EffectPathChecks*
Copy(Zone * zone,EffectPathChecks const * checks)66 RedundancyElimination::EffectPathChecks::Copy(Zone* zone,
67 EffectPathChecks const* checks) {
68 return zone->New<EffectPathChecks>(*checks);
69 }
70
71 // static
72 RedundancyElimination::EffectPathChecks const*
Empty(Zone * zone)73 RedundancyElimination::EffectPathChecks::Empty(Zone* zone) {
74 return zone->New<EffectPathChecks>(nullptr, 0);
75 }
76
Equals(EffectPathChecks const * that) const77 bool RedundancyElimination::EffectPathChecks::Equals(
78 EffectPathChecks const* that) const {
79 if (this->size_ != that->size_) return false;
80 Check* this_head = this->head_;
81 Check* that_head = that->head_;
82 while (this_head != that_head) {
83 if (this_head->node != that_head->node) return false;
84 this_head = this_head->next;
85 that_head = that_head->next;
86 }
87 return true;
88 }
89
Merge(EffectPathChecks const * that)90 void RedundancyElimination::EffectPathChecks::Merge(
91 EffectPathChecks const* that) {
92 // Change the current check list to a longest common tail of this check
93 // list and the other list.
94
95 // First, we throw away the prefix of the longer list, so that
96 // we have lists of the same length.
97 Check* that_head = that->head_;
98 size_t that_size = that->size_;
99 while (that_size > size_) {
100 that_head = that_head->next;
101 that_size--;
102 }
103 while (size_ > that_size) {
104 head_ = head_->next;
105 size_--;
106 }
107
108 // Then we go through both lists in lock-step until we find
109 // the common tail.
110 while (head_ != that_head) {
111 DCHECK_LT(0u, size_);
112 DCHECK_NOT_NULL(head_);
113 size_--;
114 head_ = head_->next;
115 that_head = that_head->next;
116 }
117 }
118
119 RedundancyElimination::EffectPathChecks const*
AddCheck(Zone * zone,Node * node) const120 RedundancyElimination::EffectPathChecks::AddCheck(Zone* zone,
121 Node* node) const {
122 Check* head = zone->New<Check>(node, head_);
123 return zone->New<EffectPathChecks>(head, size_ + 1);
124 }
125
126 namespace {
127
128 // Does check {a} subsume check {b}?
CheckSubsumes(Node const * a,Node const * b)129 bool CheckSubsumes(Node const* a, Node const* b) {
130 if (a->op() != b->op()) {
131 if (a->opcode() == IrOpcode::kCheckInternalizedString &&
132 b->opcode() == IrOpcode::kCheckString) {
133 // CheckInternalizedString(node) implies CheckString(node)
134 } else if (a->opcode() == IrOpcode::kCheckSmi &&
135 b->opcode() == IrOpcode::kCheckNumber) {
136 // CheckSmi(node) implies CheckNumber(node)
137 } else if (a->opcode() == IrOpcode::kCheckedTaggedSignedToInt32 &&
138 b->opcode() == IrOpcode::kCheckedTaggedToInt32) {
139 // CheckedTaggedSignedToInt32(node) implies CheckedTaggedToInt32(node)
140 } else if (a->opcode() == IrOpcode::kCheckedTaggedSignedToInt32 &&
141 b->opcode() == IrOpcode::kCheckedTaggedToArrayIndex) {
142 // CheckedTaggedSignedToInt32(node) implies
143 // CheckedTaggedToArrayIndex(node)
144 } else if (a->opcode() == IrOpcode::kCheckedTaggedToInt32 &&
145 b->opcode() == IrOpcode::kCheckedTaggedToArrayIndex) {
146 // CheckedTaggedToInt32(node) implies CheckedTaggedToArrayIndex(node)
147 } else if (a->opcode() == IrOpcode::kCheckReceiver &&
148 b->opcode() == IrOpcode::kCheckReceiverOrNullOrUndefined) {
149 // CheckReceiver(node) implies CheckReceiverOrNullOrUndefined(node)
150 } else if (a->opcode() != b->opcode()) {
151 return false;
152 } else {
153 switch (a->opcode()) {
154 case IrOpcode::kCheckBounds:
155 case IrOpcode::kCheckSmi:
156 case IrOpcode::kCheckString:
157 case IrOpcode::kCheckNumber:
158 case IrOpcode::kCheckBigInt:
159 break;
160 case IrOpcode::kCheckedInt32ToTaggedSigned:
161 case IrOpcode::kCheckedInt64ToInt32:
162 case IrOpcode::kCheckedInt64ToTaggedSigned:
163 case IrOpcode::kCheckedTaggedSignedToInt32:
164 case IrOpcode::kCheckedTaggedToTaggedPointer:
165 case IrOpcode::kCheckedTaggedToTaggedSigned:
166 case IrOpcode::kCheckedTaggedToArrayIndex:
167 case IrOpcode::kCheckedUint32Bounds:
168 case IrOpcode::kCheckedUint32ToInt32:
169 case IrOpcode::kCheckedUint32ToTaggedSigned:
170 case IrOpcode::kCheckedUint64Bounds:
171 case IrOpcode::kCheckedUint64ToInt32:
172 case IrOpcode::kCheckedUint64ToTaggedSigned:
173 break;
174 case IrOpcode::kCheckedFloat64ToInt32:
175 case IrOpcode::kCheckedFloat64ToInt64:
176 case IrOpcode::kCheckedTaggedToInt32:
177 case IrOpcode::kCheckedTaggedToInt64: {
178 const CheckMinusZeroParameters& ap =
179 CheckMinusZeroParametersOf(a->op());
180 const CheckMinusZeroParameters& bp =
181 CheckMinusZeroParametersOf(b->op());
182 if (ap.mode() != bp.mode()) {
183 return false;
184 }
185 break;
186 }
187 case IrOpcode::kCheckedTaggedToFloat64:
188 case IrOpcode::kCheckedTruncateTaggedToWord32: {
189 CheckTaggedInputParameters const& ap =
190 CheckTaggedInputParametersOf(a->op());
191 CheckTaggedInputParameters const& bp =
192 CheckTaggedInputParametersOf(b->op());
193 // {a} subsumes {b} if the modes are either the same, or {a} checks
194 // for Number, in which case {b} will be subsumed no matter what.
195 if (ap.mode() != bp.mode() &&
196 ap.mode() != CheckTaggedInputMode::kNumber) {
197 return false;
198 }
199 break;
200 }
201 default:
202 DCHECK(!IsCheckedWithFeedback(a->op()));
203 return false;
204 }
205 }
206 }
207 for (int i = a->op()->ValueInputCount(); --i >= 0;) {
208 if (a->InputAt(i) != b->InputAt(i)) return false;
209 }
210 return true;
211 }
212
TypeSubsumes(Node * node,Node * replacement)213 bool TypeSubsumes(Node* node, Node* replacement) {
214 if (!NodeProperties::IsTyped(node) || !NodeProperties::IsTyped(replacement)) {
215 // If either node is untyped, we are running during an untyped optimization
216 // phase, and replacement is OK.
217 return true;
218 }
219 Type node_type = NodeProperties::GetType(node);
220 Type replacement_type = NodeProperties::GetType(replacement);
221 return replacement_type.Is(node_type);
222 }
223
224 } // namespace
225
LookupCheck(Node * node) const226 Node* RedundancyElimination::EffectPathChecks::LookupCheck(Node* node) const {
227 for (Check const* check = head_; check != nullptr; check = check->next) {
228 if (CheckSubsumes(check->node, node) && TypeSubsumes(node, check->node)) {
229 DCHECK(!check->node->IsDead());
230 return check->node;
231 }
232 }
233 return nullptr;
234 }
235
LookupBoundsCheckFor(Node * node) const236 Node* RedundancyElimination::EffectPathChecks::LookupBoundsCheckFor(
237 Node* node) const {
238 for (Check const* check = head_; check != nullptr; check = check->next) {
239 if (check->node->opcode() == IrOpcode::kCheckBounds &&
240 check->node->InputAt(0) == node && TypeSubsumes(node, check->node) &&
241 !(CheckBoundsParametersOf(check->node->op()).flags() &
242 CheckBoundsFlag::kConvertStringAndMinusZero)) {
243 return check->node;
244 }
245 }
246 return nullptr;
247 }
248
249 RedundancyElimination::EffectPathChecks const*
Get(Node * node) const250 RedundancyElimination::PathChecksForEffectNodes::Get(Node* node) const {
251 size_t const id = node->id();
252 if (id < info_for_node_.size()) return info_for_node_[id];
253 return nullptr;
254 }
255
Set(Node * node,EffectPathChecks const * checks)256 void RedundancyElimination::PathChecksForEffectNodes::Set(
257 Node* node, EffectPathChecks const* checks) {
258 size_t const id = node->id();
259 if (id >= info_for_node_.size()) info_for_node_.resize(id + 1, nullptr);
260 info_for_node_[id] = checks;
261 }
262
ReduceCheckNode(Node * node)263 Reduction RedundancyElimination::ReduceCheckNode(Node* node) {
264 Node* const effect = NodeProperties::GetEffectInput(node);
265 EffectPathChecks const* checks = node_checks_.Get(effect);
266 // If we do not know anything about the predecessor, do not propagate just yet
267 // because we will have to recompute anyway once we compute the predecessor.
268 if (checks == nullptr) return NoChange();
269 // See if we have another check that dominates us.
270 if (Node* check = checks->LookupCheck(node)) {
271 ReplaceWithValue(node, check);
272 return Replace(check);
273 }
274
275 // Learn from this check.
276 return UpdateChecks(node, checks->AddCheck(zone(), node));
277 }
278
ReduceEffectPhi(Node * node)279 Reduction RedundancyElimination::ReduceEffectPhi(Node* node) {
280 Node* const control = NodeProperties::GetControlInput(node);
281 if (control->opcode() == IrOpcode::kLoop) {
282 // Here we rely on having only reducible loops:
283 // The loop entry edge always dominates the header, so we can just use
284 // the information from the loop entry edge.
285 return TakeChecksFromFirstEffect(node);
286 }
287 DCHECK_EQ(IrOpcode::kMerge, control->opcode());
288
289 // Shortcut for the case when we do not know anything about some input.
290 int const input_count = node->op()->EffectInputCount();
291 for (int i = 0; i < input_count; ++i) {
292 Node* const effect = NodeProperties::GetEffectInput(node, i);
293 if (node_checks_.Get(effect) == nullptr) return NoChange();
294 }
295
296 // Make a copy of the first input's checks and merge with the checks
297 // from other inputs.
298 EffectPathChecks* checks = EffectPathChecks::Copy(
299 zone(), node_checks_.Get(NodeProperties::GetEffectInput(node, 0)));
300 for (int i = 1; i < input_count; ++i) {
301 Node* const input = NodeProperties::GetEffectInput(node, i);
302 checks->Merge(node_checks_.Get(input));
303 }
304 return UpdateChecks(node, checks);
305 }
306
ReduceSpeculativeNumberComparison(Node * node)307 Reduction RedundancyElimination::ReduceSpeculativeNumberComparison(Node* node) {
308 NumberOperationHint const hint = NumberOperationHintOf(node->op());
309 Node* const first = NodeProperties::GetValueInput(node, 0);
310 Type const first_type = NodeProperties::GetType(first);
311 Node* const second = NodeProperties::GetValueInput(node, 1);
312 Type const second_type = NodeProperties::GetType(second);
313 Node* const effect = NodeProperties::GetEffectInput(node);
314 EffectPathChecks const* checks = node_checks_.Get(effect);
315
316 // If we do not know anything about the predecessor, do not propagate just yet
317 // because we will have to recompute anyway once we compute the predecessor.
318 if (checks == nullptr) return NoChange();
319
320 // Avoid the potentially expensive lookups below if the {node}
321 // has seen non-Smi inputs in the past, which is a clear signal
322 // that the comparison is probably not performed on a value that
323 // already passed an array bounds check.
324 if (hint == NumberOperationHint::kSignedSmall) {
325 // Don't bother trying to find a CheckBounds for the {first} input
326 // if it's type is already in UnsignedSmall range, since the bounds
327 // check is only going to narrow that range further, but the result
328 // is not going to make the representation selection any better.
329 if (!first_type.Is(Type::UnsignedSmall())) {
330 if (Node* check = checks->LookupBoundsCheckFor(first)) {
331 if (!first_type.Is(NodeProperties::GetType(check))) {
332 // Replace the {first} input with the {check}. This is safe,
333 // despite the fact that {check} can truncate -0 to 0, because
334 // the regular Number comparisons in JavaScript also identify
335 // 0 and -0 (unlike special comparisons as Object.is).
336 NodeProperties::ReplaceValueInput(node, check, 0);
337 return Changed(node).FollowedBy(
338 ReduceSpeculativeNumberComparison(node));
339 }
340 }
341 }
342
343 // Don't bother trying to find a CheckBounds for the {second} input
344 // if it's type is already in UnsignedSmall range, since the bounds
345 // check is only going to narrow that range further, but the result
346 // is not going to make the representation selection any better.
347 if (!second_type.Is(Type::UnsignedSmall())) {
348 if (Node* check = checks->LookupBoundsCheckFor(second)) {
349 if (!second_type.Is(NodeProperties::GetType(check))) {
350 // Replace the {second} input with the {check}. This is safe,
351 // despite the fact that {check} can truncate -0 to 0, because
352 // the regular Number comparisons in JavaScript also identify
353 // 0 and -0 (unlike special comparisons as Object.is).
354 NodeProperties::ReplaceValueInput(node, check, 1);
355 return Changed(node).FollowedBy(
356 ReduceSpeculativeNumberComparison(node));
357 }
358 }
359 }
360 }
361
362 return UpdateChecks(node, checks);
363 }
364
ReduceSpeculativeNumberOperation(Node * node)365 Reduction RedundancyElimination::ReduceSpeculativeNumberOperation(Node* node) {
366 DCHECK(node->opcode() == IrOpcode::kSpeculativeNumberAdd ||
367 node->opcode() == IrOpcode::kSpeculativeNumberSubtract ||
368 node->opcode() == IrOpcode::kSpeculativeSafeIntegerAdd ||
369 node->opcode() == IrOpcode::kSpeculativeSafeIntegerSubtract ||
370 node->opcode() == IrOpcode::kSpeculativeToNumber);
371 DCHECK_EQ(1, node->op()->EffectInputCount());
372 DCHECK_EQ(1, node->op()->EffectOutputCount());
373
374 Node* const first = NodeProperties::GetValueInput(node, 0);
375 Node* const effect = NodeProperties::GetEffectInput(node);
376 EffectPathChecks const* checks = node_checks_.Get(effect);
377 // If we do not know anything about the predecessor, do not propagate just yet
378 // because we will have to recompute anyway once we compute the predecessor.
379 if (checks == nullptr) return NoChange();
380
381 // Check if there's a CheckBounds operation on {first}
382 // in the graph already, which we might be able to
383 // reuse here to improve the representation selection
384 // for the {node} later on.
385 if (Node* check = checks->LookupBoundsCheckFor(first)) {
386 // Only use the bounds {check} if its type is better
387 // than the type of the {first} node, otherwise we
388 // would end up replacing NumberConstant inputs with
389 // CheckBounds operations, which is kind of pointless.
390 if (!NodeProperties::GetType(first).Is(NodeProperties::GetType(check))) {
391 NodeProperties::ReplaceValueInput(node, check, 0);
392 }
393 }
394
395 return UpdateChecks(node, checks);
396 }
397
ReduceStart(Node * node)398 Reduction RedundancyElimination::ReduceStart(Node* node) {
399 return UpdateChecks(node, EffectPathChecks::Empty(zone()));
400 }
401
ReduceOtherNode(Node * node)402 Reduction RedundancyElimination::ReduceOtherNode(Node* node) {
403 if (node->op()->EffectInputCount() == 1) {
404 if (node->op()->EffectOutputCount() == 1) {
405 return TakeChecksFromFirstEffect(node);
406 } else {
407 // Effect terminators should be handled specially.
408 return NoChange();
409 }
410 }
411 DCHECK_EQ(0, node->op()->EffectInputCount());
412 DCHECK_EQ(0, node->op()->EffectOutputCount());
413 return NoChange();
414 }
415
TakeChecksFromFirstEffect(Node * node)416 Reduction RedundancyElimination::TakeChecksFromFirstEffect(Node* node) {
417 DCHECK_EQ(1, node->op()->EffectOutputCount());
418 Node* const effect = NodeProperties::GetEffectInput(node);
419 EffectPathChecks const* checks = node_checks_.Get(effect);
420 // If we do not know anything about the predecessor, do not propagate just yet
421 // because we will have to recompute anyway once we compute the predecessor.
422 if (checks == nullptr) return NoChange();
423 // We just propagate the information from the effect input (ideally,
424 // we would only revisit effect uses if there is change).
425 return UpdateChecks(node, checks);
426 }
427
UpdateChecks(Node * node,EffectPathChecks const * checks)428 Reduction RedundancyElimination::UpdateChecks(Node* node,
429 EffectPathChecks const* checks) {
430 EffectPathChecks const* original = node_checks_.Get(node);
431 // Only signal that the {node} has Changed, if the information about {checks}
432 // has changed wrt. the {original}.
433 if (checks != original) {
434 if (original == nullptr || !checks->Equals(original)) {
435 node_checks_.Set(node, checks);
436 return Changed(node);
437 }
438 }
439 return NoChange();
440 }
441
442 } // namespace compiler
443 } // namespace internal
444 } // namespace v8
445