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 18namespace v8 { 19namespace internal { 20namespace compiler { 21 22CommonOperatorReducer::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 39Reduction 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 70Decision 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 92Reduction 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 143Reduction 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 178Reduction 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 214Reduction 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 239Reduction 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 305Reduction 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 389Reduction 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 432Reduction 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 469Reduction 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 481Reduction 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 505Reduction 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 514Reduction 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