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/js-builtin-reducer.h" 6 #include "src/compiler/js-graph.h" 7 #include "src/compiler/node-matchers.h" 8 #include "src/compiler/node-properties.h" 9 #include "src/compiler/simplified-operator.h" 10 #include "src/objects-inl.h" 11 #include "src/type-cache.h" 12 #include "src/types.h" 13 14 namespace v8 { 15 namespace internal { 16 namespace compiler { 17 18 19 // Helper class to access JSCallFunction nodes that are potential candidates 20 // for reduction when they have a BuiltinFunctionId associated with them. 21 class JSCallReduction { 22 public: 23 explicit JSCallReduction(Node* node) : node_(node) {} 24 25 // Determines whether the node is a JSCallFunction operation that targets a 26 // constant callee being a well-known builtin with a BuiltinFunctionId. 27 bool HasBuiltinFunctionId() { 28 if (node_->opcode() != IrOpcode::kJSCallFunction) return false; 29 HeapObjectMatcher m(NodeProperties::GetValueInput(node_, 0)); 30 if (!m.HasValue() || !m.Value()->IsJSFunction()) return false; 31 Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value()); 32 return function->shared()->HasBuiltinFunctionId(); 33 } 34 35 // Retrieves the BuiltinFunctionId as described above. 36 BuiltinFunctionId GetBuiltinFunctionId() { 37 DCHECK_EQ(IrOpcode::kJSCallFunction, node_->opcode()); 38 HeapObjectMatcher m(NodeProperties::GetValueInput(node_, 0)); 39 Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value()); 40 return function->shared()->builtin_function_id(); 41 } 42 43 // Determines whether the call takes zero inputs. 44 bool InputsMatchZero() { return GetJSCallArity() == 0; } 45 46 // Determines whether the call takes one input of the given type. 47 bool InputsMatchOne(Type* t1) { 48 return GetJSCallArity() == 1 && 49 NodeProperties::GetType(GetJSCallInput(0))->Is(t1); 50 } 51 52 // Determines whether the call takes two inputs of the given types. 53 bool InputsMatchTwo(Type* t1, Type* t2) { 54 return GetJSCallArity() == 2 && 55 NodeProperties::GetType(GetJSCallInput(0))->Is(t1) && 56 NodeProperties::GetType(GetJSCallInput(1))->Is(t2); 57 } 58 59 // Determines whether the call takes inputs all of the given type. 60 bool InputsMatchAll(Type* t) { 61 for (int i = 0; i < GetJSCallArity(); i++) { 62 if (!NodeProperties::GetType(GetJSCallInput(i))->Is(t)) { 63 return false; 64 } 65 } 66 return true; 67 } 68 69 Node* left() { return GetJSCallInput(0); } 70 Node* right() { return GetJSCallInput(1); } 71 72 int GetJSCallArity() { 73 DCHECK_EQ(IrOpcode::kJSCallFunction, node_->opcode()); 74 // Skip first (i.e. callee) and second (i.e. receiver) operand. 75 return node_->op()->ValueInputCount() - 2; 76 } 77 78 Node* GetJSCallInput(int index) { 79 DCHECK_EQ(IrOpcode::kJSCallFunction, node_->opcode()); 80 DCHECK_LT(index, GetJSCallArity()); 81 // Skip first (i.e. callee) and second (i.e. receiver) operand. 82 return NodeProperties::GetValueInput(node_, index + 2); 83 } 84 85 private: 86 Node* node_; 87 }; 88 89 JSBuiltinReducer::JSBuiltinReducer(Editor* editor, JSGraph* jsgraph) 90 : AdvancedReducer(editor), 91 jsgraph_(jsgraph), 92 type_cache_(TypeCache::Get()) {} 93 94 // ES6 section 20.2.2.1 Math.abs ( x ) 95 Reduction JSBuiltinReducer::ReduceMathAbs(Node* node) { 96 JSCallReduction r(node); 97 if (r.InputsMatchOne(Type::PlainPrimitive())) { 98 // Math.abs(a:plain-primitive) -> NumberAbs(ToNumber(a)) 99 Node* input = ToNumber(r.GetJSCallInput(0)); 100 Node* value = graph()->NewNode(simplified()->NumberAbs(), input); 101 return Replace(value); 102 } 103 return NoChange(); 104 } 105 106 // ES6 section 20.2.2.6 Math.atan ( x ) 107 Reduction JSBuiltinReducer::ReduceMathAtan(Node* node) { 108 JSCallReduction r(node); 109 if (r.InputsMatchOne(Type::PlainPrimitive())) { 110 // Math.atan(a:plain-primitive) -> NumberAtan(ToNumber(a)) 111 Node* input = ToNumber(r.GetJSCallInput(0)); 112 Node* value = graph()->NewNode(simplified()->NumberAtan(), input); 113 return Replace(value); 114 } 115 return NoChange(); 116 } 117 118 // ES6 section 20.2.2.8 Math.atan2 ( y, x ) 119 Reduction JSBuiltinReducer::ReduceMathAtan2(Node* node) { 120 JSCallReduction r(node); 121 if (r.InputsMatchTwo(Type::PlainPrimitive(), Type::PlainPrimitive())) { 122 // Math.atan2(a:plain-primitive, 123 // b:plain-primitive) -> NumberAtan2(ToNumber(a), 124 // ToNumber(b)) 125 Node* left = ToNumber(r.left()); 126 Node* right = ToNumber(r.right()); 127 Node* value = graph()->NewNode(simplified()->NumberAtan2(), left, right); 128 return Replace(value); 129 } 130 return NoChange(); 131 } 132 133 // ES6 section 20.2.2.7 Math.atanh ( x ) 134 Reduction JSBuiltinReducer::ReduceMathAtanh(Node* node) { 135 JSCallReduction r(node); 136 if (r.InputsMatchOne(Type::Number())) { 137 // Math.atanh(a:number) -> NumberAtanh(a) 138 Node* value = graph()->NewNode(simplified()->NumberAtanh(), r.left()); 139 return Replace(value); 140 } 141 return NoChange(); 142 } 143 144 // ES6 section 20.2.2.10 Math.ceil ( x ) 145 Reduction JSBuiltinReducer::ReduceMathCeil(Node* node) { 146 JSCallReduction r(node); 147 if (r.InputsMatchOne(Type::PlainPrimitive())) { 148 // Math.ceil(a:plain-primitive) -> NumberCeil(ToNumber(a)) 149 Node* input = ToNumber(r.GetJSCallInput(0)); 150 Node* value = graph()->NewNode(simplified()->NumberCeil(), input); 151 return Replace(value); 152 } 153 return NoChange(); 154 } 155 156 // ES6 section 20.2.2.11 Math.clz32 ( x ) 157 Reduction JSBuiltinReducer::ReduceMathClz32(Node* node) { 158 JSCallReduction r(node); 159 if (r.InputsMatchOne(Type::PlainPrimitive())) { 160 // Math.clz32(a:plain-primitive) -> NumberClz32(ToUint32(a)) 161 Node* input = ToUint32(r.GetJSCallInput(0)); 162 Node* value = graph()->NewNode(simplified()->NumberClz32(), input); 163 return Replace(value); 164 } 165 return NoChange(); 166 } 167 168 // ES6 section 20.2.2.12 Math.cos ( x ) 169 Reduction JSBuiltinReducer::ReduceMathCos(Node* node) { 170 JSCallReduction r(node); 171 if (r.InputsMatchOne(Type::PlainPrimitive())) { 172 // Math.cos(a:plain-primitive) -> NumberCos(ToNumber(a)) 173 Node* input = ToNumber(r.GetJSCallInput(0)); 174 Node* value = graph()->NewNode(simplified()->NumberCos(), input); 175 return Replace(value); 176 } 177 return NoChange(); 178 } 179 180 // ES6 section 20.2.2.14 Math.exp ( x ) 181 Reduction JSBuiltinReducer::ReduceMathExp(Node* node) { 182 JSCallReduction r(node); 183 if (r.InputsMatchOne(Type::PlainPrimitive())) { 184 // Math.exp(a:plain-primitive) -> NumberExp(ToNumber(a)) 185 Node* input = ToNumber(r.GetJSCallInput(0)); 186 Node* value = graph()->NewNode(simplified()->NumberExp(), input); 187 return Replace(value); 188 } 189 return NoChange(); 190 } 191 192 // ES6 section 20.2.2.15 Math.expm1 ( x ) 193 Reduction JSBuiltinReducer::ReduceMathExpm1(Node* node) { 194 JSCallReduction r(node); 195 if (r.InputsMatchOne(Type::Number())) { 196 // Math.expm1(a:number) -> NumberExpm1(a) 197 Node* value = graph()->NewNode(simplified()->NumberExpm1(), r.left()); 198 return Replace(value); 199 } 200 return NoChange(); 201 } 202 203 // ES6 section 20.2.2.16 Math.floor ( x ) 204 Reduction JSBuiltinReducer::ReduceMathFloor(Node* node) { 205 JSCallReduction r(node); 206 if (r.InputsMatchOne(Type::PlainPrimitive())) { 207 // Math.floor(a:plain-primitive) -> NumberFloor(ToNumber(a)) 208 Node* input = ToNumber(r.GetJSCallInput(0)); 209 Node* value = graph()->NewNode(simplified()->NumberFloor(), input); 210 return Replace(value); 211 } 212 return NoChange(); 213 } 214 215 // ES6 section 20.2.2.17 Math.fround ( x ) 216 Reduction JSBuiltinReducer::ReduceMathFround(Node* node) { 217 JSCallReduction r(node); 218 if (r.InputsMatchOne(Type::PlainPrimitive())) { 219 // Math.fround(a:plain-primitive) -> NumberFround(ToNumber(a)) 220 Node* input = ToNumber(r.GetJSCallInput(0)); 221 Node* value = graph()->NewNode(simplified()->NumberFround(), input); 222 return Replace(value); 223 } 224 return NoChange(); 225 } 226 227 // ES6 section 20.2.2.19 Math.imul ( x, y ) 228 Reduction JSBuiltinReducer::ReduceMathImul(Node* node) { 229 JSCallReduction r(node); 230 if (r.InputsMatchTwo(Type::PlainPrimitive(), Type::PlainPrimitive())) { 231 // Math.imul(a:plain-primitive, 232 // b:plain-primitive) -> NumberImul(ToUint32(a), 233 // ToUint32(b)) 234 Node* left = ToUint32(r.left()); 235 Node* right = ToUint32(r.right()); 236 Node* value = graph()->NewNode(simplified()->NumberImul(), left, right); 237 return Replace(value); 238 } 239 return NoChange(); 240 } 241 242 // ES6 section 20.2.2.20 Math.log ( x ) 243 Reduction JSBuiltinReducer::ReduceMathLog(Node* node) { 244 JSCallReduction r(node); 245 if (r.InputsMatchOne(Type::PlainPrimitive())) { 246 // Math.log(a:plain-primitive) -> NumberLog(ToNumber(a)) 247 Node* input = ToNumber(r.GetJSCallInput(0)); 248 Node* value = graph()->NewNode(simplified()->NumberLog(), input); 249 return Replace(value); 250 } 251 return NoChange(); 252 } 253 254 // ES6 section 20.2.2.21 Math.log1p ( x ) 255 Reduction JSBuiltinReducer::ReduceMathLog1p(Node* node) { 256 JSCallReduction r(node); 257 if (r.InputsMatchOne(Type::PlainPrimitive())) { 258 // Math.log1p(a:plain-primitive) -> NumberLog1p(ToNumber(a)) 259 Node* input = ToNumber(r.GetJSCallInput(0)); 260 Node* value = graph()->NewNode(simplified()->NumberLog1p(), input); 261 return Replace(value); 262 } 263 return NoChange(); 264 } 265 266 // ES6 section 20.2.2.22 Math.log10 ( x ) 267 Reduction JSBuiltinReducer::ReduceMathLog10(Node* node) { 268 JSCallReduction r(node); 269 if (r.InputsMatchOne(Type::Number())) { 270 // Math.log10(a:number) -> NumberLog10(a) 271 Node* value = graph()->NewNode(simplified()->NumberLog10(), r.left()); 272 return Replace(value); 273 } 274 return NoChange(); 275 } 276 277 // ES6 section 20.2.2.23 Math.log2 ( x ) 278 Reduction JSBuiltinReducer::ReduceMathLog2(Node* node) { 279 JSCallReduction r(node); 280 if (r.InputsMatchOne(Type::Number())) { 281 // Math.log2(a:number) -> NumberLog(a) 282 Node* value = graph()->NewNode(simplified()->NumberLog2(), r.left()); 283 return Replace(value); 284 } 285 return NoChange(); 286 } 287 288 // ES6 section 20.2.2.24 Math.max ( value1, value2, ...values ) 289 Reduction JSBuiltinReducer::ReduceMathMax(Node* node) { 290 JSCallReduction r(node); 291 if (r.InputsMatchZero()) { 292 // Math.max() -> -Infinity 293 return Replace(jsgraph()->Constant(-V8_INFINITY)); 294 } 295 if (r.InputsMatchOne(Type::PlainPrimitive())) { 296 // Math.max(a:plain-primitive) -> ToNumber(a) 297 Node* value = ToNumber(r.GetJSCallInput(0)); 298 return Replace(value); 299 } 300 if (r.InputsMatchAll(Type::Integral32())) { 301 // Math.max(a:int32, b:int32, ...) 302 Node* value = r.GetJSCallInput(0); 303 for (int i = 1; i < r.GetJSCallArity(); i++) { 304 Node* const input = r.GetJSCallInput(i); 305 value = graph()->NewNode( 306 common()->Select(MachineRepresentation::kNone), 307 graph()->NewNode(simplified()->NumberLessThan(), input, value), value, 308 input); 309 } 310 return Replace(value); 311 } 312 return NoChange(); 313 } 314 315 // ES6 section 20.2.2.25 Math.min ( value1, value2, ...values ) 316 Reduction JSBuiltinReducer::ReduceMathMin(Node* node) { 317 JSCallReduction r(node); 318 if (r.InputsMatchZero()) { 319 // Math.min() -> Infinity 320 return Replace(jsgraph()->Constant(V8_INFINITY)); 321 } 322 if (r.InputsMatchOne(Type::PlainPrimitive())) { 323 // Math.min(a:plain-primitive) -> ToNumber(a) 324 Node* value = ToNumber(r.GetJSCallInput(0)); 325 return Replace(value); 326 } 327 if (r.InputsMatchAll(Type::Integral32())) { 328 // Math.min(a:int32, b:int32, ...) 329 Node* value = r.GetJSCallInput(0); 330 for (int i = 1; i < r.GetJSCallArity(); i++) { 331 Node* const input = r.GetJSCallInput(i); 332 value = graph()->NewNode( 333 common()->Select(MachineRepresentation::kNone), 334 graph()->NewNode(simplified()->NumberLessThan(), input, value), input, 335 value); 336 } 337 return Replace(value); 338 } 339 return NoChange(); 340 } 341 342 // ES6 section 20.2.2.28 Math.round ( x ) 343 Reduction JSBuiltinReducer::ReduceMathRound(Node* node) { 344 JSCallReduction r(node); 345 if (r.InputsMatchOne(Type::PlainPrimitive())) { 346 // Math.round(a:plain-primitive) -> NumberRound(ToNumber(a)) 347 Node* input = ToNumber(r.GetJSCallInput(0)); 348 Node* value = graph()->NewNode(simplified()->NumberRound(), input); 349 return Replace(value); 350 } 351 return NoChange(); 352 } 353 354 // ES6 section 20.2.2.9 Math.cbrt ( x ) 355 Reduction JSBuiltinReducer::ReduceMathCbrt(Node* node) { 356 JSCallReduction r(node); 357 if (r.InputsMatchOne(Type::Number())) { 358 // Math.cbrt(a:number) -> NumberCbrt(a) 359 Node* value = graph()->NewNode(simplified()->NumberCbrt(), r.left()); 360 return Replace(value); 361 } 362 return NoChange(); 363 } 364 365 // ES6 section 20.2.2.30 Math.sin ( x ) 366 Reduction JSBuiltinReducer::ReduceMathSin(Node* node) { 367 JSCallReduction r(node); 368 if (r.InputsMatchOne(Type::PlainPrimitive())) { 369 // Math.sin(a:plain-primitive) -> NumberSin(ToNumber(a)) 370 Node* input = ToNumber(r.GetJSCallInput(0)); 371 Node* value = graph()->NewNode(simplified()->NumberSin(), input); 372 return Replace(value); 373 } 374 return NoChange(); 375 } 376 377 // ES6 section 20.2.2.32 Math.sqrt ( x ) 378 Reduction JSBuiltinReducer::ReduceMathSqrt(Node* node) { 379 JSCallReduction r(node); 380 if (r.InputsMatchOne(Type::PlainPrimitive())) { 381 // Math.sqrt(a:plain-primitive) -> NumberSqrt(ToNumber(a)) 382 Node* input = ToNumber(r.GetJSCallInput(0)); 383 Node* value = graph()->NewNode(simplified()->NumberSqrt(), input); 384 return Replace(value); 385 } 386 return NoChange(); 387 } 388 389 // ES6 section 20.2.2.33 Math.tan ( x ) 390 Reduction JSBuiltinReducer::ReduceMathTan(Node* node) { 391 JSCallReduction r(node); 392 if (r.InputsMatchOne(Type::PlainPrimitive())) { 393 // Math.tan(a:plain-primitive) -> NumberTan(ToNumber(a)) 394 Node* input = ToNumber(r.GetJSCallInput(0)); 395 Node* value = graph()->NewNode(simplified()->NumberTan(), input); 396 return Replace(value); 397 } 398 return NoChange(); 399 } 400 401 // ES6 section 20.2.2.35 Math.trunc ( x ) 402 Reduction JSBuiltinReducer::ReduceMathTrunc(Node* node) { 403 JSCallReduction r(node); 404 if (r.InputsMatchOne(Type::PlainPrimitive())) { 405 // Math.trunc(a:plain-primitive) -> NumberTrunc(ToNumber(a)) 406 Node* input = ToNumber(r.GetJSCallInput(0)); 407 Node* value = graph()->NewNode(simplified()->NumberTrunc(), input); 408 return Replace(value); 409 } 410 return NoChange(); 411 } 412 413 // ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits ) 414 Reduction JSBuiltinReducer::ReduceStringFromCharCode(Node* node) { 415 JSCallReduction r(node); 416 if (r.InputsMatchOne(Type::PlainPrimitive())) { 417 // String.fromCharCode(a:plain-primitive) -> StringFromCharCode(a) 418 Node* input = ToNumber(r.GetJSCallInput(0)); 419 Node* value = graph()->NewNode(simplified()->StringFromCharCode(), input); 420 return Replace(value); 421 } 422 return NoChange(); 423 } 424 425 Reduction JSBuiltinReducer::Reduce(Node* node) { 426 Reduction reduction = NoChange(); 427 JSCallReduction r(node); 428 429 // Dispatch according to the BuiltinFunctionId if present. 430 if (!r.HasBuiltinFunctionId()) return NoChange(); 431 switch (r.GetBuiltinFunctionId()) { 432 case kMathAbs: 433 reduction = ReduceMathAbs(node); 434 break; 435 case kMathAtan: 436 reduction = ReduceMathAtan(node); 437 break; 438 case kMathAtan2: 439 reduction = ReduceMathAtan2(node); 440 break; 441 case kMathAtanh: 442 reduction = ReduceMathAtanh(node); 443 break; 444 case kMathClz32: 445 reduction = ReduceMathClz32(node); 446 break; 447 case kMathCeil: 448 reduction = ReduceMathCeil(node); 449 break; 450 case kMathCos: 451 reduction = ReduceMathCos(node); 452 break; 453 case kMathExp: 454 reduction = ReduceMathExp(node); 455 break; 456 case kMathExpm1: 457 reduction = ReduceMathExpm1(node); 458 break; 459 case kMathFloor: 460 reduction = ReduceMathFloor(node); 461 break; 462 case kMathFround: 463 reduction = ReduceMathFround(node); 464 break; 465 case kMathImul: 466 reduction = ReduceMathImul(node); 467 break; 468 case kMathLog: 469 reduction = ReduceMathLog(node); 470 break; 471 case kMathLog1p: 472 reduction = ReduceMathLog1p(node); 473 break; 474 case kMathLog10: 475 reduction = ReduceMathLog10(node); 476 break; 477 case kMathLog2: 478 reduction = ReduceMathLog2(node); 479 break; 480 case kMathMax: 481 reduction = ReduceMathMax(node); 482 break; 483 case kMathMin: 484 reduction = ReduceMathMin(node); 485 break; 486 case kMathCbrt: 487 reduction = ReduceMathCbrt(node); 488 break; 489 case kMathRound: 490 reduction = ReduceMathRound(node); 491 break; 492 case kMathSin: 493 reduction = ReduceMathSin(node); 494 break; 495 case kMathSqrt: 496 reduction = ReduceMathSqrt(node); 497 break; 498 case kMathTan: 499 reduction = ReduceMathTan(node); 500 break; 501 case kMathTrunc: 502 reduction = ReduceMathTrunc(node); 503 break; 504 case kStringFromCharCode: 505 reduction = ReduceStringFromCharCode(node); 506 break; 507 default: 508 break; 509 } 510 511 // Replace builtin call assuming replacement nodes are pure values that don't 512 // produce an effect. Replaces {node} with {reduction} and relaxes effects. 513 if (reduction.Changed()) ReplaceWithValue(node, reduction.replacement()); 514 515 return reduction; 516 } 517 518 Node* JSBuiltinReducer::ToNumber(Node* input) { 519 Type* input_type = NodeProperties::GetType(input); 520 if (input_type->Is(Type::Number())) return input; 521 return graph()->NewNode(simplified()->PlainPrimitiveToNumber(), input); 522 } 523 524 Node* JSBuiltinReducer::ToUint32(Node* input) { 525 input = ToNumber(input); 526 Type* input_type = NodeProperties::GetType(input); 527 if (input_type->Is(Type::Unsigned32())) return input; 528 return graph()->NewNode(simplified()->NumberToUint32(), input); 529 } 530 531 Graph* JSBuiltinReducer::graph() const { return jsgraph()->graph(); } 532 533 534 Isolate* JSBuiltinReducer::isolate() const { return jsgraph()->isolate(); } 535 536 537 CommonOperatorBuilder* JSBuiltinReducer::common() const { 538 return jsgraph()->common(); 539 } 540 541 542 SimplifiedOperatorBuilder* JSBuiltinReducer::simplified() const { 543 return jsgraph()->simplified(); 544 } 545 546 } // namespace compiler 547 } // namespace internal 548 } // namespace v8 549