ICState.h (7449B)
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 #ifndef jit_ICState_h 8 #define jit_ICState_h 9 10 #include "jit/JitOptions.h" 11 12 namespace js { 13 namespace jit { 14 15 // Used to track trial inlining status for a Baseline IC. 16 // See also setTrialInliningState below. 17 enum class TrialInliningState : uint8_t { 18 Initial = 0, 19 Candidate, 20 Inlined, 21 MonomorphicInlined, 22 Failure, 23 }; 24 25 // ICState stores information about a Baseline or Ion IC. 26 class ICState { 27 public: 28 // When we attach the maximum number of stubs, we discard all stubs and 29 // transition the IC to Megamorphic to attach stubs that are more generic 30 // (handle more cases). If we again attach the maximum number of stubs, we 31 // transition to Generic and (depending on the IC) will either attach a 32 // single stub that handles everything or stop attaching new stubs. 33 // 34 // We also transition to Generic when we repeatedly fail to attach a stub, 35 // to avoid wasting time trying. 36 enum class Mode : uint8_t { Specialized = 0, Megamorphic, Generic }; 37 38 private: 39 uint8_t mode_ : 2; 40 41 // The TrialInliningState for a Baseline IC. 42 uint8_t trialInliningState_ : 3; 43 44 // Whether WarpOracle created a snapshot based on stubs attached to this 45 // Baseline IC. 46 bool usedByTranspiler_ : 1; 47 48 // Whether stubs attached to this IC have been folded together into a single 49 // stub. Used as a hint when attaching additional stubs to try folding them 50 // too. The folded stub may be removed later by GC sweeping so this is not 51 // exact. 52 bool mayHaveFoldedStub_ : 1; 53 54 // Number of optimized stubs currently attached to this IC. 55 uint8_t numOptimizedStubs_; 56 57 // Number of times we failed to attach a stub. 58 uint8_t numFailures_; 59 60 static const size_t MaxOptimizedStubs = 6; 61 62 void setMode(Mode mode) { 63 mode_ = uint32_t(mode); 64 MOZ_ASSERT(Mode(mode_) == mode, "mode must fit in bitfield"); 65 } 66 67 void transition(Mode mode) { 68 MOZ_ASSERT(mode > this->mode()); 69 setMode(mode); 70 numFailures_ = 0; 71 } 72 73 MOZ_ALWAYS_INLINE size_t maxFailures() const { 74 // Allow more failures if we attached stubs. 75 static_assert(MaxOptimizedStubs == 6, 76 "numFailures_/maxFailures should fit in uint8_t"); 77 size_t res = 5 + size_t(40) * numOptimizedStubs_; 78 MOZ_ASSERT(res <= UINT8_MAX, "numFailures_ should not overflow"); 79 return res; 80 } 81 82 public: 83 ICState() { reset(); } 84 85 Mode mode() const { return Mode(mode_); } 86 size_t numOptimizedStubs() const { return numOptimizedStubs_; } 87 bool hasFailures() const { return (numFailures_ != 0); } 88 bool newStubIsFirstStub() const { 89 return (mode() == Mode::Specialized && numOptimizedStubs() == 0); 90 } 91 92 MOZ_ALWAYS_INLINE bool canAttachStub() const { 93 // Note: we cannot assert that numOptimizedStubs_ <= MaxOptimizedStubs 94 // because old-style baseline ICs may attach more stubs than 95 // MaxOptimizedStubs allows. 96 if (mode() == Mode::Generic || JitOptions.disableCacheIR) { 97 return false; 98 } 99 return true; 100 } 101 102 [[nodiscard]] MOZ_ALWAYS_INLINE bool shouldTransition() { 103 // Note: we cannot assert that numOptimizedStubs_ <= MaxOptimizedStubs 104 // because old-style baseline ICs may attach more stubs than 105 // MaxOptimizedStubs allows. 106 if (mode() == Mode::Generic) { 107 return false; 108 } 109 if (numOptimizedStubs_ < MaxOptimizedStubs && 110 numFailures_ < maxFailures()) { 111 return false; 112 } 113 return true; 114 } 115 116 // If this returns true, we transitioned to a new mode and the caller 117 // should discard all stubs. 118 [[nodiscard]] MOZ_ALWAYS_INLINE bool maybeTransition() { 119 if (!shouldTransition()) { 120 return false; 121 } 122 forceTransition(); 123 return true; 124 } 125 126 MOZ_ALWAYS_INLINE void forceTransition() { 127 if (numFailures_ >= maxFailures() || mode() == Mode::Megamorphic) { 128 transition(Mode::Generic); 129 } else { 130 MOZ_ASSERT(mode() == Mode::Specialized); 131 transition(Mode::Megamorphic); 132 } 133 } 134 135 void reset() { 136 setMode(Mode::Specialized); 137 #ifdef DEBUG 138 if (JitOptions.forceMegamorphicICs) { 139 setMode(Mode::Megamorphic); 140 } 141 #endif 142 trialInliningState_ = uint32_t(TrialInliningState::Initial); 143 usedByTranspiler_ = false; 144 mayHaveFoldedStub_ = false; 145 numOptimizedStubs_ = 0; 146 numFailures_ = 0; 147 } 148 void trackAttached() { 149 // We'd like to assert numOptimizedStubs_ < MaxOptimizedStubs, but 150 // since this code is also used for non-CacheIR Baseline stubs, assert 151 // < 16 for now. Note that we do have the stronger assert in other 152 // methods, because they are only used by CacheIR ICs. 153 MOZ_ASSERT(numOptimizedStubs_ < 16); 154 numOptimizedStubs_++; 155 // As a heuristic, reduce the failure count after each successful attach 156 // to delay hitting Generic mode. Reset to 1 instead of 0 so that 157 // code which inspects state can distinguish no-failures from rare-failures. 158 numFailures_ = std::min(numFailures_, static_cast<uint8_t>(1)); 159 } 160 void trackNotAttached() { 161 // Note: we can't assert numFailures_ < maxFailures() because 162 // maxFailures() depends on numOptimizedStubs_ and it's possible a 163 // GC discarded stubs before we got here. 164 numFailures_++; 165 MOZ_ASSERT(numFailures_ > 0, "numFailures_ should not overflow"); 166 } 167 void trackUnlinkedStub() { 168 MOZ_ASSERT(numOptimizedStubs_ > 0); 169 numOptimizedStubs_--; 170 } 171 void trackUnlinkedAllStubs() { numOptimizedStubs_ = 0; } 172 173 void clearUsedByTranspiler() { usedByTranspiler_ = false; } 174 void setUsedByTranspiler() { usedByTranspiler_ = true; } 175 bool usedByTranspiler() const { return usedByTranspiler_; } 176 177 void clearMayHaveFoldedStub() { mayHaveFoldedStub_ = false; } 178 void setMayHaveFoldedStub() { mayHaveFoldedStub_ = true; } 179 bool mayHaveFoldedStub() const { return mayHaveFoldedStub_; } 180 181 TrialInliningState trialInliningState() const { 182 return TrialInliningState(trialInliningState_); 183 } 184 void setTrialInliningState(TrialInliningState state) { 185 #ifdef DEBUG 186 // Moving to the Failure state is always valid. The other states should 187 // happen in this order: 188 // 189 // Initial -> Candidate --> Inlined 190 // \-> MonomorphicInlined 191 // 192 // This ensures we perform trial inlining at most once per IC site. 193 if (state != TrialInliningState::Failure) { 194 switch (trialInliningState()) { 195 case TrialInliningState::Initial: 196 MOZ_ASSERT(state == TrialInliningState::Candidate); 197 break; 198 case TrialInliningState::Candidate: 199 MOZ_ASSERT(state == TrialInliningState::Candidate || 200 state == TrialInliningState::Inlined || 201 state == TrialInliningState::MonomorphicInlined); 202 break; 203 case TrialInliningState::Inlined: 204 case TrialInliningState::MonomorphicInlined: 205 case TrialInliningState::Failure: 206 MOZ_CRASH("Inlined and Failure can only change to Failure"); 207 break; 208 } 209 } 210 #endif 211 212 trialInliningState_ = uint32_t(state); 213 MOZ_ASSERT(trialInliningState() == state, 214 "TrialInliningState must fit in bitfield"); 215 } 216 }; 217 218 } // namespace jit 219 } // namespace js 220 221 #endif /* jit_ICState_h */