TrialInlining.cpp (35676B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "jit/TrialInlining.h" 8 9 #include "mozilla/DebugOnly.h" 10 11 #include "jit/BaselineCacheIRCompiler.h" 12 #include "jit/BaselineFrame.h" 13 #include "jit/BaselineIC.h" 14 #include "jit/BytecodeAnalysis.h" 15 #include "jit/CacheIRCloner.h" 16 #include "jit/CacheIRHealth.h" 17 #include "jit/CacheIRWriter.h" 18 #include "jit/InlineScriptTree.h" 19 #include "jit/Ion.h" // TooManyFormalArguments 20 #include "jit/StubFolding.h" 21 22 #include "vm/BytecodeLocation-inl.h" 23 24 using mozilla::Maybe; 25 26 namespace js { 27 namespace jit { 28 29 bool DoTrialInlining(JSContext* cx, BaselineFrame* frame) { 30 RootedScript script(cx, frame->script()); 31 ICScript* icScript = frame->icScript(); 32 bool isRecursive = icScript->depth() > 0; 33 34 #ifdef JS_CACHEIR_SPEW 35 if (cx->spewer().enabled(cx, script, SpewChannel::CacheIRHealthReport)) { 36 for (uint32_t i = 0; i < icScript->numICEntries(); i++) { 37 ICEntry& entry = icScript->icEntry(i); 38 ICFallbackStub* fallbackStub = icScript->fallbackStub(i); 39 40 // If the IC is megamorphic or generic, then we have already 41 // spewed the IC report on transition. 42 if (!(uint8_t(fallbackStub->state().mode()) > 0)) { 43 jit::ICStub* stub = entry.firstStub(); 44 bool sawNonZeroCount = false; 45 while (!stub->isFallback()) { 46 uint32_t count = stub->enteredCount(); 47 if (count > 0 && sawNonZeroCount) { 48 CacheIRHealth cih; 49 cih.healthReportForIC(cx, &entry, fallbackStub, script, 50 SpewContext::TrialInlining); 51 break; 52 } 53 54 if (count > 0 && !sawNonZeroCount) { 55 sawNonZeroCount = true; 56 } 57 58 stub = stub->toCacheIRStub()->next(); 59 } 60 } 61 } 62 } 63 #endif 64 65 if (!script->canIonCompile()) { 66 return true; 67 } 68 69 // Don't do trial inlining in scripts that are too large. 70 if (JitOptions.limitScriptSize && 71 script->length() > JitOptions.ionMaxScriptSize) { 72 // Baseline should elide trial inlining calls if the script is big. 73 MOZ_ASSERT(frame->runningInInterpreter()); 74 return true; 75 } 76 77 // Maximum trial inlining depth for ICScripts. This is smaller than 78 // InlineScriptTree::MaxDepth because we can reach the bigger number if we are 79 // doing monomorphic inlining using shared IC data. 80 const uint32_t MaxICScriptDepth = 4; 81 static_assert(MaxICScriptDepth <= InlineScriptTree::MaxDepth, 82 "Trial inlining depth must not exceed max inlining depth"); 83 if (icScript->depth() > MaxICScriptDepth) { 84 return true; 85 } 86 87 InliningRoot* root = isRecursive ? icScript->inliningRoot() 88 : script->jitScript()->inliningRoot(); 89 if (JitSpewEnabled(JitSpew_WarpTrialInlining)) { 90 // Eagerly create the inlining root when it's used in the spew output. 91 if (!root) { 92 MOZ_ASSERT(!isRecursive); 93 root = script->jitScript()->getOrCreateInliningRoot(cx, script); 94 if (!root) { 95 return false; 96 } 97 } 98 UniqueChars funName; 99 if (script->function() && script->function()->fullDisplayAtom()) { 100 funName = 101 AtomToPrintableString(cx, script->function()->fullDisplayAtom()); 102 } 103 104 JitSpew( 105 JitSpew_WarpTrialInlining, 106 "Trial inlining for %s script '%s' (%s:%u:%u (%p)) (inliningRoot=%p)", 107 (isRecursive ? "inner" : "outer"), 108 funName ? funName.get() : "<unnamed>", script->filename(), 109 script->lineno(), script->column().oneOriginValue(), frame->script(), 110 root); 111 JitSpewIndent spewIndent(JitSpew_WarpTrialInlining); 112 } 113 114 TrialInliner inliner(cx, script, icScript); 115 return inliner.tryInlining(); 116 } 117 118 void TrialInliner::cloneSharedPrefix(ICCacheIRStub* stub, 119 const uint8_t* endOfPrefix, 120 CacheIRWriter& writer) { 121 CacheIRReader reader(stub->stubInfo()); 122 CacheIRCloner cloner(stub); 123 while (reader.currentPosition() < endOfPrefix) { 124 CacheOp op = reader.readOp(); 125 cloner.cloneOp(op, reader, writer); 126 } 127 } 128 129 bool TrialInliner::replaceICStub(ICEntry& entry, ICFallbackStub* fallback, 130 CacheIRWriter& writer, CacheKind kind) { 131 MOZ_ASSERT(fallback->trialInliningState() == TrialInliningState::Candidate); 132 133 fallback->discardStubs(cx()->zone(), &entry); 134 135 // Note: AttachBaselineCacheIRStub never throws an exception. 136 ICAttachResult result = AttachBaselineCacheIRStub( 137 cx(), writer, kind, script_, icScript_, fallback, "TrialInline"); 138 if (result == ICAttachResult::Attached) { 139 MOZ_ASSERT(fallback->trialInliningState() == TrialInliningState::Inlined); 140 return true; 141 } 142 143 MOZ_ASSERT(fallback->trialInliningState() == TrialInliningState::Candidate); 144 icScript_->removeInlinedChild(fallback->pcOffset()); 145 146 if (result == ICAttachResult::OOM) { 147 ReportOutOfMemory(cx()); 148 return false; 149 } 150 151 // We failed to attach a new IC stub due to CacheIR size limits. Disable trial 152 // inlining for this location and return true. 153 MOZ_ASSERT(result == ICAttachResult::TooLarge); 154 fallback->setTrialInliningState(TrialInliningState::Failure); 155 return true; 156 } 157 158 ICCacheIRStub* TrialInliner::maybeSingleStub(const ICEntry& entry) { 159 // Look for a single non-fallback stub followed by stubs with entered-count 0. 160 // Allow one optimized stub before the fallback stub to support the 161 // CallIRGenerator::emitCalleeGuard optimization where we first try a 162 // GuardSpecificFunction guard before falling back to GuardFunctionHasScript. 163 ICStub* stub = entry.firstStub(); 164 if (stub->isFallback()) { 165 return nullptr; 166 } 167 ICStub* next = stub->toCacheIRStub()->next(); 168 if (next->enteredCount() != 0) { 169 return nullptr; 170 } 171 172 ICFallbackStub* fallback = nullptr; 173 if (next->isFallback()) { 174 fallback = next->toFallbackStub(); 175 } else { 176 ICStub* nextNext = next->toCacheIRStub()->next(); 177 if (!nextNext->isFallback() || nextNext->enteredCount() != 0) { 178 return nullptr; 179 } 180 fallback = nextNext->toFallbackStub(); 181 } 182 183 if (fallback->trialInliningState() != TrialInliningState::Candidate) { 184 return nullptr; 185 } 186 187 return stub->toCacheIRStub(); 188 } 189 190 Maybe<InlinableOpData> FindInlinableOpData(ICCacheIRStub* stub, 191 BytecodeLocation loc) { 192 if (loc.isInvokeOp()) { 193 Maybe<InlinableCallData> call = FindInlinableCallData(stub); 194 if (call.isSome()) { 195 return call; 196 } 197 } 198 if (loc.isGetPropOp() || loc.isGetElemOp()) { 199 Maybe<InlinableGetterData> getter = FindInlinableGetterData(stub); 200 if (getter.isSome()) { 201 return getter; 202 } 203 } 204 if (loc.isSetPropOp()) { 205 Maybe<InlinableSetterData> setter = FindInlinableSetterData(stub); 206 if (setter.isSome()) { 207 return setter; 208 } 209 } 210 return mozilla::Nothing(); 211 } 212 213 Maybe<InlinableCallData> FindInlinableCallData(ICCacheIRStub* stub) { 214 Maybe<InlinableCallData> data; 215 216 const CacheIRStubInfo* stubInfo = stub->stubInfo(); 217 const uint8_t* stubData = stub->stubDataStart(); 218 219 ObjOperandId calleeGuardOperand; 220 CallFlags flags; 221 JSScript* targetScript = nullptr; 222 223 CacheIRReader reader(stubInfo); 224 while (reader.more()) { 225 const uint8_t* opStart = reader.currentPosition(); 226 227 CacheOp op = reader.readOp(); 228 CacheIROpInfo opInfo = CacheIROpInfos[size_t(op)]; 229 uint32_t argLength = opInfo.argLength; 230 mozilla::DebugOnly<const uint8_t*> argStart = reader.currentPosition(); 231 232 switch (op) { 233 case CacheOp::GuardSpecificFunction: { 234 // If we see a guard for a scripted function, remember which 235 // operand we are guarding. 236 MOZ_ASSERT(data.isNothing()); 237 auto args = reader.argsForGuardSpecificFunction(); 238 uint32_t targetOffset = args.expectedOffset; 239 uintptr_t rawFunction = 240 stubInfo->getStubRawWord(stubData, targetOffset); 241 JSFunction* function = reinterpret_cast<JSFunction*>(rawFunction); 242 if (function->hasBytecode()) { 243 calleeGuardOperand = args.funId; 244 targetScript = function->nonLazyScript(); 245 } 246 break; 247 } 248 case CacheOp::GuardFunctionScript: { 249 MOZ_ASSERT(data.isNothing()); 250 auto args = reader.argsForGuardFunctionScript(); 251 calleeGuardOperand = args.objId; 252 uint32_t targetOffset = args.expectedOffset; 253 uintptr_t rawScript = stubInfo->getStubRawWord(stubData, targetOffset); 254 targetScript = reinterpret_cast<JSScript*>(rawScript); 255 break; 256 } 257 case CacheOp::CallScriptedFunction: { 258 // If we see a call, check if `callee` is the previously guarded 259 // operand. If it is, we know the target and can inline. 260 auto args = reader.argsForCallScriptedFunction(); 261 flags = args.flags; 262 MOZ_ASSERT(args.argcFixed <= MaxUnrolledArgCopy); 263 264 if (args.calleeId == calleeGuardOperand) { 265 MOZ_ASSERT(args.argcId.id() == 0); 266 MOZ_ASSERT(data.isNothing()); 267 data.emplace(); 268 data->endOfSharedPrefix = opStart; 269 } 270 break; 271 } 272 case CacheOp::CallInlinedFunction: { 273 auto args = reader.argsForCallInlinedFunction(); 274 flags = args.flags; 275 MOZ_ASSERT(args.argcFixed <= MaxUnrolledArgCopy); 276 277 if (args.calleeId == calleeGuardOperand) { 278 MOZ_ASSERT(args.argcId.id() == 0); 279 MOZ_ASSERT(data.isNothing()); 280 data.emplace(); 281 data->endOfSharedPrefix = opStart; 282 uintptr_t rawICScript = 283 stubInfo->getStubRawWord(stubData, args.icScriptOffset); 284 data->icScript = reinterpret_cast<ICScript*>(rawICScript); 285 } 286 break; 287 } 288 default: 289 if (!opInfo.transpile) { 290 return mozilla::Nothing(); 291 } 292 if (data.isSome()) { 293 MOZ_ASSERT(op == CacheOp::ReturnFromIC); 294 } 295 reader.skip(argLength); 296 break; 297 } 298 MOZ_ASSERT(argStart + argLength == reader.currentPosition()); 299 } 300 301 if (data.isSome()) { 302 // Warp only supports inlining Standard and FunCall calls. 303 if (flags.getArgFormat() != CallFlags::Standard && 304 flags.getArgFormat() != CallFlags::FunCall) { 305 return mozilla::Nothing(); 306 } 307 data->calleeOperand = calleeGuardOperand; 308 data->callFlags = flags; 309 data->target = targetScript; 310 } 311 return data; 312 } 313 314 Maybe<InlinableGetterData> FindInlinableGetterData(ICCacheIRStub* stub) { 315 Maybe<InlinableGetterData> data; 316 317 const CacheIRStubInfo* stubInfo = stub->stubInfo(); 318 const uint8_t* stubData = stub->stubDataStart(); 319 320 ObjOperandId maybeCalleeOperand; 321 JSScript* targetScript = nullptr; 322 323 CacheIRReader reader(stubInfo); 324 while (reader.more()) { 325 const uint8_t* opStart = reader.currentPosition(); 326 327 CacheOp op = reader.readOp(); 328 CacheIROpInfo opInfo = CacheIROpInfos[size_t(op)]; 329 uint32_t argLength = opInfo.argLength; 330 mozilla::DebugOnly<const uint8_t*> argStart = reader.currentPosition(); 331 332 switch (op) { 333 case CacheOp::LoadObject: { 334 // If we load a constant object, remember it in case it's the callee. 335 auto [resultOperand, objOffset] = reader.argsForLoadObject(); 336 uintptr_t rawObject = stubInfo->getStubRawWord(stubData, objOffset); 337 JSObject* object = reinterpret_cast<JSObject*>(rawObject); 338 if (object->is<JSFunction>() && 339 object->as<JSFunction>().hasBytecode()) { 340 maybeCalleeOperand = resultOperand; 341 targetScript = object->as<JSFunction>().nonLazyScript(); 342 } 343 break; 344 } 345 case CacheOp::GuardFunctionScript: { 346 MOZ_ASSERT(data.isNothing()); 347 auto args = reader.argsForGuardFunctionScript(); 348 maybeCalleeOperand = args.objId; 349 uint32_t targetOffset = args.expectedOffset; 350 uintptr_t rawScript = stubInfo->getStubRawWord(stubData, targetOffset); 351 targetScript = reinterpret_cast<JSScript*>(rawScript); 352 break; 353 } 354 case CacheOp::CallScriptedGetterResult: { 355 auto args = reader.argsForCallScriptedGetterResult(); 356 ObjOperandId calleeOperand = args.calleeId; 357 358 if (maybeCalleeOperand == calleeOperand) { 359 data.emplace(); 360 data->target = targetScript; 361 data->receiverOperand = args.receiverId; 362 data->calleeOperand = calleeOperand; 363 data->sameRealm = args.sameRealm; 364 data->endOfSharedPrefix = opStart; 365 } 366 break; 367 } 368 case CacheOp::CallInlinedGetterResult: { 369 auto args = reader.argsForCallInlinedGetterResult(); 370 ObjOperandId calleeOperand = args.calleeId; 371 uintptr_t rawICScript = 372 stubInfo->getStubRawWord(stubData, args.icScriptOffset); 373 374 if (maybeCalleeOperand == calleeOperand) { 375 data.emplace(); 376 data->target = targetScript; 377 data->receiverOperand = args.receiverId; 378 data->calleeOperand = calleeOperand; 379 data->icScript = reinterpret_cast<ICScript*>(rawICScript); 380 data->sameRealm = args.sameRealm; 381 data->endOfSharedPrefix = opStart; 382 } 383 break; 384 } 385 default: 386 if (!opInfo.transpile) { 387 return mozilla::Nothing(); 388 } 389 if (data.isSome()) { 390 MOZ_ASSERT(op == CacheOp::ReturnFromIC); 391 } 392 reader.skip(argLength); 393 break; 394 } 395 MOZ_ASSERT(argStart + argLength == reader.currentPosition()); 396 } 397 398 return data; 399 } 400 401 Maybe<InlinableSetterData> FindInlinableSetterData(ICCacheIRStub* stub) { 402 Maybe<InlinableSetterData> data; 403 404 const CacheIRStubInfo* stubInfo = stub->stubInfo(); 405 const uint8_t* stubData = stub->stubDataStart(); 406 407 ObjOperandId maybeCalleeOperand; 408 JSScript* targetScript = nullptr; 409 410 CacheIRReader reader(stubInfo); 411 while (reader.more()) { 412 const uint8_t* opStart = reader.currentPosition(); 413 414 CacheOp op = reader.readOp(); 415 CacheIROpInfo opInfo = CacheIROpInfos[size_t(op)]; 416 uint32_t argLength = opInfo.argLength; 417 mozilla::DebugOnly<const uint8_t*> argStart = reader.currentPosition(); 418 419 switch (op) { 420 case CacheOp::LoadObject: { 421 // If we load a constant object, remember it in case it's the callee. 422 auto [resultOperand, objOffset] = reader.argsForLoadObject(); 423 uintptr_t rawObject = stubInfo->getStubRawWord(stubData, objOffset); 424 JSObject* object = reinterpret_cast<JSObject*>(rawObject); 425 if (object->is<JSFunction>() && 426 object->as<JSFunction>().hasBytecode()) { 427 maybeCalleeOperand = resultOperand; 428 targetScript = object->as<JSFunction>().nonLazyScript(); 429 } 430 break; 431 } 432 case CacheOp::GuardFunctionScript: { 433 MOZ_ASSERT(data.isNothing()); 434 auto args = reader.argsForGuardFunctionScript(); 435 maybeCalleeOperand = args.objId; 436 uint32_t targetOffset = args.expectedOffset; 437 uintptr_t rawScript = stubInfo->getStubRawWord(stubData, targetOffset); 438 targetScript = reinterpret_cast<JSScript*>(rawScript); 439 break; 440 } 441 case CacheOp::CallScriptedSetter: { 442 auto args = reader.argsForCallScriptedSetter(); 443 ObjOperandId calleeOperand = args.calleeId; 444 445 if (maybeCalleeOperand == calleeOperand) { 446 data.emplace(); 447 data->target = targetScript; 448 data->receiverOperand = args.receiverId; 449 data->calleeOperand = calleeOperand; 450 data->rhsOperand = args.rhsId; 451 data->sameRealm = args.sameRealm; 452 data->endOfSharedPrefix = opStart; 453 } 454 break; 455 } 456 case CacheOp::CallInlinedSetter: { 457 auto args = reader.argsForCallInlinedSetter(); 458 ObjOperandId calleeOperand = args.calleeId; 459 uintptr_t rawICScript = 460 stubInfo->getStubRawWord(stubData, args.icScriptOffset); 461 462 if (maybeCalleeOperand == calleeOperand) { 463 data.emplace(); 464 data->target = targetScript; 465 data->receiverOperand = args.receiverId; 466 data->calleeOperand = calleeOperand; 467 data->rhsOperand = args.rhsId; 468 data->icScript = reinterpret_cast<ICScript*>(rawICScript); 469 data->sameRealm = args.sameRealm; 470 data->endOfSharedPrefix = opStart; 471 } 472 break; 473 } 474 default: 475 if (!opInfo.transpile) { 476 return mozilla::Nothing(); 477 } 478 if (data.isSome()) { 479 MOZ_ASSERT(op == CacheOp::ReturnFromIC); 480 } 481 reader.skip(argLength); 482 break; 483 } 484 MOZ_ASSERT(argStart + argLength == reader.currentPosition()); 485 } 486 487 return data; 488 } 489 490 // Return the maximum number of actual arguments that will be passed to the 491 // target function. This may be an overapproximation, for example when inlining 492 // js::fun_call we may omit an argument. 493 static uint32_t GetMaxCalleeNumActuals(BytecodeLocation loc) { 494 switch (loc.getOp()) { 495 case JSOp::GetProp: 496 case JSOp::GetElem: 497 // Getters do not pass arguments. 498 return 0; 499 500 case JSOp::SetProp: 501 case JSOp::StrictSetProp: 502 // Setters pass 1 argument. 503 return 1; 504 505 case JSOp::Call: 506 case JSOp::CallContent: 507 case JSOp::CallIgnoresRv: 508 case JSOp::CallIter: 509 case JSOp::CallContentIter: 510 case JSOp::New: 511 case JSOp::NewContent: 512 case JSOp::SuperCall: 513 return loc.getCallArgc(); 514 515 default: 516 MOZ_CRASH("Unsupported op"); 517 } 518 } 519 520 /*static*/ 521 bool TrialInliner::IsValidInliningOp(JSOp op) { 522 switch (op) { 523 case JSOp::GetProp: 524 case JSOp::GetElem: 525 case JSOp::SetProp: 526 case JSOp::StrictSetProp: 527 case JSOp::Call: 528 case JSOp::CallContent: 529 case JSOp::CallIgnoresRv: 530 case JSOp::CallIter: 531 case JSOp::CallContentIter: 532 case JSOp::New: 533 case JSOp::NewContent: 534 case JSOp::SuperCall: 535 return true; 536 default: 537 break; 538 } 539 return false; 540 } 541 542 /*static*/ 543 bool TrialInliner::canInline(JSContext* cx, JSScript* script, 544 HandleScript caller, BytecodeLocation loc) { 545 if (!script->hasJitScript()) { 546 JitSpew(JitSpew_WarpTrialInlining, "SKIP: no JIT script"); 547 return false; 548 } 549 if (script->uninlineable()) { 550 JitSpew(JitSpew_WarpTrialInlining, "SKIP: uninlineable flag"); 551 return false; 552 } 553 if (!script->canIonCompile()) { 554 JitSpew(JitSpew_WarpTrialInlining, "SKIP: can't ion-compile"); 555 return false; 556 } 557 if (script->isDebuggee()) { 558 JitSpew(JitSpew_WarpTrialInlining, "SKIP: is debuggee"); 559 return false; 560 } 561 // Don't inline cross-realm calls. 562 if (script->realm() != caller->realm()) { 563 JitSpew(JitSpew_WarpTrialInlining, "SKIP: cross-realm call"); 564 return false; 565 } 566 if (JitOptions.onlyInlineSelfHosted && !script->selfHosted()) { 567 JitSpew(JitSpew_WarpTrialInlining, "SKIP: only inlining self hosted"); 568 return false; 569 } 570 if (!IsValidInliningOp(loc.getOp())) { 571 JitSpew(JitSpew_WarpTrialInlining, "SKIP: non inlineable op"); 572 return false; 573 } 574 575 uint32_t maxCalleeNumActuals = GetMaxCalleeNumActuals(loc); 576 if (maxCalleeNumActuals > ArgumentsObject::MaxInlinedArgs) { 577 if (script->needsArgsObj()) { 578 JitSpew(JitSpew_WarpTrialInlining, 579 "SKIP: needs arguments object with %u actual args (maximum %u)", 580 maxCalleeNumActuals, ArgumentsObject::MaxInlinedArgs); 581 return false; 582 } 583 // The GetArgument(n) intrinsic in self-hosted code uses MGetInlinedArgument 584 // too, so the same limit applies. 585 if (script->usesArgumentsIntrinsics()) { 586 JitSpew(JitSpew_WarpTrialInlining, 587 "SKIP: uses GetArgument(i) with %u actual args (maximum %u)", 588 maxCalleeNumActuals, ArgumentsObject::MaxInlinedArgs); 589 return false; 590 } 591 } 592 593 if (script->function() && 594 TooManyFormalArguments(script->function()->nargs())) { 595 JitSpew(JitSpew_WarpTrialInlining, "SKIP: Too many formal arguments: %u", 596 unsigned(script->function()->nargs())); 597 return false; 598 } 599 600 if (TooManyFormalArguments(maxCalleeNumActuals)) { 601 JitSpew(JitSpew_WarpTrialInlining, "SKIP: argc too large: %u", 602 unsigned(loc.getCallArgc())); 603 return false; 604 } 605 606 if (!script->hasBaselineScript() && 607 !script->jitScript()->ranBytecodeAnalysis()) { 608 // If we don't have a baseline script, then we maybe haven't done 609 // bytecode analysis yet. It's possible that the script is 610 // uninlineable or can't be Ion compiled. Do bytecode analysis now. 611 TempAllocator temp(&cx->tempLifoAlloc()); 612 BytecodeAnalysis analysis(temp, script); 613 if (!analysis.init(temp)) { 614 JitSpew(JitSpew_WarpTrialInlining, "SKIP: OOM in bytecode analysis"); 615 cx->recoverFromOutOfMemory(); 616 return false; 617 } 618 bool result = true; 619 if (analysis.isInliningDisabled()) { 620 JitSpew(JitSpew_WarpTrialInlining, "SKIP: uninlineable flag"); 621 script->disableIon(); 622 result = false; 623 } 624 if (analysis.isIonDisabled()) { 625 JitSpew(JitSpew_WarpTrialInlining, "SKIP: can't ion-compile"); 626 script->setUninlineable(); 627 result = false; 628 } 629 script->jitScript()->setRanBytecodeAnalysis(); 630 return result; 631 } 632 633 return true; 634 } 635 636 static bool ShouldUseMonomorphicInlining(JSScript* targetScript) { 637 switch (JitOptions.monomorphicInlining) { 638 case UseMonomorphicInlining::Default: 639 // Use heuristics below. 640 break; 641 case UseMonomorphicInlining::Always: 642 return true; 643 case UseMonomorphicInlining::Never: 644 return false; 645 } 646 647 JitScript* jitScript = targetScript->jitScript(); 648 ICScript* icScript = jitScript->icScript(); 649 650 // Check for any ICs which are not monomorphic. The observation here is that 651 // trial inlining can help us a lot in cases where it lets us further 652 // specialize a script. But if it's already monomorphic, it's unlikely that 653 // we will see significant specialization wins from trial inlining, so we 654 // can use a cheaper and simpler inlining strategy. 655 for (size_t i = 0; i < icScript->numICEntries(); i++) { 656 ICEntry& entry = icScript->icEntry(i); 657 ICFallbackStub* fallback = icScript->fallbackStub(i); 658 if (fallback->enteredCount() > 0 || 659 fallback->state().mode() != ICState::Mode::Specialized) { 660 return false; 661 } 662 663 ICStub* firstStub = entry.firstStub(); 664 if (firstStub != fallback) { 665 for (ICStub* next = firstStub->toCacheIRStub()->next(); next; 666 next = next->maybeNext()) { 667 if (next->enteredCount() != 0) { 668 return false; 669 } 670 } 671 } 672 } 673 674 return true; 675 } 676 677 TrialInliningDecision TrialInliner::getInliningDecision(JSScript* targetScript, 678 ICCacheIRStub* stub, 679 BytecodeLocation loc) { 680 #ifdef JS_JITSPEW 681 if (JitSpewEnabled(JitSpew_WarpTrialInlining)) { 682 UniqueChars funName; 683 if (targetScript->function()) { 684 if (JSAtom* atom = targetScript->function()->maybePartialDisplayAtom()) { 685 funName = AtomToPrintableString(cx(), atom); 686 } 687 } 688 689 JitSpew(JitSpew_WarpTrialInlining, 690 "Inlining candidate JSOp::%s (offset=%u): callee script '%s' " 691 "(%s:%u:%u)", 692 CodeName(loc.getOp()), loc.bytecodeToOffset(script_), 693 funName ? funName.get() : "<unnamed>", targetScript->filename(), 694 targetScript->lineno(), targetScript->column().oneOriginValue()); 695 JitSpewIndent spewIndent(JitSpew_WarpTrialInlining); 696 } 697 #endif 698 699 if (!canInline(cx(), targetScript, script_, loc)) { 700 return TrialInliningDecision::NoInline; 701 } 702 703 // Don't inline (direct) recursive calls. This still allows recursion if 704 // called through another function (f => g => f). 705 if (script_ == targetScript) { 706 JitSpew(JitSpew_WarpTrialInlining, "SKIP: recursion"); 707 return TrialInliningDecision::NoInline; 708 } 709 710 // Don't inline if the callee has a loop that was hot enough to enter Warp 711 // via OSR. This helps prevent getting stuck in Baseline code for a long time. 712 if (targetScript->jitScript()->hadIonOSR()) { 713 JitSpew(JitSpew_WarpTrialInlining, "SKIP: had OSR"); 714 return TrialInliningDecision::NoInline; 715 } 716 717 // Ensure the total bytecode size does not exceed ionMaxScriptSize. 718 size_t newTotalSize = 719 inliningRootTotalBytecodeSize() + targetScript->length(); 720 if (newTotalSize > JitOptions.ionMaxScriptSize) { 721 JitSpew(JitSpew_WarpTrialInlining, "SKIP: total size too big"); 722 return TrialInliningDecision::NoInline; 723 } 724 725 uint32_t entryCount = stub->enteredCount(); 726 if (entryCount < JitOptions.inliningEntryThreshold) { 727 JitSpew(JitSpew_WarpTrialInlining, "SKIP: Entry count is %u (minimum %u)", 728 unsigned(entryCount), unsigned(JitOptions.inliningEntryThreshold)); 729 return TrialInliningDecision::NoInline; 730 } 731 732 if (!JitOptions.isSmallFunction(targetScript)) { 733 if (!targetScript->isInlinableLargeFunction()) { 734 JitSpew(JitSpew_WarpTrialInlining, "SKIP: Length is %u (maximum %u)", 735 unsigned(targetScript->length()), 736 unsigned(JitOptions.smallFunctionMaxBytecodeLength)); 737 return TrialInliningDecision::NoInline; 738 } 739 740 JitSpew(JitSpew_WarpTrialInlining, 741 "INFO: Ignored length (%u) of InlinableLargeFunction", 742 unsigned(targetScript->length())); 743 } 744 745 // Decide between trial inlining or monomorphic inlining. 746 if (!ShouldUseMonomorphicInlining(targetScript)) { 747 return TrialInliningDecision::Inline; 748 } 749 750 JitSpewIndent spewIndent(JitSpew_WarpTrialInlining); 751 JitSpew(JitSpew_WarpTrialInlining, "SUCCESS: Inlined monomorphically"); 752 return TrialInliningDecision::MonomorphicInline; 753 } 754 755 ICScript* TrialInliner::createInlinedICScript(JSScript* targetScript, 756 BytecodeLocation loc) { 757 MOZ_ASSERT(targetScript->hasJitScript()); 758 759 InliningRoot* root = getOrCreateInliningRoot(); 760 if (!root) { 761 return nullptr; 762 } 763 764 // We don't have to check for overflow here because we have already 765 // successfully allocated an ICScript with this number of entries 766 // when creating the JitScript for the target function, and we 767 // checked for overflow then. 768 uint32_t fallbackStubsOffset = 769 sizeof(ICScript) + targetScript->numICEntries() * sizeof(ICEntry); 770 uint32_t allocSize = fallbackStubsOffset + 771 targetScript->numICEntries() * sizeof(ICFallbackStub); 772 773 void* raw = cx()->pod_malloc<uint8_t>(allocSize); 774 MOZ_ASSERT(uintptr_t(raw) % alignof(ICScript) == 0); 775 if (!raw) { 776 return nullptr; 777 } 778 779 uint32_t initialWarmUpCount = JitOptions.trialInliningInitialWarmUpCount; 780 781 uint32_t depth = icScript_->depth() + 1; 782 UniquePtr<ICScript> inlinedICScript( 783 new (raw) ICScript(initialWarmUpCount, fallbackStubsOffset, allocSize, 784 depth, targetScript->length(), root)); 785 786 inlinedICScript->initICEntries(cx(), targetScript); 787 788 uint32_t pcOffset = loc.bytecodeToOffset(script_); 789 ICScript* result = inlinedICScript.get(); 790 if (!icScript_->addInlinedChild(cx(), std::move(inlinedICScript), pcOffset)) { 791 return nullptr; 792 } 793 MOZ_ASSERT(result->numICEntries() == targetScript->numICEntries()); 794 795 if (targetScript->needsFunctionEnvironmentObjects()) { 796 result->ensureEnvAllocSite(root->owningScript()); 797 } 798 799 root->addToTotalBytecodeSize(targetScript->length()); 800 801 JitSpewIndent spewIndent(JitSpew_WarpTrialInlining); 802 JitSpew(JitSpew_WarpTrialInlining, 803 "SUCCESS: Outer ICScript: %p Inner ICScript: %p", icScript_, result); 804 805 return result; 806 } 807 808 bool TrialInliner::maybeInlineCall(ICEntry& entry, ICFallbackStub* fallback, 809 BytecodeLocation loc) { 810 ICCacheIRStub* stub = maybeSingleStub(entry); 811 if (!stub) { 812 #ifdef JS_JITSPEW 813 if (fallback->numOptimizedStubs() > 1) { 814 JitSpew(JitSpew_WarpTrialInlining, 815 "Inlining candidate JSOp::%s (offset=%u):", CodeName(loc.getOp()), 816 fallback->pcOffset()); 817 JitSpewIndent spewIndent(JitSpew_WarpTrialInlining); 818 JitSpew(JitSpew_WarpTrialInlining, "SKIP: Polymorphic (%u stubs)", 819 (unsigned)fallback->numOptimizedStubs()); 820 } 821 #endif 822 return true; 823 } 824 825 MOZ_ASSERT(!icScript_->hasInlinedChild(fallback->pcOffset())); 826 827 // Look for a CallScriptedFunction with a known target. 828 Maybe<InlinableCallData> data = FindInlinableCallData(stub); 829 if (data.isNothing()) { 830 return true; 831 } 832 833 MOZ_ASSERT(!data->icScript); 834 835 TrialInliningDecision inlining = getInliningDecision(data->target, stub, loc); 836 // Decide whether to inline the target. 837 if (inlining == TrialInliningDecision::NoInline) { 838 return true; 839 } 840 841 if (inlining == TrialInliningDecision::MonomorphicInline) { 842 fallback->setTrialInliningState(TrialInliningState::MonomorphicInlined); 843 return true; 844 } 845 846 ICScript* newICScript = createInlinedICScript(data->target, loc); 847 if (!newICScript) { 848 return false; 849 } 850 851 CacheIRWriter writer(cx()); 852 Int32OperandId argcId(writer.setInputOperandId(0)); 853 cloneSharedPrefix(stub, data->endOfSharedPrefix, writer); 854 855 writer.callInlinedFunction(data->calleeOperand, argcId, newICScript, 856 data->callFlags, 857 ClampFixedArgc(loc.getCallArgc())); 858 writer.returnFromIC(); 859 860 return replaceICStub(entry, fallback, writer, CacheKind::Call); 861 } 862 863 bool TrialInliner::maybeInlineGetter(ICEntry& entry, ICFallbackStub* fallback, 864 BytecodeLocation loc, CacheKind kind) { 865 ICCacheIRStub* stub = maybeSingleStub(entry); 866 if (!stub) { 867 return true; 868 } 869 870 MOZ_ASSERT(!icScript_->hasInlinedChild(fallback->pcOffset())); 871 872 Maybe<InlinableGetterData> data = FindInlinableGetterData(stub); 873 if (data.isNothing()) { 874 return true; 875 } 876 877 MOZ_ASSERT(!data->icScript); 878 879 TrialInliningDecision inlining = getInliningDecision(data->target, stub, loc); 880 // Decide whether to inline the target. 881 if (inlining == TrialInliningDecision::NoInline) { 882 return true; 883 } 884 885 if (inlining == TrialInliningDecision::MonomorphicInline) { 886 fallback->setTrialInliningState(TrialInliningState::MonomorphicInlined); 887 return true; 888 } 889 890 ICScript* newICScript = createInlinedICScript(data->target, loc); 891 if (!newICScript) { 892 return false; 893 } 894 895 CacheIRWriter writer(cx()); 896 ValOperandId valId(writer.setInputOperandId(0)); 897 if (kind == CacheKind::GetElem) { 898 // Register the key operand. 899 writer.setInputOperandId(1); 900 } 901 cloneSharedPrefix(stub, data->endOfSharedPrefix, writer); 902 903 writer.callInlinedGetterResult(data->receiverOperand, data->calleeOperand, 904 data->target->function(), newICScript, 905 data->sameRealm); 906 writer.returnFromIC(); 907 908 return replaceICStub(entry, fallback, writer, kind); 909 } 910 911 bool TrialInliner::maybeInlineSetter(ICEntry& entry, ICFallbackStub* fallback, 912 BytecodeLocation loc, CacheKind kind) { 913 ICCacheIRStub* stub = maybeSingleStub(entry); 914 if (!stub) { 915 return true; 916 } 917 918 MOZ_ASSERT(!icScript_->hasInlinedChild(fallback->pcOffset())); 919 920 Maybe<InlinableSetterData> data = FindInlinableSetterData(stub); 921 if (data.isNothing()) { 922 return true; 923 } 924 925 MOZ_ASSERT(!data->icScript); 926 927 TrialInliningDecision inlining = getInliningDecision(data->target, stub, loc); 928 // Decide whether to inline the target. 929 if (inlining == TrialInliningDecision::NoInline) { 930 return true; 931 } 932 933 if (inlining == TrialInliningDecision::MonomorphicInline) { 934 fallback->setTrialInliningState(TrialInliningState::MonomorphicInlined); 935 return true; 936 } 937 938 ICScript* newICScript = createInlinedICScript(data->target, loc); 939 if (!newICScript) { 940 return false; 941 } 942 943 CacheIRWriter writer(cx()); 944 ValOperandId objValId(writer.setInputOperandId(0)); 945 ValOperandId rhsValId(writer.setInputOperandId(1)); 946 cloneSharedPrefix(stub, data->endOfSharedPrefix, writer); 947 948 writer.callInlinedSetter(data->receiverOperand, data->calleeOperand, 949 data->target->function(), data->rhsOperand, 950 newICScript, data->sameRealm); 951 writer.returnFromIC(); 952 953 return replaceICStub(entry, fallback, writer, kind); 954 } 955 956 bool TrialInliner::tryInlining() { 957 uint32_t numICEntries = icScript_->numICEntries(); 958 BytecodeLocation startLoc = script_->location(); 959 960 for (uint32_t icIndex = 0; icIndex < numICEntries; icIndex++) { 961 ICEntry& entry = icScript_->icEntry(icIndex); 962 ICFallbackStub* fallback = icScript_->fallbackStub(icIndex); 963 964 if (!TryFoldingStubs(cx(), fallback, script_, icScript_)) { 965 return false; 966 } 967 968 BytecodeLocation loc = 969 startLoc + BytecodeLocationOffset(fallback->pcOffset()); 970 JSOp op = loc.getOp(); 971 switch (op) { 972 case JSOp::Call: 973 case JSOp::CallContent: 974 case JSOp::CallIgnoresRv: 975 case JSOp::CallIter: 976 case JSOp::CallContentIter: 977 case JSOp::New: 978 case JSOp::NewContent: 979 case JSOp::SuperCall: 980 if (!maybeInlineCall(entry, fallback, loc)) { 981 return false; 982 } 983 break; 984 case JSOp::GetProp: 985 if (!maybeInlineGetter(entry, fallback, loc, CacheKind::GetProp)) { 986 return false; 987 } 988 break; 989 case JSOp::GetElem: 990 if (!maybeInlineGetter(entry, fallback, loc, CacheKind::GetElem)) { 991 return false; 992 } 993 break; 994 case JSOp::SetProp: 995 case JSOp::StrictSetProp: 996 if (!maybeInlineSetter(entry, fallback, loc, CacheKind::SetProp)) { 997 return false; 998 } 999 break; 1000 default: 1001 break; 1002 } 1003 } 1004 1005 return true; 1006 } 1007 1008 InliningRoot* TrialInliner::maybeGetInliningRoot() const { 1009 if (auto* root = icScript_->inliningRoot()) { 1010 return root; 1011 } 1012 1013 MOZ_ASSERT(!icScript_->isInlined()); 1014 return script_->jitScript()->inliningRoot(); 1015 } 1016 1017 InliningRoot* TrialInliner::getOrCreateInliningRoot() { 1018 if (auto* root = maybeGetInliningRoot()) { 1019 return root; 1020 } 1021 return script_->jitScript()->getOrCreateInliningRoot(cx(), script_); 1022 } 1023 1024 size_t TrialInliner::inliningRootTotalBytecodeSize() const { 1025 if (auto* root = maybeGetInliningRoot()) { 1026 return root->totalBytecodeSize(); 1027 } 1028 return script_->length(); 1029 } 1030 1031 bool InliningRoot::addInlinedScript(UniquePtr<ICScript> icScript) { 1032 return inlinedScripts_.append(std::move(icScript)); 1033 } 1034 1035 void InliningRoot::trace(JSTracer* trc) { 1036 TraceEdge(trc, &owningScript_, "inlining-root-owning-script"); 1037 for (auto& inlinedScript : inlinedScripts_) { 1038 inlinedScript->trace(trc); 1039 } 1040 } 1041 1042 bool InliningRoot::traceWeak(JSTracer* trc) { 1043 bool allSurvived = true; 1044 for (auto& inlinedScript : inlinedScripts_) { 1045 if (!inlinedScript->traceWeak(trc)) { 1046 allSurvived = false; 1047 } 1048 } 1049 return allSurvived; 1050 } 1051 1052 void InliningRoot::purgeInactiveICScripts() { 1053 mozilla::DebugOnly<uint32_t> totalSize = owningScript_->length(); 1054 1055 for (auto& inlinedScript : inlinedScripts_) { 1056 if (inlinedScript->active()) { 1057 totalSize += inlinedScript->bytecodeSize(); 1058 } else { 1059 MOZ_ASSERT(inlinedScript->bytecodeSize() < totalBytecodeSize_); 1060 totalBytecodeSize_ -= inlinedScript->bytecodeSize(); 1061 } 1062 } 1063 1064 MOZ_ASSERT(totalBytecodeSize_ == totalSize); 1065 1066 Zone* zone = owningScript_->zone(); 1067 1068 inlinedScripts_.eraseIf([zone](auto& inlinedScript) { 1069 if (inlinedScript->active()) { 1070 return false; 1071 } 1072 inlinedScript->prepareForDestruction(zone); 1073 return true; 1074 }); 1075 } 1076 1077 } // namespace jit 1078 } // namespace js