1 // Copyright 2014 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/common-operator-reducer.h"
6
7 #include <algorithm>
8
9 #include "src/compiler/common-operator.h"
10 #include "src/compiler/graph.h"
11 #include "src/compiler/js-heap-broker.h"
12 #include "src/compiler/machine-operator.h"
13 #include "src/compiler/node-matchers.h"
14 #include "src/compiler/node-properties.h"
15 #include "src/compiler/node.h"
16 #include "src/compiler/opcodes.h"
17
18 namespace v8 {
19 namespace internal {
20 namespace compiler {
21
CommonOperatorReducer(Editor * editor,Graph * graph,JSHeapBroker * broker,CommonOperatorBuilder * common,MachineOperatorBuilder * machine,Zone * temp_zone,BranchSemantics branch_semantics)22 CommonOperatorReducer::CommonOperatorReducer(Editor* editor, Graph* graph,
23 JSHeapBroker* broker,
24 CommonOperatorBuilder* common,
25 MachineOperatorBuilder* machine,
26 Zone* temp_zone,
27 BranchSemantics branch_semantics)
28 : AdvancedReducer(editor),
29 graph_(graph),
30 broker_(broker),
31 common_(common),
32 machine_(machine),
33 dead_(graph->NewNode(common->Dead())),
34 zone_(temp_zone),
35 branch_semantics_(branch_semantics) {
36 NodeProperties::SetType(dead_, Type::None());
37 }
38
Reduce(Node * node)39 Reduction CommonOperatorReducer::Reduce(Node* node) {
40 DisallowHeapAccessIf no_heap_access(broker() == nullptr);
41 switch (node->opcode()) {
42 case IrOpcode::kBranch:
43 return ReduceBranch(node);
44 case IrOpcode::kDeoptimizeIf:
45 case IrOpcode::kDeoptimizeUnless:
46 return ReduceDeoptimizeConditional(node);
47 case IrOpcode::kMerge:
48 return ReduceMerge(node);
49 case IrOpcode::kEffectPhi:
50 return ReduceEffectPhi(node);
51 case IrOpcode::kPhi:
52 return ReducePhi(node);
53 case IrOpcode::kReturn:
54 return ReduceReturn(node);
55 case IrOpcode::kSelect:
56 return ReduceSelect(node);
57 case IrOpcode::kSwitch:
58 return ReduceSwitch(node);
59 case IrOpcode::kStaticAssert:
60 return ReduceStaticAssert(node);
61 case IrOpcode::kTrapIf:
62 case IrOpcode::kTrapUnless:
63 return ReduceTrapConditional(node);
64 default:
65 break;
66 }
67 return NoChange();
68 }
69
DecideCondition(Node * const cond)70 Decision CommonOperatorReducer::DecideCondition(Node* const cond) {
71 Node* unwrapped = SkipValueIdentities(cond);
72 switch (unwrapped->opcode()) {
73 case IrOpcode::kInt32Constant: {
74 DCHECK_EQ(branch_semantics_, BranchSemantics::kMachine);
75 Int32Matcher m(unwrapped);
76 return m.ResolvedValue() ? Decision::kTrue : Decision::kFalse;
77 }
78 case IrOpcode::kHeapConstant: {
79 if (branch_semantics_ == BranchSemantics::kMachine) {
80 return Decision::kTrue;
81 }
82 HeapObjectMatcher m(unwrapped);
83 base::Optional<bool> maybe_result = m.Ref(broker_).TryGetBooleanValue();
84 if (!maybe_result.has_value()) return Decision::kUnknown;
85 return *maybe_result ? Decision::kTrue : Decision::kFalse;
86 }
87 default:
88 return Decision::kUnknown;
89 }
90 }
91
ReduceBranch(Node * node)92 Reduction CommonOperatorReducer::ReduceBranch(Node* node) {
93 DCHECK_EQ(IrOpcode::kBranch, node->opcode());
94 Node* const cond = node->InputAt(0);
95 // Swap IfTrue/IfFalse on {branch} if {cond} is a BooleanNot and use the input
96 // to BooleanNot as new condition for {branch}. Note we assume that {cond} was
97 // already properly optimized before we get here (as guaranteed by the graph
98 // reduction logic). The same applies if {cond} is a Select acting as boolean
99 // not (i.e. true being returned in the false case and vice versa).
100 if (cond->opcode() == IrOpcode::kBooleanNot ||
101 (cond->opcode() == IrOpcode::kSelect &&
102 DecideCondition(cond->InputAt(1)) == Decision::kFalse &&
103 DecideCondition(cond->InputAt(2)) == Decision::kTrue)) {
104 for (Node* const use : node->uses()) {
105 switch (use->opcode()) {
106 case IrOpcode::kIfTrue:
107 NodeProperties::ChangeOp(use, common()->IfFalse());
108 break;
109 case IrOpcode::kIfFalse:
110 NodeProperties::ChangeOp(use, common()->IfTrue());
111 break;
112 default:
113 UNREACHABLE();
114 }
115 }
116 // Update the condition of {branch}. No need to mark the uses for revisit,
117 // since we tell the graph reducer that the {branch} was changed and the
118 // graph reduction logic will ensure that the uses are revisited properly.
119 node->ReplaceInput(0, cond->InputAt(0));
120 // Negate the hint for {branch}.
121 NodeProperties::ChangeOp(
122 node, common()->Branch(NegateBranchHint(BranchHintOf(node->op()))));
123 return Changed(node);
124 }
125 Decision const decision = DecideCondition(cond);
126 if (decision == Decision::kUnknown) return NoChange();
127 Node* const control = node->InputAt(1);
128 for (Node* const use : node->uses()) {
129 switch (use->opcode()) {
130 case IrOpcode::kIfTrue:
131 Replace(use, (decision == Decision::kTrue) ? control : dead());
132 break;
133 case IrOpcode::kIfFalse:
134 Replace(use, (decision == Decision::kFalse) ? control : dead());
135 break;
136 default:
137 UNREACHABLE();
138 }
139 }
140 return Replace(dead());
141 }
142
ReduceDeoptimizeConditional(Node * node)143 Reduction CommonOperatorReducer::ReduceDeoptimizeConditional(Node* node) {
144 DCHECK(node->opcode() == IrOpcode::kDeoptimizeIf ||
145 node->opcode() == IrOpcode::kDeoptimizeUnless);
146 bool condition_is_true = node->opcode() == IrOpcode::kDeoptimizeUnless;
147 DeoptimizeParameters p = DeoptimizeParametersOf(node->op());
148 Node* condition = NodeProperties::GetValueInput(node, 0);
149 Node* frame_state = NodeProperties::GetValueInput(node, 1);
150 Node* effect = NodeProperties::GetEffectInput(node);
151 Node* control = NodeProperties::GetControlInput(node);
152 // Swap DeoptimizeIf/DeoptimizeUnless on {node} if {cond} is a BooleaNot
153 // and use the input to BooleanNot as new condition for {node}. Note we
154 // assume that {cond} was already properly optimized before we get here
155 // (as guaranteed by the graph reduction logic).
156 if (condition->opcode() == IrOpcode::kBooleanNot) {
157 NodeProperties::ReplaceValueInput(node, condition->InputAt(0), 0);
158 NodeProperties::ChangeOp(
159 node, condition_is_true
160 ? common()->DeoptimizeIf(p.reason(), p.feedback())
161 : common()->DeoptimizeUnless(p.reason(), p.feedback()));
162 return Changed(node);
163 }
164 Decision const decision = DecideCondition(condition);
165 if (decision == Decision::kUnknown) return NoChange();
166 if (condition_is_true == (decision == Decision::kTrue)) {
167 ReplaceWithValue(node, dead(), effect, control);
168 } else {
169 control = graph()->NewNode(common()->Deoptimize(p.reason(), p.feedback()),
170 frame_state, effect, control);
171 // TODO(bmeurer): This should be on the AdvancedReducer somehow.
172 NodeProperties::MergeControlToEnd(graph(), common(), control);
173 Revisit(graph()->end());
174 }
175 return Replace(dead());
176 }
177
ReduceMerge(Node * node)178 Reduction CommonOperatorReducer::ReduceMerge(Node* node) {
179 DCHECK_EQ(IrOpcode::kMerge, node->opcode());
180 //
181 // Check if this is a merge that belongs to an unused diamond, which means
182 // that:
183 //
184 // a) the {Merge} has no {Phi} or {EffectPhi} uses, and
185 // b) the {Merge} has two inputs, one {IfTrue} and one {IfFalse}, which are
186 // both owned by the Merge, and
187 // c) and the {IfTrue} and {IfFalse} nodes point to the same {Branch}.
188 //
189 if (node->InputCount() == 2) {
190 for (Node* const use : node->uses()) {
191 if (IrOpcode::IsPhiOpcode(use->opcode())) return NoChange();
192 }
193 Node* if_true = node->InputAt(0);
194 Node* if_false = node->InputAt(1);
195 if (if_true->opcode() != IrOpcode::kIfTrue) std::swap(if_true, if_false);
196 if (if_true->opcode() == IrOpcode::kIfTrue &&
197 if_false->opcode() == IrOpcode::kIfFalse &&
198 if_true->InputAt(0) == if_false->InputAt(0) && if_true->OwnedBy(node) &&
199 if_false->OwnedBy(node)) {
200 Node* const branch = if_true->InputAt(0);
201 DCHECK_EQ(IrOpcode::kBranch, branch->opcode());
202 DCHECK(branch->OwnedBy(if_true, if_false));
203 Node* const control = branch->InputAt(1);
204 // Mark the {branch} as {Dead}.
205 branch->TrimInputCount(0);
206 NodeProperties::ChangeOp(branch, common()->Dead());
207 return Replace(control);
208 }
209 }
210 return NoChange();
211 }
212
213
ReduceEffectPhi(Node * node)214 Reduction CommonOperatorReducer::ReduceEffectPhi(Node* node) {
215 DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
216 Node::Inputs inputs = node->inputs();
217 int const effect_input_count = inputs.count() - 1;
218 DCHECK_LE(1, effect_input_count);
219 Node* const merge = inputs[effect_input_count];
220 DCHECK(IrOpcode::IsMergeOpcode(merge->opcode()));
221 DCHECK_EQ(effect_input_count, merge->InputCount());
222 Node* const effect = inputs[0];
223 DCHECK_NE(node, effect);
224 for (int i = 1; i < effect_input_count; ++i) {
225 Node* const input = inputs[i];
226 if (input == node) {
227 // Ignore redundant inputs.
228 DCHECK_EQ(IrOpcode::kLoop, merge->opcode());
229 continue;
230 }
231 if (input != effect) return NoChange();
232 }
233 // We might now be able to further reduce the {merge} node.
234 Revisit(merge);
235 return Replace(effect);
236 }
237
238
ReducePhi(Node * node)239 Reduction CommonOperatorReducer::ReducePhi(Node* node) {
240 DCHECK_EQ(IrOpcode::kPhi, node->opcode());
241 Node::Inputs inputs = node->inputs();
242 int const value_input_count = inputs.count() - 1;
243 DCHECK_LE(1, value_input_count);
244 Node* const merge = inputs[value_input_count];
245 DCHECK(IrOpcode::IsMergeOpcode(merge->opcode()));
246 DCHECK_EQ(value_input_count, merge->InputCount());
247 if (value_input_count == 2) {
248 Node* vtrue = inputs[0];
249 Node* vfalse = inputs[1];
250 Node::Inputs merge_inputs = merge->inputs();
251 Node* if_true = merge_inputs[0];
252 Node* if_false = merge_inputs[1];
253 if (if_true->opcode() != IrOpcode::kIfTrue) {
254 std::swap(if_true, if_false);
255 std::swap(vtrue, vfalse);
256 }
257 if (if_true->opcode() == IrOpcode::kIfTrue &&
258 if_false->opcode() == IrOpcode::kIfFalse &&
259 if_true->InputAt(0) == if_false->InputAt(0)) {
260 Node* const branch = if_true->InputAt(0);
261 // Check that the branch is not dead already.
262 if (branch->opcode() != IrOpcode::kBranch) return NoChange();
263 Node* const cond = branch->InputAt(0);
264 if (cond->opcode() == IrOpcode::kFloat32LessThan) {
265 Float32BinopMatcher mcond(cond);
266 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
267 vfalse->opcode() == IrOpcode::kFloat32Sub) {
268 Float32BinopMatcher mvfalse(vfalse);
269 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
270 // We might now be able to further reduce the {merge} node.
271 Revisit(merge);
272 return Change(node, machine()->Float32Abs(), vtrue);
273 }
274 }
275 } else if (cond->opcode() == IrOpcode::kFloat64LessThan) {
276 Float64BinopMatcher mcond(cond);
277 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
278 vfalse->opcode() == IrOpcode::kFloat64Sub) {
279 Float64BinopMatcher mvfalse(vfalse);
280 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
281 // We might now be able to further reduce the {merge} node.
282 Revisit(merge);
283 return Change(node, machine()->Float64Abs(), vtrue);
284 }
285 }
286 }
287 }
288 }
289 Node* const value = inputs[0];
290 DCHECK_NE(node, value);
291 for (int i = 1; i < value_input_count; ++i) {
292 Node* const input = inputs[i];
293 if (input == node) {
294 // Ignore redundant inputs.
295 DCHECK_EQ(IrOpcode::kLoop, merge->opcode());
296 continue;
297 }
298 if (input != value) return NoChange();
299 }
300 // We might now be able to further reduce the {merge} node.
301 Revisit(merge);
302 return Replace(value);
303 }
304
ReduceReturn(Node * node)305 Reduction CommonOperatorReducer::ReduceReturn(Node* node) {
306 DCHECK_EQ(IrOpcode::kReturn, node->opcode());
307 Node* effect = NodeProperties::GetEffectInput(node);
308 if (effect->opcode() == IrOpcode::kCheckpoint) {
309 // Any {Return} node can never be used to insert a deoptimization point,
310 // hence checkpoints can be cut out of the effect chain flowing into it.
311 effect = NodeProperties::GetEffectInput(effect);
312 NodeProperties::ReplaceEffectInput(node, effect);
313 return Changed(node).FollowedBy(ReduceReturn(node));
314 }
315 // TODO(ahaas): Extend the reduction below to multiple return values.
316 if (ValueInputCountOfReturn(node->op()) != 1) {
317 return NoChange();
318 }
319 Node* pop_count = NodeProperties::GetValueInput(node, 0);
320 Node* value = NodeProperties::GetValueInput(node, 1);
321 Node* control = NodeProperties::GetControlInput(node);
322 if (value->opcode() == IrOpcode::kPhi &&
323 NodeProperties::GetControlInput(value) == control &&
324 control->opcode() == IrOpcode::kMerge) {
325 // This optimization pushes {Return} nodes through merges. It checks that
326 // the return value is actually a {Phi} and the return control dependency
327 // is the {Merge} to which the {Phi} belongs.
328
329 // Value1 ... ValueN Control1 ... ControlN
330 // ^ ^ ^ ^
331 // | | | |
332 // +----+-----+ +------+-----+
333 // | |
334 // Phi --------------> Merge
335 // ^ ^
336 // | |
337 // | +-----------------+
338 // | |
339 // Return -----> Effect
340 // ^
341 // |
342 // End
343
344 // Now the effect input to the {Return} node can be either an {EffectPhi}
345 // hanging off the same {Merge}, or the effect chain doesn't depend on the
346 // {Phi} or the {Merge}, in which case we know that the effect input must
347 // somehow dominate all merged branches.
348
349 Node::Inputs control_inputs = control->inputs();
350 Node::Inputs value_inputs = value->inputs();
351 DCHECK_NE(0, control_inputs.count());
352 DCHECK_EQ(control_inputs.count(), value_inputs.count() - 1);
353 DCHECK_EQ(IrOpcode::kEnd, graph()->end()->opcode());
354 DCHECK_NE(0, graph()->end()->InputCount());
355 if (control->OwnedBy(node, value) && value->OwnedBy(node)) {
356 for (int i = 0; i < control_inputs.count(); ++i) {
357 // Create a new {Return} and connect it to {end}. We don't need to mark
358 // {end} as revisit, because we mark {node} as {Dead} below, which was
359 // previously connected to {end}, so we know for sure that at some point
360 // the reducer logic will visit {end} again.
361 Node* ret = graph()->NewNode(node->op(), pop_count, value_inputs[i],
362 effect, control_inputs[i]);
363 NodeProperties::MergeControlToEnd(graph(), common(), ret);
364 }
365 // Mark the Merge {control} and Return {node} as {dead}.
366 Replace(control, dead());
367 return Replace(dead());
368 } else if (effect->opcode() == IrOpcode::kEffectPhi &&
369 NodeProperties::GetControlInput(effect) == control) {
370 Node::Inputs effect_inputs = effect->inputs();
371 DCHECK_EQ(control_inputs.count(), effect_inputs.count() - 1);
372 for (int i = 0; i < control_inputs.count(); ++i) {
373 // Create a new {Return} and connect it to {end}. We don't need to mark
374 // {end} as revisit, because we mark {node} as {Dead} below, which was
375 // previously connected to {end}, so we know for sure that at some point
376 // the reducer logic will visit {end} again.
377 Node* ret = graph()->NewNode(node->op(), pop_count, value_inputs[i],
378 effect_inputs[i], control_inputs[i]);
379 NodeProperties::MergeControlToEnd(graph(), common(), ret);
380 }
381 // Mark the Merge {control} and Return {node} as {dead}.
382 Replace(control, dead());
383 return Replace(dead());
384 }
385 }
386 return NoChange();
387 }
388
ReduceSelect(Node * node)389 Reduction CommonOperatorReducer::ReduceSelect(Node* node) {
390 DCHECK_EQ(IrOpcode::kSelect, node->opcode());
391 Node* const cond = node->InputAt(0);
392 Node* const vtrue = node->InputAt(1);
393 Node* const vfalse = node->InputAt(2);
394 if (vtrue == vfalse) return Replace(vtrue);
395 switch (DecideCondition(cond)) {
396 case Decision::kTrue:
397 return Replace(vtrue);
398 case Decision::kFalse:
399 return Replace(vfalse);
400 case Decision::kUnknown:
401 break;
402 }
403 switch (cond->opcode()) {
404 case IrOpcode::kFloat32LessThan: {
405 Float32BinopMatcher mcond(cond);
406 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
407 vfalse->opcode() == IrOpcode::kFloat32Sub) {
408 Float32BinopMatcher mvfalse(vfalse);
409 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
410 return Change(node, machine()->Float32Abs(), vtrue);
411 }
412 }
413 break;
414 }
415 case IrOpcode::kFloat64LessThan: {
416 Float64BinopMatcher mcond(cond);
417 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
418 vfalse->opcode() == IrOpcode::kFloat64Sub) {
419 Float64BinopMatcher mvfalse(vfalse);
420 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
421 return Change(node, machine()->Float64Abs(), vtrue);
422 }
423 }
424 break;
425 }
426 default:
427 break;
428 }
429 return NoChange();
430 }
431
ReduceSwitch(Node * node)432 Reduction CommonOperatorReducer::ReduceSwitch(Node* node) {
433 DCHECK_EQ(IrOpcode::kSwitch, node->opcode());
434 Node* const switched_value = node->InputAt(0);
435 Node* const control = node->InputAt(1);
436
437 // Attempt to constant match the switched value against the IfValue cases. If
438 // no case matches, then use the IfDefault. We don't bother marking
439 // non-matching cases as dead code (same for an unused IfDefault), because the
440 // Switch itself will be marked as dead code.
441 Int32Matcher mswitched(switched_value);
442 if (mswitched.HasResolvedValue()) {
443 bool matched = false;
444
445 size_t const projection_count = node->op()->ControlOutputCount();
446 Node** projections = zone_->NewArray<Node*>(projection_count);
447 NodeProperties::CollectControlProjections(node, projections,
448 projection_count);
449 for (size_t i = 0; i < projection_count - 1; i++) {
450 Node* if_value = projections[i];
451 DCHECK_EQ(IrOpcode::kIfValue, if_value->opcode());
452 const IfValueParameters& p = IfValueParametersOf(if_value->op());
453 if (p.value() == mswitched.ResolvedValue()) {
454 matched = true;
455 Replace(if_value, control);
456 break;
457 }
458 }
459 if (!matched) {
460 Node* if_default = projections[projection_count - 1];
461 DCHECK_EQ(IrOpcode::kIfDefault, if_default->opcode());
462 Replace(if_default, control);
463 }
464 return Replace(dead());
465 }
466 return NoChange();
467 }
468
ReduceStaticAssert(Node * node)469 Reduction CommonOperatorReducer::ReduceStaticAssert(Node* node) {
470 DCHECK_EQ(IrOpcode::kStaticAssert, node->opcode());
471 Node* const cond = node->InputAt(0);
472 Decision decision = DecideCondition(cond);
473 if (decision == Decision::kTrue) {
474 RelaxEffectsAndControls(node);
475 return Changed(node);
476 } else {
477 return NoChange();
478 }
479 }
480
ReduceTrapConditional(Node * trap)481 Reduction CommonOperatorReducer::ReduceTrapConditional(Node* trap) {
482 DCHECK(trap->opcode() == IrOpcode::kTrapIf ||
483 trap->opcode() == IrOpcode::kTrapUnless);
484 bool trapping_condition = trap->opcode() == IrOpcode::kTrapIf;
485 Node* const cond = trap->InputAt(0);
486 Decision decision = DecideCondition(cond);
487
488 if (decision == Decision::kUnknown) {
489 return NoChange();
490 } else if ((decision == Decision::kTrue) == trapping_condition) {
491 // This will always trap. Mark its outputs as dead and connect it to
492 // graph()->end().
493 ReplaceWithValue(trap, dead(), dead(), dead());
494 Node* effect = NodeProperties::GetEffectInput(trap);
495 Node* control = graph()->NewNode(common()->Throw(), effect, trap);
496 NodeProperties::MergeControlToEnd(graph(), common(), control);
497 Revisit(graph()->end());
498 return Changed(trap);
499 } else {
500 // This will not trap, remove it.
501 return Replace(NodeProperties::GetControlInput(trap));
502 }
503 }
504
Change(Node * node,Operator const * op,Node * a)505 Reduction CommonOperatorReducer::Change(Node* node, Operator const* op,
506 Node* a) {
507 node->ReplaceInput(0, a);
508 node->TrimInputCount(1);
509 NodeProperties::ChangeOp(node, op);
510 return Changed(node);
511 }
512
513
Change(Node * node,Operator const * op,Node * a,Node * b)514 Reduction CommonOperatorReducer::Change(Node* node, Operator const* op, Node* a,
515 Node* b) {
516 node->ReplaceInput(0, a);
517 node->ReplaceInput(1, b);
518 node->TrimInputCount(2);
519 NodeProperties::ChangeOp(node, op);
520 return Changed(node);
521 }
522
523 } // namespace compiler
524 } // namespace internal
525 } // namespace v8
526