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