/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_IntrinsicISizesCache_h #define mozilla_IntrinsicISizesCache_h #include "nsIFrame.h" namespace mozilla { // Some frame classes keep a cache of intrinsic inline sizes. This class // encapsulates the logic for caching them depending on the IntrinsicSizeInput. // // The cache is intended to take as little space as possible // (max(sizeof(nscoord) * 2, sizeof(void*))), when there are no percentage-size // dependencies. struct IntrinsicISizesCache final { IntrinsicISizesCache() { new (&mInline) InlineCache(); MOZ_ASSERT(IsInline()); } ~IntrinsicISizesCache() { delete GetOutOfLine(); } template nscoord GetOrSet(nsIFrame& aFrame, IntrinsicISizeType aType, const IntrinsicSizeInput& aInput, Compute aCompute) { bool dependentOnPercentBSize = aFrame.HasAnyStateBits( NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); nscoord value = Get(dependentOnPercentBSize, aType, aInput); if (value != kNotFound) { return value; } value = aCompute(); // Inside of aCompute(), we might have newly discovered that we do have a // descendant whose intrinsic isize depends on our bsize; so we check that // state bit again before updating the cache. dependentOnPercentBSize = aFrame.HasAnyStateBits( NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); Set(dependentOnPercentBSize, aType, aInput, value); return value; } void Clear() { if (auto* ool = GetOutOfLine()) { ool->mCacheWithPercentageBasis.Clear(); ool->mCacheWithoutPercentageBasis.Clear(); ool->mLastPercentageBasis.reset(); } else { mInline.Clear(); } } private: // We use nscoord_MAX rather than NS_INTRINSIC_ISIZE_UNKNOWN as our sentinel // value so that our high bit is always free. static constexpr nscoord kNotFound = nscoord_MAX; nscoord Get(bool aDependentOnPercentBSize, IntrinsicISizeType aType, const IntrinsicSizeInput& aInput) const { const bool usePercentageAwareCache = aDependentOnPercentBSize && aInput.HasSomePercentageBasisForChildren(); if (!usePercentageAwareCache) { if (auto* ool = GetOutOfLine()) { return ool->mCacheWithoutPercentageBasis.Get(aType); } return mInline.Get(aType); } if (auto* ool = GetOutOfLine()) { if (ool->mLastPercentageBasis == aInput.mPercentageBasisForChildren) { return ool->mCacheWithPercentageBasis.Get(aType); } } return kNotFound; } void Set(bool aDependentOnPercentBSize, IntrinsicISizeType aType, const IntrinsicSizeInput& aInput, nscoord aValue) { // Intrinsic sizes should be nonnegative, so this std::max clamping should // rarely be necessary except in cases of integer overflow. We have to be // strict about it, though, because of how we (ab)use the high bit // (see kHighBit) aValue = std::max(aValue, 0); const bool usePercentageAwareCache = aDependentOnPercentBSize && aInput.HasSomePercentageBasisForChildren(); if (usePercentageAwareCache) { auto* ool = EnsureOutOfLine(); if (ool->mLastPercentageBasis != aInput.mPercentageBasisForChildren) { ool->mLastPercentageBasis = aInput.mPercentageBasisForChildren; ool->mCacheWithPercentageBasis.Clear(); } ool->mCacheWithPercentageBasis.Set(aType, aValue); } else if (auto* ool = GetOutOfLine()) { ool->mCacheWithoutPercentageBasis.Set(aType, aValue); } else { mInline.Set(aType, aValue); // No inline value should be able to cause us to misinterpret our // representation as out-of-line, because intrinsic isizes should always // be non-negative. MOZ_DIAGNOSTIC_ASSERT(IsInline()); } } struct InlineCache { nscoord mCachedMinISize = kNotFound; nscoord mCachedPrefISize = kNotFound; nscoord Get(IntrinsicISizeType aType) const { return aType == IntrinsicISizeType::MinISize ? mCachedMinISize : mCachedPrefISize; } void Set(IntrinsicISizeType aType, nscoord aValue) { MOZ_ASSERT(aValue >= 0); if (aType == IntrinsicISizeType::MinISize) { mCachedMinISize = aValue; } else { mCachedPrefISize = aValue; } } void Clear() { *this = {}; } }; struct OutOfLineCache { InlineCache mCacheWithoutPercentageBasis; InlineCache mCacheWithPercentageBasis; Maybe mLastPercentageBasis; }; // If the high bit of mOutOfLine is 1, then it points to an OutOfLineCache. union { InlineCache mInline; struct { #ifndef HAVE_64BIT_BUILD uintptr_t mPadding = 0; #endif uintptr_t mOutOfLine = 0; }; }; static constexpr uintptr_t kHighBit = uintptr_t(1) << (sizeof(void*) * CHAR_BIT - 1); bool IsOutOfLine() const { #ifdef HAVE_64BIT_BUILD return mOutOfLine & kHighBit; #else return mPadding & kHighBit; #endif } bool IsInline() const { return !IsOutOfLine(); } OutOfLineCache* EnsureOutOfLine() { if (auto* ool = GetOutOfLine()) { return ool; } auto inlineCache = mInline; auto* ool = new OutOfLineCache(); ool->mCacheWithoutPercentageBasis = inlineCache; #ifdef HAVE_64BIT_BUILD MOZ_ASSERT((reinterpret_cast(ool) & kHighBit) == 0); mOutOfLine = reinterpret_cast(ool) | kHighBit; #else mOutOfLine = reinterpret_cast(ool); mPadding = kHighBit; #endif MOZ_ASSERT(IsOutOfLine()); return ool; } OutOfLineCache* GetOutOfLine() const { if (!IsOutOfLine()) { return nullptr; } #ifdef HAVE_64BIT_BUILD return reinterpret_cast(mOutOfLine & ~kHighBit); #else return reinterpret_cast(mOutOfLine); #endif } }; static_assert(sizeof(IntrinsicISizesCache) == 8, "Unexpected cache size"); } // namespace mozilla #endif