| 1 | // Licensed to the .NET Foundation under one or more agreements. |
| 2 | // The .NET Foundation licenses this file to you under the MIT license. |
| 3 | // See the LICENSE file in the project root for more information. |
| 4 | |
| 5 | // Inlining Support |
| 6 | // |
| 7 | // This file contains enum and class definitions and related |
| 8 | // information that the jit uses to make inlining decisions. |
| 9 | // |
| 10 | // -- ENUMS -- |
| 11 | // |
| 12 | // InlineCallFrequency - rough assessment of call site frequency |
| 13 | // InlineDecision - overall decision made about an inline |
| 14 | // InlineTarget - target of a particular observation |
| 15 | // InlineImpact - impact of a particular observation |
| 16 | // InlineObservation - facts observed when considering an inline |
| 17 | // |
| 18 | // -- CLASSES -- |
| 19 | // |
| 20 | // InlineResult - accumulates observations, consults with policy |
| 21 | // InlineCandidateInfo - basic information needed for inlining |
| 22 | // InlArgInfo - information about a candidate's argument |
| 23 | // InlLclVarInfo - information about a candidate's local variable |
| 24 | // InlineInfo - detailed information needed for inlining |
| 25 | // InlineContext - class, remembers what inlines happened |
| 26 | // InlinePolicy - class, determines policy for inlining |
| 27 | // InlineStrategy - class, determines overall inline strategy |
| 28 | // |
| 29 | // Enums are used throughout to provide various descriptions. |
| 30 | // |
| 31 | // There are 4 sitations where inline candidacy is evaluated. In each |
| 32 | // case an InlineResult is allocated on the stack to collect |
| 33 | // information about the inline candidate. Each InlineResult refers |
| 34 | // to an InlinePolicy. |
| 35 | // |
| 36 | // 1. Importer Candidate Screen (impMarkInlineCandidate) |
| 37 | // |
| 38 | // Creates: InlineCandidateInfo |
| 39 | // |
| 40 | // During importing, the IL being imported is scanned to identify |
| 41 | // inline candidates. This happens both when the root method is being |
| 42 | // imported as well as when prospective inlines are being imported. |
| 43 | // Candidates are marked in the IL and given an InlineCandidateInfo. |
| 44 | // |
| 45 | // 2. Inlining Optimization Pass -- candidates (fgInline) |
| 46 | // |
| 47 | // Creates / Uses: InlineContext |
| 48 | // Creates: InlineInfo, InlArgInfo, InlLocalVarInfo |
| 49 | // |
| 50 | // During the inlining optimation pass, each candidate is further |
| 51 | // analyzed. Viable candidates will eventually inspire creation of an |
| 52 | // InlineInfo and a set of InlArgInfos (for call arguments) and |
| 53 | // InlLocalVarInfos (for callee locals). |
| 54 | // |
| 55 | // The analysis will also examine InlineContexts from relevant prior |
| 56 | // inlines. If the inline is successful, a new InlineContext will be |
| 57 | // created to remember this inline. In DEBUG builds, failing inlines |
| 58 | // also create InlineContexts. |
| 59 | // |
| 60 | // 3. Inlining Optimization Pass -- non-candidates (fgNoteNotInlineCandidate) |
| 61 | // |
| 62 | // Creates / Uses: InlineContext |
| 63 | // |
| 64 | // In DEBUG, the jit also searches for non-candidate calls to try |
| 65 | // and get a complete picture of the set of failed inlines. |
| 66 | // |
| 67 | // 4. Prejit suitability screen (compCompileHelper) |
| 68 | // |
| 69 | // When prejitting, each method is scanned to see if it is a viable |
| 70 | // inline candidate. |
| 71 | |
| 72 | #ifndef _INLINE_H_ |
| 73 | #define _INLINE_H_ |
| 74 | |
| 75 | #include "jit.h" |
| 76 | #include "gentree.h" |
| 77 | |
| 78 | // Implementation limits |
| 79 | |
| 80 | const unsigned int MAX_INL_ARGS = 32; // does not include obj pointer |
| 81 | const unsigned int MAX_INL_LCLS = 32; |
| 82 | |
| 83 | // Forward declarations |
| 84 | |
| 85 | class InlineStrategy; |
| 86 | |
| 87 | // InlineCallsiteFrequency gives a rough classification of how |
| 88 | // often a call site will be excuted at runtime. |
| 89 | |
| 90 | enum class InlineCallsiteFrequency |
| 91 | { |
| 92 | UNUSED, // n/a |
| 93 | RARE, // once in a blue moon |
| 94 | BORING, // normal call site |
| 95 | WARM, // seen during profiling |
| 96 | LOOP, // in a loop |
| 97 | HOT // very frequent |
| 98 | }; |
| 99 | |
| 100 | // InlineDecision describes the various states the jit goes through when |
| 101 | // evaluating an inline candidate. It is distinct from CorInfoInline |
| 102 | // because it must capture internal states that don't get reported back |
| 103 | // to the runtime. |
| 104 | |
| 105 | enum class InlineDecision |
| 106 | { |
| 107 | UNDECIDED, |
| 108 | CANDIDATE, |
| 109 | SUCCESS, |
| 110 | FAILURE, |
| 111 | NEVER |
| 112 | }; |
| 113 | |
| 114 | // Translate a decision into a CorInfoInline for reporting back to the runtime. |
| 115 | |
| 116 | CorInfoInline InlGetCorInfoInlineDecision(InlineDecision d); |
| 117 | |
| 118 | // Get a string describing this InlineDecision |
| 119 | |
| 120 | const char* InlGetDecisionString(InlineDecision d); |
| 121 | |
| 122 | // True if this InlineDecsion describes a failing inline |
| 123 | |
| 124 | bool InlDecisionIsFailure(InlineDecision d); |
| 125 | |
| 126 | // True if this decision describes a successful inline |
| 127 | |
| 128 | bool InlDecisionIsSuccess(InlineDecision d); |
| 129 | |
| 130 | // True if this InlineDecision is a never inline decision |
| 131 | |
| 132 | bool InlDecisionIsNever(InlineDecision d); |
| 133 | |
| 134 | // True if this InlineDecision describes a viable candidate |
| 135 | |
| 136 | bool InlDecisionIsCandidate(InlineDecision d); |
| 137 | |
| 138 | // True if this InlineDecsion describes a decision |
| 139 | |
| 140 | bool InlDecisionIsDecided(InlineDecision d); |
| 141 | |
| 142 | // InlineTarget describes the possible targets of an inline observation. |
| 143 | |
| 144 | enum class InlineTarget |
| 145 | { |
| 146 | CALLEE, // observation applies to all calls to this callee |
| 147 | CALLER, // observation applies to all calls made by this caller |
| 148 | CALLSITE // observation applies to a specific call site |
| 149 | }; |
| 150 | |
| 151 | // InlineImpact describe the possible impact of an inline observation. |
| 152 | |
| 153 | enum class InlineImpact |
| 154 | { |
| 155 | FATAL, // inlining impossible, unsafe to evaluate further |
| 156 | FUNDAMENTAL, // inlining impossible for fundamental reasons, deeper exploration safe |
| 157 | LIMITATION, // inlining impossible because of jit limitations, deeper exploration safe |
| 158 | PERFORMANCE, // inlining inadvisable because of performance concerns |
| 159 | INFORMATION // policy-free observation to provide data for later decision making |
| 160 | }; |
| 161 | |
| 162 | // InlineObservation describes the set of possible inline observations. |
| 163 | |
| 164 | enum class InlineObservation |
| 165 | { |
| 166 | #define INLINE_OBSERVATION(name, type, description, impact, scope) scope##_##name, |
| 167 | #include "inline.def" |
| 168 | #undef INLINE_OBSERVATION |
| 169 | }; |
| 170 | |
| 171 | #ifdef DEBUG |
| 172 | |
| 173 | // Sanity check the observation value |
| 174 | |
| 175 | bool InlIsValidObservation(InlineObservation obs); |
| 176 | |
| 177 | #endif // DEBUG |
| 178 | |
| 179 | // Get a string describing this observation |
| 180 | |
| 181 | const char* InlGetObservationString(InlineObservation obs); |
| 182 | |
| 183 | // Get a string describing the target of this observation |
| 184 | |
| 185 | const char* InlGetTargetString(InlineObservation obs); |
| 186 | |
| 187 | // Get a string describing the impact of this observation |
| 188 | |
| 189 | const char* InlGetImpactString(InlineObservation obs); |
| 190 | |
| 191 | // Get the target of this observation |
| 192 | |
| 193 | InlineTarget InlGetTarget(InlineObservation obs); |
| 194 | |
| 195 | // Get the impact of this observation |
| 196 | |
| 197 | InlineImpact InlGetImpact(InlineObservation obs); |
| 198 | |
| 199 | // InlinePolicy is an abstract base class for a family of inline |
| 200 | // policies. |
| 201 | |
| 202 | class InlinePolicy |
| 203 | { |
| 204 | public: |
| 205 | // Factory method for getting policies |
| 206 | static InlinePolicy* GetPolicy(Compiler* compiler, bool isPrejitRoot); |
| 207 | |
| 208 | // Obligatory virtual dtor |
| 209 | virtual ~InlinePolicy() |
| 210 | { |
| 211 | } |
| 212 | |
| 213 | // Get the current decision |
| 214 | InlineDecision GetDecision() const |
| 215 | { |
| 216 | return m_Decision; |
| 217 | } |
| 218 | |
| 219 | // Get the observation responsible for the result |
| 220 | InlineObservation GetObservation() const |
| 221 | { |
| 222 | return m_Observation; |
| 223 | } |
| 224 | |
| 225 | // Policy observations |
| 226 | virtual void NoteSuccess() = 0; |
| 227 | virtual void NoteBool(InlineObservation obs, bool value) = 0; |
| 228 | virtual void NoteFatal(InlineObservation obs) = 0; |
| 229 | virtual void NoteInt(InlineObservation obs, int value) = 0; |
| 230 | |
| 231 | // Optional observations. Most policies ignore these. |
| 232 | virtual void NoteContext(InlineContext* context) |
| 233 | { |
| 234 | (void)context; |
| 235 | } |
| 236 | virtual void NoteOffset(IL_OFFSETX offset) |
| 237 | { |
| 238 | (void)offset; |
| 239 | } |
| 240 | |
| 241 | // Policy determinations |
| 242 | virtual void DetermineProfitability(CORINFO_METHOD_INFO* methodInfo) = 0; |
| 243 | |
| 244 | // Policy policies |
| 245 | virtual bool PropagateNeverToRuntime() const = 0; |
| 246 | |
| 247 | // Policy estimates |
| 248 | virtual int CodeSizeEstimate() = 0; |
| 249 | |
| 250 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 251 | |
| 252 | // Record observation for prior failure |
| 253 | virtual void NotePriorFailure(InlineObservation obs) = 0; |
| 254 | |
| 255 | // Name of the policy |
| 256 | virtual const char* GetName() const = 0; |
| 257 | // Detailed data value dump |
| 258 | virtual void DumpData(FILE* file) const |
| 259 | { |
| 260 | } |
| 261 | // Detailed data name dump |
| 262 | virtual void DumpSchema(FILE* file) const |
| 263 | { |
| 264 | } |
| 265 | // True if this is the inline targeted by data collection |
| 266 | bool IsDataCollectionTarget() |
| 267 | { |
| 268 | return m_IsDataCollectionTarget; |
| 269 | } |
| 270 | |
| 271 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 272 | |
| 273 | protected: |
| 274 | InlinePolicy(bool isPrejitRoot) |
| 275 | : m_Decision(InlineDecision::UNDECIDED) |
| 276 | , m_Observation(InlineObservation::CALLEE_UNUSED_INITIAL) |
| 277 | , m_IsPrejitRoot(isPrejitRoot) |
| 278 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 279 | , m_IsDataCollectionTarget(false) |
| 280 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 281 | |
| 282 | { |
| 283 | // empty |
| 284 | } |
| 285 | |
| 286 | private: |
| 287 | // No copying or assignment supported |
| 288 | InlinePolicy(const InlinePolicy&) = delete; |
| 289 | InlinePolicy& operator=(const InlinePolicy&) = delete; |
| 290 | |
| 291 | protected: |
| 292 | InlineDecision m_Decision; |
| 293 | InlineObservation m_Observation; |
| 294 | bool m_IsPrejitRoot; |
| 295 | |
| 296 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 297 | |
| 298 | bool m_IsDataCollectionTarget; |
| 299 | |
| 300 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 301 | }; |
| 302 | |
| 303 | // InlineResult summarizes what is known about the viability of a |
| 304 | // particular inline candiate. |
| 305 | |
| 306 | class InlineResult |
| 307 | { |
| 308 | public: |
| 309 | // Construct a new InlineResult to help evaluate a |
| 310 | // particular call for inlining. |
| 311 | InlineResult(Compiler* compiler, GenTreeCall* call, GenTreeStmt* stmt, const char* description); |
| 312 | |
| 313 | // Construct a new InlineResult to evaluate a particular |
| 314 | // method to see if it is inlineable. |
| 315 | InlineResult(Compiler* compiler, CORINFO_METHOD_HANDLE method, const char* description); |
| 316 | |
| 317 | // Has the policy determined this inline should fail? |
| 318 | bool IsFailure() const |
| 319 | { |
| 320 | return InlDecisionIsFailure(m_Policy->GetDecision()); |
| 321 | } |
| 322 | |
| 323 | // Has the policy determined this inline will succeed? |
| 324 | bool IsSuccess() const |
| 325 | { |
| 326 | return InlDecisionIsSuccess(m_Policy->GetDecision()); |
| 327 | } |
| 328 | |
| 329 | // Has the policy determined this inline will fail, |
| 330 | // and that the callee should never be inlined? |
| 331 | bool IsNever() const |
| 332 | { |
| 333 | return InlDecisionIsNever(m_Policy->GetDecision()); |
| 334 | } |
| 335 | |
| 336 | // Has the policy determined this inline attempt is still viable? |
| 337 | bool IsCandidate() const |
| 338 | { |
| 339 | return InlDecisionIsCandidate(m_Policy->GetDecision()); |
| 340 | } |
| 341 | |
| 342 | // Has the policy determined this inline attempt is still viable |
| 343 | // and is a discretionary inline? |
| 344 | bool IsDiscretionaryCandidate() const |
| 345 | { |
| 346 | bool result = InlDecisionIsCandidate(m_Policy->GetDecision()) && |
| 347 | (m_Policy->GetObservation() == InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE); |
| 348 | |
| 349 | return result; |
| 350 | } |
| 351 | |
| 352 | // Has the policy made a determination? |
| 353 | bool IsDecided() const |
| 354 | { |
| 355 | return InlDecisionIsDecided(m_Policy->GetDecision()); |
| 356 | } |
| 357 | |
| 358 | // NoteSuccess means the all the various checks have passed and |
| 359 | // the inline can happen. |
| 360 | void NoteSuccess() |
| 361 | { |
| 362 | assert(IsCandidate()); |
| 363 | m_Policy->NoteSuccess(); |
| 364 | } |
| 365 | |
| 366 | // Make a true observation, and update internal state |
| 367 | // appropriately. |
| 368 | // |
| 369 | // Caller is expected to call isFailure after this to see whether |
| 370 | // more observation is desired. |
| 371 | void Note(InlineObservation obs) |
| 372 | { |
| 373 | m_Policy->NoteBool(obs, true); |
| 374 | } |
| 375 | |
| 376 | // Make a boolean observation, and update internal state |
| 377 | // appropriately. |
| 378 | // |
| 379 | // Caller is expected to call isFailure after this to see whether |
| 380 | // more observation is desired. |
| 381 | void NoteBool(InlineObservation obs, bool value) |
| 382 | { |
| 383 | m_Policy->NoteBool(obs, value); |
| 384 | } |
| 385 | |
| 386 | // Make an observation that must lead to immediate failure. |
| 387 | void NoteFatal(InlineObservation obs) |
| 388 | { |
| 389 | m_Policy->NoteFatal(obs); |
| 390 | assert(IsFailure()); |
| 391 | } |
| 392 | |
| 393 | // Make an observation with an int value |
| 394 | void NoteInt(InlineObservation obs, int value) |
| 395 | { |
| 396 | m_Policy->NoteInt(obs, value); |
| 397 | } |
| 398 | |
| 399 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 400 | |
| 401 | // Record observation from an earlier failure. |
| 402 | void NotePriorFailure(InlineObservation obs) |
| 403 | { |
| 404 | m_Policy->NotePriorFailure(obs); |
| 405 | assert(IsFailure()); |
| 406 | } |
| 407 | |
| 408 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 409 | |
| 410 | // Determine if this inline is profitable |
| 411 | void DetermineProfitability(CORINFO_METHOD_INFO* methodInfo) |
| 412 | { |
| 413 | m_Policy->DetermineProfitability(methodInfo); |
| 414 | } |
| 415 | |
| 416 | // Ensure details of this inlining process are appropriately |
| 417 | // reported when the result goes out of scope. |
| 418 | ~InlineResult() |
| 419 | { |
| 420 | Report(); |
| 421 | } |
| 422 | |
| 423 | // The observation leading to this particular result |
| 424 | InlineObservation GetObservation() const |
| 425 | { |
| 426 | return m_Policy->GetObservation(); |
| 427 | } |
| 428 | |
| 429 | // The callee handle for this result |
| 430 | CORINFO_METHOD_HANDLE GetCallee() const |
| 431 | { |
| 432 | return m_Callee; |
| 433 | } |
| 434 | |
| 435 | // The call being considered |
| 436 | GenTreeCall* GetCall() const |
| 437 | { |
| 438 | return m_Call; |
| 439 | } |
| 440 | |
| 441 | // Result that can be reported back to the runtime |
| 442 | CorInfoInline Result() const |
| 443 | { |
| 444 | return InlGetCorInfoInlineDecision(m_Policy->GetDecision()); |
| 445 | } |
| 446 | |
| 447 | // String describing the decision made |
| 448 | const char* ResultString() const |
| 449 | { |
| 450 | return InlGetDecisionString(m_Policy->GetDecision()); |
| 451 | } |
| 452 | |
| 453 | // String describing the reason for the decision |
| 454 | const char* ReasonString() const |
| 455 | { |
| 456 | return InlGetObservationString(m_Policy->GetObservation()); |
| 457 | } |
| 458 | |
| 459 | // Get the policy that evaluated this result. |
| 460 | InlinePolicy* GetPolicy() const |
| 461 | { |
| 462 | return m_Policy; |
| 463 | } |
| 464 | |
| 465 | // SetReported indicates that this particular result doesn't need |
| 466 | // to be reported back to the runtime, either because the runtime |
| 467 | // already knows, or we aren't actually inlining yet. |
| 468 | void SetReported() |
| 469 | { |
| 470 | m_Reported = true; |
| 471 | } |
| 472 | |
| 473 | // Get the InlineContext for this inline |
| 474 | InlineContext* GetInlineContext() const |
| 475 | { |
| 476 | return m_InlineContext; |
| 477 | } |
| 478 | |
| 479 | private: |
| 480 | // No copying or assignment allowed. |
| 481 | InlineResult(const InlineResult&) = delete; |
| 482 | InlineResult& operator=(const InlineResult&) = delete; |
| 483 | |
| 484 | // Report/log/dump decision as appropriate |
| 485 | void Report(); |
| 486 | |
| 487 | Compiler* m_RootCompiler; |
| 488 | InlinePolicy* m_Policy; |
| 489 | GenTreeCall* m_Call; |
| 490 | InlineContext* m_InlineContext; |
| 491 | CORINFO_METHOD_HANDLE m_Caller; // immediate caller's handle |
| 492 | CORINFO_METHOD_HANDLE m_Callee; |
| 493 | const char* m_Description; |
| 494 | bool m_Reported; |
| 495 | }; |
| 496 | |
| 497 | // GuardedDevirtualizationCandidateInfo provides information about |
| 498 | // a potential target of a virtual call. |
| 499 | |
| 500 | struct GuardedDevirtualizationCandidateInfo |
| 501 | { |
| 502 | CORINFO_CLASS_HANDLE guardedClassHandle; |
| 503 | CORINFO_METHOD_HANDLE guardedMethodHandle; |
| 504 | void* stubAddr; |
| 505 | }; |
| 506 | |
| 507 | // InlineCandidateInfo provides basic information about a particular |
| 508 | // inline candidate. |
| 509 | // |
| 510 | // It is a superset of GuardedDevirtualizationCandidateInfo: calls |
| 511 | // can start out as GDv candidates and turn into inline candidates |
| 512 | |
| 513 | struct InlineCandidateInfo : public GuardedDevirtualizationCandidateInfo |
| 514 | { |
| 515 | CORINFO_METHOD_INFO methInfo; |
| 516 | CORINFO_METHOD_HANDLE ilCallerHandle; // the logical IL caller of this inlinee. |
| 517 | CORINFO_CLASS_HANDLE clsHandle; |
| 518 | CORINFO_CONTEXT_HANDLE exactContextHnd; |
| 519 | GenTree* retExpr; |
| 520 | DWORD dwRestrictions; |
| 521 | unsigned preexistingSpillTemp; |
| 522 | unsigned clsAttr; |
| 523 | unsigned methAttr; |
| 524 | CorInfoInitClassResult initClassResult; |
| 525 | var_types fncRetType; |
| 526 | bool exactContextNeedsRuntimeLookup; |
| 527 | }; |
| 528 | |
| 529 | // InlArgInfo describes inline candidate argument properties. |
| 530 | |
| 531 | struct InlArgInfo |
| 532 | { |
| 533 | GenTree* argNode; // caller node for this argument |
| 534 | GenTree* argBashTmpNode; // tmp node created, if it may be replaced with actual arg |
| 535 | unsigned argTmpNum; // the argument tmp number |
| 536 | unsigned argIsUsed : 1; // is this arg used at all? |
| 537 | unsigned argIsInvariant : 1; // the argument is a constant or a local variable address |
| 538 | unsigned argIsLclVar : 1; // the argument is a local variable |
| 539 | unsigned argIsThis : 1; // the argument is the 'this' pointer |
| 540 | unsigned argHasSideEff : 1; // the argument has side effects |
| 541 | unsigned argHasGlobRef : 1; // the argument has a global ref |
| 542 | unsigned argHasCallerLocalRef : 1; // the argument value depends on an aliased caller local |
| 543 | unsigned argHasTmp : 1; // the argument will be evaluated to a temp |
| 544 | unsigned argHasLdargaOp : 1; // Is there LDARGA(s) operation on this argument? |
| 545 | unsigned argHasStargOp : 1; // Is there STARG(s) operation on this argument? |
| 546 | unsigned argIsByRefToStructLocal : 1; // Is this arg an address of a struct local or a normed struct local or a |
| 547 | // field in them? |
| 548 | }; |
| 549 | |
| 550 | // InlLclVarInfo describes inline candidate argument and local variable properties. |
| 551 | |
| 552 | struct InlLclVarInfo |
| 553 | { |
| 554 | typeInfo lclVerTypeInfo; |
| 555 | var_types lclTypeInfo; |
| 556 | unsigned lclHasLdlocaOp : 1; // Is there LDLOCA(s) operation on this local? |
| 557 | unsigned lclHasStlocOp : 1; // Is there a STLOC on this local? |
| 558 | unsigned lclHasMultipleStlocOp : 1; // Is there more than one STLOC on this local |
| 559 | unsigned lclIsPinned : 1; |
| 560 | }; |
| 561 | |
| 562 | // InlineInfo provides detailed information about a particular inline candidate. |
| 563 | |
| 564 | struct InlineInfo |
| 565 | { |
| 566 | Compiler* InlinerCompiler; // The Compiler instance for the caller (i.e. the inliner) |
| 567 | Compiler* InlineRoot; // The Compiler instance that is the root of the inlining tree of which the owner of "this" is |
| 568 | // a member. |
| 569 | |
| 570 | CORINFO_METHOD_HANDLE fncHandle; |
| 571 | InlineCandidateInfo* inlineCandidateInfo; |
| 572 | |
| 573 | InlineResult* inlineResult; |
| 574 | |
| 575 | GenTree* retExpr; // The return expression of the inlined candidate. |
| 576 | CORINFO_CLASS_HANDLE retExprClassHnd; |
| 577 | bool retExprClassHndIsExact; |
| 578 | |
| 579 | CORINFO_CONTEXT_HANDLE tokenLookupContextHandle; // The context handle that will be passed to |
| 580 | // impTokenLookupContextHandle in Inlinee's Compiler. |
| 581 | |
| 582 | unsigned argCnt; |
| 583 | InlArgInfo inlArgInfo[MAX_INL_ARGS + 1]; |
| 584 | int lclTmpNum[MAX_INL_LCLS]; // map local# -> temp# (-1 if unused) |
| 585 | InlLclVarInfo lclVarInfo[MAX_INL_LCLS + MAX_INL_ARGS + 1]; // type information from local sig |
| 586 | |
| 587 | unsigned numberOfGcRefLocals; // Number of TYP_REF and TYP_BYREF locals |
| 588 | |
| 589 | bool HasGcRefLocals() const |
| 590 | { |
| 591 | return numberOfGcRefLocals > 0; |
| 592 | } |
| 593 | |
| 594 | bool thisDereferencedFirst; |
| 595 | unsigned typeContextArg; |
| 596 | |
| 597 | #ifdef FEATURE_SIMD |
| 598 | bool hasSIMDTypeArgLocalOrReturn; |
| 599 | #endif // FEATURE_SIMD |
| 600 | |
| 601 | GenTreeCall* iciCall; // The GT_CALL node to be inlined. |
| 602 | GenTreeStmt* iciStmt; // The statement iciCall is in. |
| 603 | BasicBlock* iciBlock; // The basic block iciStmt is in. |
| 604 | }; |
| 605 | |
| 606 | // InlineContext tracks the inline history in a method. |
| 607 | // |
| 608 | // Notes: |
| 609 | // |
| 610 | // InlineContexts form a tree with the root method as the root and |
| 611 | // inlines as children. Nested inlines are represented as granchildren |
| 612 | // and so on. |
| 613 | // |
| 614 | // Leaves in the tree represent successful inlines of leaf methods. |
| 615 | // In DEBUG builds we also keep track of failed inline attempts. |
| 616 | // |
| 617 | // During inlining, all statements in the IR refer back to the |
| 618 | // InlineContext that is responsible for those statements existing. |
| 619 | // This makes it possible to detect recursion and to keep track of the |
| 620 | // depth of each inline attempt. |
| 621 | |
| 622 | class InlineContext |
| 623 | { |
| 624 | // InlineContexts are created by InlineStrategies |
| 625 | friend class InlineStrategy; |
| 626 | |
| 627 | public: |
| 628 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 629 | |
| 630 | // Dump the full subtree, including failures |
| 631 | void Dump(unsigned indent = 0); |
| 632 | |
| 633 | // Dump only the success subtree, with rich data |
| 634 | void DumpData(unsigned indent = 0); |
| 635 | |
| 636 | // Dump full subtree in xml format |
| 637 | void DumpXml(FILE* file = stderr, unsigned indent = 0); |
| 638 | |
| 639 | // Get callee handle |
| 640 | CORINFO_METHOD_HANDLE GetCallee() const |
| 641 | { |
| 642 | return m_Callee; |
| 643 | } |
| 644 | |
| 645 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 646 | |
| 647 | // Get the parent context for this context. |
| 648 | InlineContext* GetParent() const |
| 649 | { |
| 650 | return m_Parent; |
| 651 | } |
| 652 | |
| 653 | // Get the code pointer for this context. |
| 654 | BYTE* GetCode() const |
| 655 | { |
| 656 | return m_Code; |
| 657 | } |
| 658 | |
| 659 | // True if this context describes a successful inline. |
| 660 | bool IsSuccess() const |
| 661 | { |
| 662 | return m_Success; |
| 663 | } |
| 664 | |
| 665 | // Get the observation that supported or disqualified this inline. |
| 666 | InlineObservation GetObservation() const |
| 667 | { |
| 668 | return m_Observation; |
| 669 | } |
| 670 | |
| 671 | // Get the IL code size for this inline. |
| 672 | unsigned GetILSize() const |
| 673 | { |
| 674 | return m_ILSize; |
| 675 | } |
| 676 | |
| 677 | // Get the native code size estimate for this inline. |
| 678 | unsigned GetCodeSizeEstimate() const |
| 679 | { |
| 680 | return m_CodeSizeEstimate; |
| 681 | } |
| 682 | |
| 683 | // Get the offset of the call site |
| 684 | IL_OFFSETX GetOffset() const |
| 685 | { |
| 686 | return m_Offset; |
| 687 | } |
| 688 | |
| 689 | // True if this is the root context |
| 690 | bool IsRoot() const |
| 691 | { |
| 692 | return m_Parent == nullptr; |
| 693 | } |
| 694 | |
| 695 | bool IsDevirtualized() const |
| 696 | { |
| 697 | return m_Devirtualized; |
| 698 | } |
| 699 | |
| 700 | bool IsGuarded() const |
| 701 | { |
| 702 | return m_Guarded; |
| 703 | } |
| 704 | |
| 705 | bool IsUnboxed() const |
| 706 | { |
| 707 | return m_Unboxed; |
| 708 | } |
| 709 | |
| 710 | private: |
| 711 | InlineContext(InlineStrategy* strategy); |
| 712 | |
| 713 | private: |
| 714 | InlineStrategy* m_InlineStrategy; // overall strategy |
| 715 | InlineContext* m_Parent; // logical caller (parent) |
| 716 | InlineContext* m_Child; // first child |
| 717 | InlineContext* m_Sibling; // next child of the parent |
| 718 | BYTE* m_Code; // address of IL buffer for the method |
| 719 | unsigned m_ILSize; // size of IL buffer for the method |
| 720 | IL_OFFSETX m_Offset; // call site location within parent |
| 721 | InlineObservation m_Observation; // what lead to this inline |
| 722 | int m_CodeSizeEstimate; // in bytes * 10 |
| 723 | bool m_Success : 1; // true if this was a successful inline |
| 724 | bool m_Devirtualized : 1; // true if this was a devirtualized call |
| 725 | bool m_Guarded : 1; // true if this was a guarded call |
| 726 | bool m_Unboxed : 1; // true if this call now invokes the unboxed entry |
| 727 | |
| 728 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 729 | |
| 730 | InlinePolicy* m_Policy; // policy that evaluated this inline |
| 731 | CORINFO_METHOD_HANDLE m_Callee; // handle to the method |
| 732 | unsigned m_TreeID; // ID of the GenTreeCall |
| 733 | unsigned m_Ordinal; // Ordinal number of this inline |
| 734 | |
| 735 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 736 | }; |
| 737 | |
| 738 | // The InlineStrategy holds the per-method persistent inline state. |
| 739 | // It is responsible for providing information that applies to |
| 740 | // multiple inlining decisions. |
| 741 | |
| 742 | class InlineStrategy |
| 743 | { |
| 744 | |
| 745 | public: |
| 746 | // Construct a new inline strategy. |
| 747 | InlineStrategy(Compiler* compiler); |
| 748 | |
| 749 | // Create context for a successful inline. |
| 750 | InlineContext* NewSuccess(InlineInfo* inlineInfo); |
| 751 | |
| 752 | // Create context for a failing inline. |
| 753 | InlineContext* NewFailure(GenTreeStmt* stmt, InlineResult* inlineResult); |
| 754 | |
| 755 | // Compiler associated with this strategy |
| 756 | Compiler* GetCompiler() const |
| 757 | { |
| 758 | return m_Compiler; |
| 759 | } |
| 760 | |
| 761 | // Root context |
| 762 | InlineContext* GetRootContext(); |
| 763 | |
| 764 | // Context for the last sucessful inline |
| 765 | // (or root if no inlines) |
| 766 | InlineContext* GetLastContext() const |
| 767 | { |
| 768 | return m_LastContext; |
| 769 | } |
| 770 | |
| 771 | // Get IL size for maximum allowable inline |
| 772 | unsigned GetMaxInlineILSize() const |
| 773 | { |
| 774 | return m_MaxInlineSize; |
| 775 | } |
| 776 | |
| 777 | // Get depth of maximum allowable inline |
| 778 | unsigned GetMaxInlineDepth() const |
| 779 | { |
| 780 | return m_MaxInlineDepth; |
| 781 | } |
| 782 | |
| 783 | // Number of successful inlines into the root |
| 784 | unsigned GetInlineCount() const |
| 785 | { |
| 786 | return m_InlineCount; |
| 787 | } |
| 788 | |
| 789 | // Return the current code size estimate for this method |
| 790 | int GetCurrentSizeEstimate() const |
| 791 | { |
| 792 | return m_CurrentSizeEstimate; |
| 793 | } |
| 794 | |
| 795 | // Return the initial code size estimate for this method |
| 796 | int GetInitialSizeEstimate() const |
| 797 | { |
| 798 | return m_InitialSizeEstimate; |
| 799 | } |
| 800 | |
| 801 | // Inform strategy that there's another call |
| 802 | void NoteCall() |
| 803 | { |
| 804 | m_CallCount++; |
| 805 | } |
| 806 | |
| 807 | // Inform strategy that there's a new inline candidate. |
| 808 | void NoteCandidate() |
| 809 | { |
| 810 | m_CandidateCount++; |
| 811 | } |
| 812 | |
| 813 | // Inform strategy that a candidate was assessed and determined to |
| 814 | // be unprofitable. |
| 815 | void NoteUnprofitable() |
| 816 | { |
| 817 | m_UnprofitableCandidateCount++; |
| 818 | } |
| 819 | |
| 820 | // Inform strategy that a candidate has passed screening |
| 821 | // and that the jit will attempt to inline. |
| 822 | void NoteAttempt(InlineResult* result); |
| 823 | |
| 824 | // Inform strategy that jit is about to import the inlinee IL. |
| 825 | void NoteImport() |
| 826 | { |
| 827 | m_ImportCount++; |
| 828 | } |
| 829 | |
| 830 | // Inform strategy about the inline decision for a prejit root |
| 831 | void NotePrejitDecision(const InlineResult& r) |
| 832 | { |
| 833 | m_PrejitRootDecision = r.GetPolicy()->GetDecision(); |
| 834 | m_PrejitRootObservation = r.GetPolicy()->GetObservation(); |
| 835 | } |
| 836 | |
| 837 | // Dump csv header for inline stats to indicated file. |
| 838 | static void (FILE* f); |
| 839 | |
| 840 | // Dump csv data for inline stats to indicated file. |
| 841 | void DumpCsvData(FILE* f); |
| 842 | |
| 843 | // See if an inline of this size would fit within the current jit |
| 844 | // time budget. |
| 845 | bool BudgetCheck(unsigned ilSize); |
| 846 | |
| 847 | // Check if this method is not allowing inlines. |
| 848 | static bool IsNoInline(ICorJitInfo* info, CORINFO_METHOD_HANDLE method); |
| 849 | |
| 850 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 851 | |
| 852 | // Dump textual description of inlines done so far. |
| 853 | void Dump(bool showBudget); |
| 854 | |
| 855 | // Dump data-format description of inlines done so far. |
| 856 | void DumpData(); |
| 857 | void DumpDataEnsurePolicyIsSet(); |
| 858 | void (FILE* file); |
| 859 | void DumpDataSchema(FILE* file); |
| 860 | void DumpDataContents(FILE* file); |
| 861 | |
| 862 | // Dump xml-formatted description of inlines |
| 863 | void DumpXml(FILE* file = stderr, unsigned indent = 0); |
| 864 | static void FinalizeXml(FILE* file = stderr); |
| 865 | |
| 866 | // Cache for file position of this method in the inline xml |
| 867 | long GetMethodXmlFilePosition() |
| 868 | { |
| 869 | return m_MethodXmlFilePosition; |
| 870 | } |
| 871 | |
| 872 | void SetMethodXmlFilePosition(long val) |
| 873 | { |
| 874 | m_MethodXmlFilePosition = val; |
| 875 | } |
| 876 | |
| 877 | // Set up or access random state (for use by RandomPolicy) |
| 878 | CLRRandom* GetRandom(); |
| 879 | |
| 880 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 881 | |
| 882 | // Some inline limit values |
| 883 | enum |
| 884 | { |
| 885 | ALWAYS_INLINE_SIZE = 16, |
| 886 | IMPLEMENTATION_MAX_INLINE_SIZE = _UI16_MAX, |
| 887 | IMPLEMENTATION_MAX_INLINE_DEPTH = 1000 |
| 888 | }; |
| 889 | |
| 890 | private: |
| 891 | // Create a context for the root method. |
| 892 | InlineContext* NewRoot(); |
| 893 | |
| 894 | // Accounting updates for a successful or failed inline. |
| 895 | void NoteOutcome(InlineContext* context); |
| 896 | |
| 897 | // Cap on allowable increase in jit time due to inlining. |
| 898 | // Multiplicative, so BUDGET = 10 means up to 10x increase |
| 899 | // in jit time. |
| 900 | enum |
| 901 | { |
| 902 | BUDGET = 10 |
| 903 | }; |
| 904 | |
| 905 | // Estimate the jit time change because of this inline. |
| 906 | int EstimateTime(InlineContext* context); |
| 907 | |
| 908 | // EstimateTime helpers |
| 909 | int EstimateRootTime(unsigned ilSize); |
| 910 | int EstimateInlineTime(unsigned ilSize); |
| 911 | |
| 912 | // Estimate native code size change because of this inline. |
| 913 | int EstimateSize(InlineContext* context); |
| 914 | |
| 915 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 916 | static bool ; |
| 917 | static bool ; |
| 918 | static CritSecObject s_XmlWriterLock; |
| 919 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 920 | |
| 921 | Compiler* m_Compiler; |
| 922 | InlineContext* m_RootContext; |
| 923 | InlinePolicy* m_LastSuccessfulPolicy; |
| 924 | InlineContext* m_LastContext; |
| 925 | InlineDecision m_PrejitRootDecision; |
| 926 | InlineObservation m_PrejitRootObservation; |
| 927 | unsigned m_CallCount; |
| 928 | unsigned m_CandidateCount; |
| 929 | unsigned m_AlwaysCandidateCount; |
| 930 | unsigned m_ForceCandidateCount; |
| 931 | unsigned m_DiscretionaryCandidateCount; |
| 932 | unsigned m_UnprofitableCandidateCount; |
| 933 | unsigned m_ImportCount; |
| 934 | unsigned m_InlineCount; |
| 935 | unsigned m_MaxInlineSize; |
| 936 | unsigned m_MaxInlineDepth; |
| 937 | int m_InitialTimeBudget; |
| 938 | int m_InitialTimeEstimate; |
| 939 | int m_CurrentTimeBudget; |
| 940 | int m_CurrentTimeEstimate; |
| 941 | int m_InitialSizeEstimate; |
| 942 | int m_CurrentSizeEstimate; |
| 943 | bool m_HasForceViaDiscretionary; |
| 944 | |
| 945 | #if defined(DEBUG) || defined(INLINE_DATA) |
| 946 | long m_MethodXmlFilePosition; |
| 947 | CLRRandom* m_Random; |
| 948 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
| 949 | }; |
| 950 | |
| 951 | #endif // _INLINE_H_ |
| 952 | |