| File: System\Linq\Expressions\Compiler\BoundConstants.cs | Web Access |
| Project: src\runtime\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Dynamic.Utils; using System.Reflection.Emit; using System.Runtime.CompilerServices; using static System.Linq.Expressions.CachedReflectionInfo; namespace System.Linq.Expressions.Compiler { /// <summary> /// This type tracks "runtime" constants--live objects that appear in /// ConstantExpression nodes and must be bound to the delegate. /// </summary> internal sealed class BoundConstants { /// <summary> /// Constants can emit themselves as different types /// For caching purposes, we need to treat each distinct Type as a /// separate thing to cache. (If we have to cast it on the way out, it /// ends up using a JIT temp and defeats the purpose of caching the /// value in a local) /// </summary> private readonly struct TypedConstant : IEquatable<TypedConstant> { internal readonly object Value; internal readonly Type Type; internal TypedConstant(object value, Type type) { Value = value; Type = type; } public override int GetHashCode() { return RuntimeHelpers.GetHashCode(Value) ^ Type.GetHashCode(); } public bool Equals(TypedConstant other) { return object.ReferenceEquals(Value, other.Value) && Type.Equals(other.Type); } public override bool Equals([NotNullWhen(true)] object? obj) { return obj is TypedConstant typedConstant && Equals(typedConstant); } } /// <summary> /// The list of constants in the order they appear in the constant array /// </summary> private readonly List<object> _values = new List<object>(); /// <summary> /// The index of each constant in the constant array /// </summary> private readonly Dictionary<object, int> _indexes = new Dictionary<object, int>(ReferenceEqualityComparer.Instance); /// <summary> /// Each constant referenced within this lambda, and how often it was referenced /// </summary> private readonly Dictionary<TypedConstant, int> _references = new Dictionary<TypedConstant, int>(); /// <summary> /// IL locals for storing frequently used constants /// </summary> private readonly Dictionary<TypedConstant, LocalBuilder> _cache = new Dictionary<TypedConstant, LocalBuilder>(); internal int Count => _values.Count; internal object[] ToArray() { return _values.ToArray(); } /// <summary> /// Called by VariableBinder. Adds the constant to the list (if needed) /// and increases the reference count by one /// </summary> internal void AddReference(object value, Type type) { if (_indexes.TryAdd(value, _values.Count)) { _values.Add(value); } Helpers.IncrementCount(new TypedConstant(value, type), _references); } /// <summary> /// Emits a live object as a constant /// </summary> internal void EmitConstant(LambdaCompiler lc, object value, Type type) { Debug.Assert(!ILGen.CanEmitConstant(value, type)); #if FEATURE_COMPILE_TO_METHODBUILDER if (!lc.CanEmitBoundConstants) { throw Error.CannotCompileConstant(value); } #endif if (_cache.TryGetValue(new TypedConstant(value, type), out LocalBuilder? local)) { lc.IL.Emit(OpCodes.Ldloc, local); return; } EmitConstantsArray(lc); EmitConstantFromArray(lc, value, type); } /// <summary> /// Emit code to cache frequently used constants into IL locals, /// instead of pulling them out of the array each time /// </summary> internal void EmitCacheConstants(LambdaCompiler lc) { int count = 0; foreach (KeyValuePair<TypedConstant, int> reference in _references) { #if FEATURE_COMPILE_TO_METHODBUILDER if (!lc.CanEmitBoundConstants) { throw Error.CannotCompileConstant(reference.Key.Value); } #endif if (ShouldCache(reference.Value)) { count++; } } if (count == 0) { return; } EmitConstantsArray(lc); // The same lambda can be in multiple places in the tree, so we // need to clear any locals from last time. _cache.Clear(); foreach (KeyValuePair<TypedConstant, int> reference in _references) { if (ShouldCache(reference.Value)) { if (--count > 0) { // Dup array to keep it on the stack lc.IL.Emit(OpCodes.Dup); } LocalBuilder local = lc.IL.DeclareLocal(reference.Key.Type); EmitConstantFromArray(lc, reference.Key.Value, local.LocalType); lc.IL.Emit(OpCodes.Stloc, local); _cache.Add(reference.Key, local); } } } private static bool ShouldCache(int refCount) { // This caching is too aggressive in the face of conditionals and // switch. Also, it is too conservative for variables used inside // of loops. return refCount > 2; } private static void EmitConstantsArray(LambdaCompiler lc) { #if FEATURE_COMPILE_TO_METHODBUILDER Debug.Assert(lc.CanEmitBoundConstants); // this should've been checked already #endif lc.EmitClosureArgument(); lc.IL.Emit(OpCodes.Ldfld, Closure_Constants); } private void EmitConstantFromArray(LambdaCompiler lc, object value, Type type) { int index; if (!_indexes.TryGetValue(value, out index)) { _indexes.Add(value, index = _values.Count); _values.Add(value); } lc.IL.EmitPrimitive(index); lc.IL.Emit(OpCodes.Ldelem_Ref); if (type.IsValueType) { lc.IL.Emit(OpCodes.Unbox_Any, type); } else if (type != typeof(object)) { lc.IL.Emit(OpCodes.Castclass, type); } } } }