File: Parser\LexerCache.cs
Web Access
Project: src\roslyn\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 System.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;
using Microsoft.CodeAnalysis.Text;
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 LookupWhitespaceTrivia(
            in SlidingTextWindow textWindow,
            int lexemeStartPosition,
            int hashCode)
        {
            var span = TextSpan.FromBounds(lexemeStartPosition, textWindow.Position);
            Debug.Assert(span.Length > 0);

            if (textWindow.TryGetTextIfWithinWindow(span, out var lexemeTextSpan))
            {
                var value = TriviaMap.FindItem(lexemeTextSpan, hashCode);
                if (value == null)
                {
                    value = SyntaxFactory.Whitespace(textWindow.GetText(lexemeStartPosition, intern: true));
                    TriviaMap.AddItem(lexemeTextSpan, hashCode, value);
                }

                return value;
            }

            // Otherwise, if it's outside of the window, just grab from the underlying text.
            return SyntaxFactory.Whitespace(textWindow.GetText(lexemeStartPosition, intern: true));
        }

        // 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>(
            ReadOnlySpan<char> textBuffer,
            int hashCode,
            Func<TArg, SyntaxToken> createTokenFunction,
            TArg data)
        {
            var value = TokenMap.FindItem(textBuffer, hashCode);

            if (value == null)
            {
#if COLLECT_STATS
                    Miss();
#endif
                value = createTokenFunction(data);
                TokenMap.AddItem(textBuffer, hashCode, value);
            }
            else
            {
#if COLLECT_STATS
                    Hit();
#endif
            }

            return value;
        }
    }
}