File: Workspace\Solution\Checksum_Factory.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.IO.Hashing;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Serialization;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis;
 
// various factory methods. all these are just helper methods
internal readonly partial record struct Checksum
{
    private const int XXHash128SizeBytes = 128 / 8;
 
    private static readonly ObjectPool<XxHash128> s_incrementalHashPool =
        new(() => new(), size: 20);
 
    public static Checksum Create(IEnumerable<string?> values)
    {
        using var pooledHash = s_incrementalHashPool.GetPooledObject();
 
        foreach (var value in values)
        {
            pooledHash.Object.Append(MemoryMarshal.AsBytes(value.AsSpan()));
            pooledHash.Object.Append(MemoryMarshal.AsBytes("\0".AsSpan()));
        }
 
        Span<byte> hash = stackalloc byte[XXHash128SizeBytes];
        pooledHash.Object.GetHashAndReset(hash);
        return From(hash);
    }
 
    public static Checksum Create(string? value)
    {
        Span<byte> destination = stackalloc byte[XXHash128SizeBytes];
        XxHash128.Hash(MemoryMarshal.AsBytes(value.AsSpan()), destination);
        return From(destination);
    }
 
    public static Checksum Create(Stream stream)
    {
        using var pooledHash = s_incrementalHashPool.GetPooledObject();
        pooledHash.Object.Append(stream);
 
        Span<byte> hash = stackalloc byte[XXHash128SizeBytes];
        pooledHash.Object.GetHashAndReset(hash);
        return From(hash);
    }
 
    public static Checksum Create<T>(T @object, Action<T, ObjectWriter> writeObject)
    {
        using var stream = SerializableBytes.CreateWritableStream();
 
        using (var objectWriter = new ObjectWriter(stream, leaveOpen: true))
        {
            writeObject(@object, objectWriter);
        }
 
        stream.Position = 0;
        return Create(stream);
    }
 
    public static Checksum Create(Checksum checksum1, Checksum checksum2)
        => Create(stackalloc[] { checksum1, checksum2 });
 
    public static Checksum Create(Checksum checksum1, Checksum checksum2, Checksum checksum3)
        => Create(stackalloc[] { checksum1, checksum2, checksum3 });
 
    public static Checksum Create(Checksum checksum1, Checksum checksum2, Checksum checksum3, Checksum checksum4)
        => Create(stackalloc[] { checksum1, checksum2, checksum3, checksum4 });
 
    public static Checksum Create(ReadOnlySpan<Checksum> hashes)
    {
        Span<byte> destination = stackalloc byte[XXHash128SizeBytes];
        XxHash128.Hash(MemoryMarshal.AsBytes(hashes), destination);
        return From(destination);
    }
 
    public static Checksum Create(ArrayBuilder<Checksum> checksums)
    {
        // Max alloc 1 KB on stack
        const int maxStackAllocCount = 1024 / Checksum.HashSize;
 
        var checksumsCount = checksums.Count;
        if (checksumsCount <= maxStackAllocCount)
        {
            Span<Checksum> hashes = stackalloc Checksum[checksumsCount];
            for (var i = 0; i < checksumsCount; i++)
                hashes[i] = checksums[i];
 
            return Create(hashes);
        }
        else
        {
            using var pooledHash = s_incrementalHashPool.GetPooledObject();
            Span<Checksum> checksumsSpan = stackalloc Checksum[maxStackAllocCount];
            var checksumsIndex = 0;
 
            while (checksumsIndex < checksumsCount)
            {
                var count = Math.Min(maxStackAllocCount, checksumsCount - checksumsIndex);
 
                for (var checksumsSpanIndex = 0; checksumsSpanIndex < count; checksumsSpanIndex++, checksumsIndex++)
                    checksumsSpan[checksumsSpanIndex] = checksums[checksumsIndex];
 
                var hashSpan = checksumsSpan.Slice(0, count);
                pooledHash.Object.Append(MemoryMarshal.AsBytes(hashSpan));
            }
 
            Span<byte> hash = stackalloc byte[XXHash128SizeBytes];
            pooledHash.Object.GetHashAndReset(hash);
            return From(hash);
        }
    }
 
    public static Checksum Create(ImmutableArray<Checksum> checksums)
    {
        var hashes = ImmutableCollectionsMarshal.AsArray(checksums).AsSpan();
 
        return Create(hashes);
    }
 
    public static Checksum Create(ImmutableArray<byte> bytes)
    {
        var source = ImmutableCollectionsMarshal.AsArray(bytes).AsSpan();
 
        Span<byte> destination = stackalloc byte[XXHash128SizeBytes];
        XxHash128.Hash(source, destination);
        return From(destination);
    }
 
    public static Checksum Create<T>(T value, ISerializerService serializer, CancellationToken cancellationToken)
        => Create(
            (value, serializer, cancellationToken),
            static (tuple, writer) =>
            {
                var (value, serializer, cancellationToken) = tuple;
                serializer.Serialize(value!, writer, cancellationToken);
            });
}