IntrinsicISizesCache.h (6299B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_IntrinsicISizesCache_h 8 #define mozilla_IntrinsicISizesCache_h 9 10 #include "nsIFrame.h" 11 12 namespace mozilla { 13 14 // Some frame classes keep a cache of intrinsic inline sizes. This class 15 // encapsulates the logic for caching them depending on the IntrinsicSizeInput. 16 // 17 // The cache is intended to take as little space as possible 18 // (max(sizeof(nscoord) * 2, sizeof(void*))), when there are no percentage-size 19 // dependencies. 20 struct IntrinsicISizesCache final { 21 IntrinsicISizesCache() { 22 new (&mInline) InlineCache(); 23 MOZ_ASSERT(IsInline()); 24 } 25 26 ~IntrinsicISizesCache() { delete GetOutOfLine(); } 27 28 template <typename Compute> 29 nscoord GetOrSet(nsIFrame& aFrame, IntrinsicISizeType aType, 30 const IntrinsicSizeInput& aInput, Compute aCompute) { 31 bool dependentOnPercentBSize = aFrame.HasAnyStateBits( 32 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); 33 nscoord value = Get(dependentOnPercentBSize, aType, aInput); 34 if (value != kNotFound) { 35 return value; 36 } 37 value = aCompute(); 38 // Inside of aCompute(), we might have newly discovered that we do have a 39 // descendant whose intrinsic isize depends on our bsize; so we check that 40 // state bit again before updating the cache. 41 dependentOnPercentBSize = aFrame.HasAnyStateBits( 42 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); 43 Set(dependentOnPercentBSize, aType, aInput, value); 44 return value; 45 } 46 47 void Clear() { 48 if (auto* ool = GetOutOfLine()) { 49 ool->mCacheWithPercentageBasis.Clear(); 50 ool->mCacheWithoutPercentageBasis.Clear(); 51 ool->mLastPercentageBasis.reset(); 52 } else { 53 mInline.Clear(); 54 } 55 } 56 57 private: 58 // We use nscoord_MAX rather than NS_INTRINSIC_ISIZE_UNKNOWN as our sentinel 59 // value so that our high bit is always free. 60 static constexpr nscoord kNotFound = nscoord_MAX; 61 62 nscoord Get(bool aDependentOnPercentBSize, IntrinsicISizeType aType, 63 const IntrinsicSizeInput& aInput) const { 64 const bool usePercentageAwareCache = 65 aDependentOnPercentBSize && aInput.HasSomePercentageBasisForChildren(); 66 if (!usePercentageAwareCache) { 67 if (auto* ool = GetOutOfLine()) { 68 return ool->mCacheWithoutPercentageBasis.Get(aType); 69 } 70 return mInline.Get(aType); 71 } 72 if (auto* ool = GetOutOfLine()) { 73 if (ool->mLastPercentageBasis == aInput.mPercentageBasisForChildren) { 74 return ool->mCacheWithPercentageBasis.Get(aType); 75 } 76 } 77 return kNotFound; 78 } 79 80 void Set(bool aDependentOnPercentBSize, IntrinsicISizeType aType, 81 const IntrinsicSizeInput& aInput, nscoord aValue) { 82 // Intrinsic sizes should be nonnegative, so this std::max clamping should 83 // rarely be necessary except in cases of integer overflow. We have to be 84 // strict about it, though, because of how we (ab)use the high bit 85 // (see kHighBit) 86 aValue = std::max(aValue, 0); 87 const bool usePercentageAwareCache = 88 aDependentOnPercentBSize && aInput.HasSomePercentageBasisForChildren(); 89 if (usePercentageAwareCache) { 90 auto* ool = EnsureOutOfLine(); 91 if (ool->mLastPercentageBasis != aInput.mPercentageBasisForChildren) { 92 ool->mLastPercentageBasis = aInput.mPercentageBasisForChildren; 93 ool->mCacheWithPercentageBasis.Clear(); 94 } 95 ool->mCacheWithPercentageBasis.Set(aType, aValue); 96 } else if (auto* ool = GetOutOfLine()) { 97 ool->mCacheWithoutPercentageBasis.Set(aType, aValue); 98 } else { 99 mInline.Set(aType, aValue); 100 // No inline value should be able to cause us to misinterpret our 101 // representation as out-of-line, because intrinsic isizes should always 102 // be non-negative. 103 MOZ_DIAGNOSTIC_ASSERT(IsInline()); 104 } 105 } 106 107 struct InlineCache { 108 nscoord mCachedMinISize = kNotFound; 109 nscoord mCachedPrefISize = kNotFound; 110 111 nscoord Get(IntrinsicISizeType aType) const { 112 return aType == IntrinsicISizeType::MinISize ? mCachedMinISize 113 : mCachedPrefISize; 114 } 115 void Set(IntrinsicISizeType aType, nscoord aValue) { 116 MOZ_ASSERT(aValue >= 0); 117 if (aType == IntrinsicISizeType::MinISize) { 118 mCachedMinISize = aValue; 119 } else { 120 mCachedPrefISize = aValue; 121 } 122 } 123 124 void Clear() { *this = {}; } 125 }; 126 127 struct OutOfLineCache { 128 InlineCache mCacheWithoutPercentageBasis; 129 InlineCache mCacheWithPercentageBasis; 130 Maybe<LogicalSize> mLastPercentageBasis; 131 }; 132 133 // If the high bit of mOutOfLine is 1, then it points to an OutOfLineCache. 134 union { 135 InlineCache mInline; 136 struct { 137 #ifndef HAVE_64BIT_BUILD 138 uintptr_t mPadding = 0; 139 #endif 140 uintptr_t mOutOfLine = 0; 141 }; 142 }; 143 144 static constexpr uintptr_t kHighBit = uintptr_t(1) 145 << (sizeof(void*) * CHAR_BIT - 1); 146 147 bool IsOutOfLine() const { 148 #ifdef HAVE_64BIT_BUILD 149 return mOutOfLine & kHighBit; 150 #else 151 return mPadding & kHighBit; 152 #endif 153 } 154 bool IsInline() const { return !IsOutOfLine(); } 155 OutOfLineCache* EnsureOutOfLine() { 156 if (auto* ool = GetOutOfLine()) { 157 return ool; 158 } 159 auto inlineCache = mInline; 160 auto* ool = new OutOfLineCache(); 161 ool->mCacheWithoutPercentageBasis = inlineCache; 162 #ifdef HAVE_64BIT_BUILD 163 MOZ_ASSERT((reinterpret_cast<uintptr_t>(ool) & kHighBit) == 0); 164 mOutOfLine = reinterpret_cast<uintptr_t>(ool) | kHighBit; 165 #else 166 mOutOfLine = reinterpret_cast<uintptr_t>(ool); 167 mPadding = kHighBit; 168 #endif 169 MOZ_ASSERT(IsOutOfLine()); 170 return ool; 171 } 172 173 OutOfLineCache* GetOutOfLine() const { 174 if (!IsOutOfLine()) { 175 return nullptr; 176 } 177 #ifdef HAVE_64BIT_BUILD 178 return reinterpret_cast<OutOfLineCache*>(mOutOfLine & ~kHighBit); 179 #else 180 return reinterpret_cast<OutOfLineCache*>(mOutOfLine); 181 #endif 182 } 183 }; 184 185 static_assert(sizeof(IntrinsicISizesCache) == 8, "Unexpected cache size"); 186 187 } // namespace mozilla 188 189 #endif