File: Parser\LexerCache.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
// #define COLLECT_STATS
 
using System;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
    internal class LexerCache
    {
        private static readonly ObjectPool<LexerCache> s_lexerCachePool = new ObjectPool<LexerCache>(() => new LexerCache());
 
        private static readonly ObjectPool<CachingIdentityFactory<string, SyntaxKind>> s_keywordKindPool =
            CachingIdentityFactory<string, SyntaxKind>.CreatePool(
                            512,
                            (key) =>
                            {
                                var kind = SyntaxFacts.GetKeywordKind(key);
                                if (kind == SyntaxKind.None)
                                {
                                    kind = SyntaxFacts.GetContextualKeywordKind(key);
                                }
 
                                return kind;
                            });
 
        private TextKeyedCache<SyntaxTrivia>? _triviaMap;
        private TextKeyedCache<SyntaxToken>? _tokenMap;
        private CachingIdentityFactory<string, SyntaxKind>? _keywordKindMap;
        internal const int MaxKeywordLength = 10;
 
        private PooledStringBuilder? _stringBuilder;
        private readonly char[] _identBuffer;
        private SyntaxListBuilder? _leadingTriviaCache;
        private SyntaxListBuilder? _trailingTriviaCache;
 
        private const int LeadingTriviaCacheInitialCapacity = 128;
        private const int TrailingTriviaCacheInitialCapacity = 16;
 
        private LexerCache()
        {
            _identBuffer = new char[32];
        }
 
        public static LexerCache GetInstance()
        {
            return s_lexerCachePool.Allocate();
        }
 
        public void Free()
        {
            if (_keywordKindMap != null)
            {
                _keywordKindMap.Free();
                _keywordKindMap = null;
            }
 
            if (_triviaMap != null)
            {
                _triviaMap.Free();
                _triviaMap = null;
            }
 
            if (_tokenMap != null)
            {
                _tokenMap.Free();
                _tokenMap = null;
            }
 
            if (_stringBuilder != null)
            {
                _stringBuilder.Free();
                _stringBuilder = null;
            }
 
            if (_leadingTriviaCache != null)
            {
                if (_leadingTriviaCache.Capacity > LeadingTriviaCacheInitialCapacity * 2)
                {
                    // Don't reuse _leadingTriviaCache if it has grown too large for pooling.
                    _leadingTriviaCache = null;
                }
                else
                {
                    _leadingTriviaCache.Clear();
                }
            }
 
            if (_trailingTriviaCache != null)
            {
                if (_trailingTriviaCache.Capacity > TrailingTriviaCacheInitialCapacity * 2)
                {
                    // Don't reuse _trailingTriviaCache if it has grown too large for pooling.
                    _trailingTriviaCache = null;
                }
                else
                {
                    _trailingTriviaCache.Clear();
                }
            }
 
            s_lexerCachePool.Free(this);
        }
 
        internal char[] IdentBuffer => _identBuffer;
 
        private TextKeyedCache<SyntaxTrivia> TriviaMap
        {
            get
            {
                _triviaMap ??= TextKeyedCache<SyntaxTrivia>.GetInstance();
 
                return _triviaMap;
            }
        }
 
        private TextKeyedCache<SyntaxToken> TokenMap
        {
            get
            {
                _tokenMap ??= TextKeyedCache<SyntaxToken>.GetInstance();
 
                return _tokenMap;
            }
        }
 
        private CachingIdentityFactory<string, SyntaxKind> KeywordKindMap
        {
            get
            {
                _keywordKindMap ??= s_keywordKindPool.Allocate();
 
                return _keywordKindMap;
            }
        }
 
        internal PooledStringBuilder StringBuilder
        {
            get
            {
                _stringBuilder ??= PooledStringBuilder.GetInstance();
 
                return _stringBuilder;
            }
        }
 
        internal SyntaxListBuilder LeadingTriviaCache
        {
            get
            {
                _leadingTriviaCache ??= new SyntaxListBuilder(LeadingTriviaCacheInitialCapacity);
 
                return _leadingTriviaCache;
            }
        }
 
        internal SyntaxListBuilder TrailingTriviaCache
        {
            get
            {
                _trailingTriviaCache ??= new SyntaxListBuilder(TrailingTriviaCacheInitialCapacity);
 
                return _trailingTriviaCache;
            }
        }
 
        internal bool TryGetKeywordKind(string key, out SyntaxKind kind)
        {
            if (key.Length > MaxKeywordLength)
            {
                kind = SyntaxKind.None;
                return false;
            }
 
            kind = KeywordKindMap.GetOrMakeValue(key);
            return kind != SyntaxKind.None;
        }
 
        internal SyntaxTrivia LookupTrivia<TArg>(
            char[] textBuffer,
            int keyStart,
            int keyLength,
            int hashCode,
            Func<TArg, SyntaxTrivia> createTriviaFunction,
            TArg data)
        {
            var value = TriviaMap.FindItem(textBuffer, keyStart, keyLength, hashCode);
 
            if (value == null)
            {
                value = createTriviaFunction(data);
                TriviaMap.AddItem(textBuffer, keyStart, keyLength, hashCode, value);
            }
 
            return value;
        }
 
        // TODO: remove this when done tweaking this cache.
#if COLLECT_STATS
        private static int hits = 0;
        private static int misses = 0;
 
        private static void Hit()
        {
            var h = System.Threading.Interlocked.Increment(ref hits);
 
            if (h % 10000 == 0)
            {
                Console.WriteLine(h * 100 / (h + misses));
            }
        }
 
        private static void Miss()
        {
            System.Threading.Interlocked.Increment(ref misses);
        }
#endif
 
        internal SyntaxToken LookupToken<TArg>(
            char[] textBuffer,
            int keyStart,
            int keyLength,
            int hashCode,
            Func<TArg, SyntaxToken> createTokenFunction,
            TArg data)
        {
            var value = TokenMap.FindItem(textBuffer, keyStart, keyLength, hashCode);
 
            if (value == null)
            {
#if COLLECT_STATS
                    Miss();
#endif
                value = createTokenFunction(data);
                TokenMap.AddItem(textBuffer, keyStart, keyLength, hashCode, value);
            }
            else
            {
#if COLLECT_STATS
                    Hit();
#endif
            }
 
            return value;
        }
    }
}