// 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.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading; namespace Roslyn.Utilities { internal static class InterlockedOperations { private static T GetOrStore<T>([NotNull] ref T? target, T value) where T : class => Interlocked.CompareExchange(ref target, value, null) ?? value; private static int GetOrStore(ref int target, int value, int uninitializedValue) { var existingValue = Interlocked.CompareExchange(ref target, value, uninitializedValue); return existingValue == uninitializedValue ? value : existingValue; } /// <summary> /// Ensure that the given target value is initialized (not null) in a thread-safe manner. /// </summary> /// <typeparam name="T">The type of the target value. Must be a reference type.</typeparam> /// <param name="target">The target to initialize.</param> /// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called /// more than once by multiple threads, but only one of those values will successfully be written to the target.</param> /// <returns>The target value.</returns> public static T Initialize<T>([NotNull] ref T? target, Func<T> valueFactory) where T : class => Volatile.Read(ref target!) ?? GetOrStore(ref target, valueFactory()); /// <summary> /// Ensure that the given target value is initialized (not null) in a thread-safe manner. /// </summary> /// <typeparam name="T">The type of the target value. Must be a reference type.</typeparam> /// <param name="target">The target to initialize.</param> /// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam> /// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called /// more than once by multiple threads, but only one of those values will successfully be written to the target.</param> /// <param name="arg">An argument passed to the value factory.</param> /// <returns>The target value.</returns> public static T Initialize<T, TArg>([NotNull] ref T? target, Func<TArg, T> valueFactory, TArg arg) where T : class { return Volatile.Read(ref target!) ?? GetOrStore(ref target, valueFactory(arg)); } /// <summary> /// Ensure that the given target value is initialized in a thread-safe manner. /// </summary> /// <param name="target">The target to initialize.</param> /// <param name="uninitializedValue">The value indicating <paramref name="target"/> is not yet initialized.</param> /// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called /// more than once by multiple threads, but only one of those values will successfully be written to the target.</param> /// <param name="arg">An argument passed to the value factory.</param> /// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam> /// <remarks> /// If <paramref name="valueFactory"/> returns a value equal to <paramref name="uninitializedValue"/>, future /// calls to the same method may recalculate the target value. /// </remarks> /// <returns>The target value.</returns> public static int Initialize<TArg>(ref int target, int uninitializedValue, Func<TArg, int> valueFactory, TArg arg) { var existingValue = Volatile.Read(ref target); if (existingValue != uninitializedValue) return existingValue; return GetOrStore(ref target, valueFactory(arg), uninitializedValue); } /// <summary> /// Ensure that the given target value is initialized in a thread-safe manner. This overload supports the /// initialization of value types, and reference type fields where <see langword="null"/> is considered an /// initialized value. /// </summary> /// <typeparam name="T">The type of the target value.</typeparam> /// <param name="target">A target value box to initialize.</param> /// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called /// more than once by multiple threads, but only one of those values will successfully be written to the target.</param> /// <returns>The target value.</returns> public static T? Initialize<T>([NotNull] ref StrongBox<T?>? target, Func<T?> valueFactory) { var box = Volatile.Read(ref target!) ?? GetOrStore(ref target, new StrongBox<T?>(valueFactory())); return box.Value; } /// <summary> /// Ensure that the given target value is initialized in a thread-safe manner. This overload supports the /// initialization of value types, and reference type fields where <see langword="null"/> is considered an /// initialized value. /// </summary> /// <typeparam name="T">The type of the target value.</typeparam> /// <param name="target">A target value box to initialize.</param> /// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam> /// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called /// more than once by multiple threads, but only one of those values will successfully be written to the target.</param> /// <param name="arg">An argument passed to the value factory.</param> /// <returns>The target value.</returns> public static T? Initialize<T, TArg>([NotNull] ref StrongBox<T?>? target, Func<TArg, T?> valueFactory, TArg arg) { var box = Volatile.Read(ref target!) ?? GetOrStore(ref target, new StrongBox<T?>(valueFactory(arg))); return box.Value; } /// <summary> /// Initialize the value referenced by <paramref name="target"/> in a thread-safe manner. /// The value is changed to <paramref name="value"/> only if the current value is null. /// </summary> /// <typeparam name="T">Type of value.</typeparam> /// <param name="target">Reference to the target location.</param> /// <param name="value">The value to use if the target is currently null.</param> /// <returns>The new value referenced by <paramref name="target"/>. Note that this is /// nearly always more useful than the usual return from <see cref="Interlocked.CompareExchange{T}(ref T, T, T)"/> /// because it saves another read to <paramref name="target"/>.</returns> public static T Initialize<T>([NotNull] ref T? target, T value) where T : class { RoslynDebug.Assert((object?)value != null); return GetOrStore(ref target, value); } /// <summary> /// Initialize the value referenced by <paramref name="target"/> in a thread-safe manner. /// The value is changed to <paramref name="initializedValue"/> only if the current value /// is <paramref name="uninitializedValue"/>. /// </summary> /// <typeparam name="T">Type of value.</typeparam> /// <param name="target">Reference to the target location.</param> /// <param name="initializedValue">The value to use if the target is currently uninitialized.</param> /// <param name="uninitializedValue">The uninitialized value.</param> /// <returns>The new value referenced by <paramref name="target"/>. Note that this is /// nearly always more useful than the usual return from <see cref="Interlocked.CompareExchange{T}(ref T, T, T)"/> /// because it saves another read to <paramref name="target"/>.</returns> [return: NotNullIfNotNull(parameterName: nameof(initializedValue))] public static T Initialize<T>(ref T target, T initializedValue, T uninitializedValue) where T : class? { Debug.Assert((object?)initializedValue != uninitializedValue); T oldValue = Interlocked.CompareExchange(ref target, initializedValue, uninitializedValue); return (object?)oldValue == uninitializedValue ? initializedValue : oldValue; } /// <summary> /// Initialize the immutable array referenced by <paramref name="target"/> in a thread-safe manner. /// </summary> /// <typeparam name="T">Elemental type of the array.</typeparam> /// <param name="target">Reference to the target location.</param> /// <param name="initializedValue">The value to use if the target is currently uninitialized (default).</param> /// <returns>The new value referenced by <paramref name="target"/>. Note that this is /// nearly always more useful than the usual return from <see cref="Interlocked.CompareExchange{T}(ref T, T, T)"/> /// because it saves another read to <paramref name="target"/>.</returns> public static ImmutableArray<T> Initialize<T>(ref ImmutableArray<T> target, ImmutableArray<T> initializedValue) { Debug.Assert(!initializedValue.IsDefault); var oldValue = ImmutableInterlocked.InterlockedCompareExchange(ref target, initializedValue, default(ImmutableArray<T>)); return oldValue.IsDefault ? initializedValue : oldValue; } /// <summary> /// Initialize the immutable array referenced by <paramref name="target"/> in a thread-safe manner. /// </summary> /// <typeparam name="T">Elemental type of the array.</typeparam> /// <param name="createArray">Callback to produce the array if <paramref name="target"/> is 'default'. May be /// called multiple times in the event of concurrent initialization of <paramref name="target"/>. Will not be /// called if 'target' is already not 'default' at the time this is called.</param> /// <returns>The value of <paramref name="target"/> after initialization. If <paramref name="target"/> is /// already initialized, that value value will be returned.</returns> public static ImmutableArray<T> Initialize<T>(ref ImmutableArray<T> target, Func<ImmutableArray<T>> createArray) => Initialize(ref target, static createArray => createArray(), createArray); /// <summary> /// Initialize the immutable array referenced by <paramref name="target"/> in a thread-safe manner. /// </summary> /// <typeparam name="T">Elemental type of the array.</typeparam> /// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam> /// <param name="createArray">Callback to produce the array if <paramref name="target"/> is 'default'. May be /// called multiple times in the event of concurrent initialization of <paramref name="target"/>. Will not be /// called if 'target' is already not 'default' at the time this is called.</param> /// <returns>The value of <paramref name="target"/> after initialization. If <paramref name="target"/> is /// already initialized, that value value will be returned.</returns> public static ImmutableArray<T> Initialize<T, TArg>(ref ImmutableArray<T> target, Func<TArg, ImmutableArray<T>> createArray, TArg arg) { if (!target.IsDefault) { return target; } return Initialize_Slow(ref target, createArray, arg); } private static ImmutableArray<T> Initialize_Slow<T, TArg>(ref ImmutableArray<T> target, Func<TArg, ImmutableArray<T>> createArray, TArg arg) { ImmutableInterlocked.Update( ref target, static (current, tuple) => { // Once initialized, never reinitialize. if (!current.IsDefault) return current; return tuple.createArray(tuple.arg); }, (createArray, arg)); return target; } } } |