diff --git a/src/PolySharp.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs b/src/PolySharp.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs index c9fbf03..f7c8c29 100644 --- a/src/PolySharp.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs +++ b/src/PolySharp.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace PolySharp.SourceGenerators.Helpers; @@ -12,13 +14,8 @@ namespace PolySharp.SourceGenerators.Helpers; /// A helper type to build sequences of values with pooled buffers. /// /// The type of items to create sequences for. -internal struct ImmutableArrayBuilder : IDisposable +internal ref struct ImmutableArrayBuilder { - /// - /// The shared instance to share objects. - /// - private static readonly ObjectPool SharedObjectPool = new(static () => new Writer()); - /// /// The rented instance to use. /// @@ -30,7 +27,7 @@ internal struct ImmutableArrayBuilder : IDisposable /// A instance to write data to. public static ImmutableArrayBuilder Rent() { - return new(SharedObjectPool.Allocate()); + return new(new Writer()); } /// @@ -42,22 +39,21 @@ private ImmutableArrayBuilder(Writer writer) this.writer = writer; } - /// - /// Gets the data written to the underlying buffer so far, as a . - /// - public readonly ReadOnlySpan WrittenSpan + /// + public readonly int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.writer!.WrittenSpan; + get => this.writer!.Count; } /// - /// Gets the number of elements currently written in the current instance. + /// Gets the data written to the underlying buffer so far, as a . /// - public readonly int Count + [UnscopedRef] + public readonly ReadOnlySpan WrittenSpan { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.writer!.WrittenSpan.Length; + get => this.writer!.WrittenSpan; } /// @@ -70,7 +66,7 @@ public readonly void Add(T item) /// Adds the specified items to the end of the array. /// /// The items to add at the end of the array. - public readonly void AddRange(ReadOnlySpan items) + public readonly void AddRange(scoped ReadOnlySpan items) { this.writer!.AddRange(items); } @@ -95,30 +91,25 @@ public override readonly string ToString() return this.writer!.WrittenSpan.ToString(); } - /// + /// public void Dispose() { Writer? writer = this.writer; this.writer = null; - if (writer is not null) - { - writer.Clear(); - - SharedObjectPool.Free(writer); - } + writer?.Dispose(); } /// /// A class handling the actual buffer writing. /// - private sealed class Writer + private sealed class Writer : IDisposable { /// /// The underlying array. /// - private T[] array; + private T?[]? array; /// /// The starting offset within . @@ -130,23 +121,22 @@ private sealed class Writer /// public Writer() { - if (typeof(T) == typeof(char)) - { - this.array = new T[1024]; - } - else - { - this.array = new T[8]; - } - + this.array = ArrayPool.Shared.Rent(typeof(T) == typeof(char) ? 1024 : 8); this.index = 0; } + /// + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.index; + } + /// public ReadOnlySpan WrittenSpan { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.array, 0, this.index); + get => new(this.array!, 0, this.index); } /// @@ -154,7 +144,7 @@ public void Add(T value) { EnsureCapacity(1); - this.array[this.index++] = value; + this.array![this.index++] = value; } /// @@ -162,22 +152,22 @@ public void AddRange(ReadOnlySpan items) { EnsureCapacity(items.Length); - items.CopyTo(this.array.AsSpan(this.index)); + items.CopyTo(this.array.AsSpan(this.index)!); this.index += items.Length; } - /// - /// Clears the items in the current writer. - /// - public void Clear() + /// + public void Dispose() { - if (typeof(T) != typeof(char)) + T?[]? array = this.array; + + this.array = null; + + if (array is not null) { - this.array.AsSpan(0, this.index).Clear(); + ArrayPool.Shared.Return(array, clearArray: typeof(T) != typeof(char)); } - - this.index = 0; } /// @@ -187,7 +177,7 @@ public void Clear() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int requestedSize) { - if (requestedSize > this.array.Length - this.index) + if (requestedSize > this.array!.Length - this.index) { ResizeBuffer(requestedSize); } @@ -201,27 +191,15 @@ private void EnsureCapacity(int requestedSize) private void ResizeBuffer(int sizeHint) { int minimumSize = this.index + sizeHint; - int requestedSize = Math.Max(this.array.Length * 2, minimumSize); - T[] newArray = new T[requestedSize]; + T?[] oldArray = this.array!; + T?[] newArray = ArrayPool.Shared.Rent(minimumSize); - Array.Copy(this.array, newArray, this.index); + Array.Copy(oldArray, newArray, this.index); this.array = newArray; - } - } -} -/// -/// Private helpers for the type. -/// -file static class ImmutableArrayBuilder -{ - /// - /// Throws an for "index". - /// - public static void ThrowArgumentOutOfRangeExceptionForIndex() - { - throw new ArgumentOutOfRangeException("index"); + ArrayPool.Shared.Return(oldArray, clearArray: typeof(T) != typeof(char)); + } } } \ No newline at end of file diff --git a/src/PolySharp.SourceGenerators/Helpers/ObjectPool{T}.cs b/src/PolySharp.SourceGenerators/Helpers/ObjectPool{T}.cs deleted file mode 100644 index ae3d7a4..0000000 --- a/src/PolySharp.SourceGenerators/Helpers/ObjectPool{T}.cs +++ /dev/null @@ -1,163 +0,0 @@ -// 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. - -// Ported from Roslyn, see: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs. - -using System; -using System.Runtime.CompilerServices; -using System.Threading; - -namespace PolySharp.SourceGenerators.Helpers; - -/// -/// -/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose -/// is that limited number of frequently used objects can be kept in the pool for further recycling. -/// -/// -/// Notes: -/// -/// -/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there -/// is no space in the pool, extra returned objects will be dropped. -/// -/// -/// It is implied that if object was obtained from a pool, the caller will return it back in -/// a relatively short time. Keeping checked out objects for long durations is ok, but -/// reduces usefulness of pooling. Just new up your own. -/// -/// -/// -/// -/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. -/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new". -/// -/// -/// The type of objects to pool. -internal sealed class ObjectPool - where T : class -{ - /// - /// The factory is stored for the lifetime of the pool. We will call this only when pool needs to - /// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()". - /// - private readonly Func factory; - - /// - /// The array of cached items. - /// - private readonly Element[] items; - - /// - /// Storage for the pool objects. The first item is stored in a dedicated field - /// because we expect to be able to satisfy most requests from it. - /// - private T? firstItem; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The input factory to produce items. - public ObjectPool(Func factory) - : this(factory, Environment.ProcessorCount * 2) - { - } - - /// - /// Creates a new instance with the specified parameters. - /// - /// The input factory to produce items. - /// The pool size to use. - public ObjectPool(Func factory, int size) - { - this.factory = factory; - this.items = new Element[size - 1]; - } - - /// - /// Produces a instance. - /// - /// The returned item to use. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T Allocate() - { - T? item = this.firstItem; - - if (item is null || item != Interlocked.CompareExchange(ref this.firstItem, null, item)) - { - item = AllocateSlow(); - } - - return item; - } - - /// - /// Returns a given instance to the pool. - /// - /// The instance to return. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Free(T obj) - { - if (this.firstItem is null) - { - this.firstItem = obj; - } - else - { - FreeSlow(obj); - } - } - - /// - /// Allocates a new item. - /// - /// The returned item to use. - [MethodImpl(MethodImplOptions.NoInlining)] - private T AllocateSlow() - { - foreach (ref Element element in this.items.AsSpan()) - { - T? instance = element.Value; - - if (instance is not null) - { - if (instance == Interlocked.CompareExchange(ref element.Value, null, instance)) - { - return instance; - } - } - } - - return this.factory(); - } - - /// - /// Frees a given item. - /// - /// The item to return to the pool. - [MethodImpl(MethodImplOptions.NoInlining)] - private void FreeSlow(T obj) - { - foreach (ref Element element in this.items.AsSpan()) - { - if (element.Value is null) - { - element.Value = obj; - - break; - } - } - } - - /// - /// A container for a produced item (using a wrapper to avoid covariance checks). - /// - private struct Element - { - /// - /// The value held at the current element. - /// - internal T? Value; - } -} \ No newline at end of file