File: Microsoft\Scripting\Compiler\BoundConstants.cs
Project: ndp\fx\src\Core\System.Core.csproj (System.Core)
/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Apache License, Version 2.0, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Dynamic.Utils;
 
#if SILVERLIGHT
using System.Core;
#endif
 
#if CLR2
namespace Microsoft.Scripting.Ast.Compiler {
#else
namespace System.Linq.Expressions.Compiler {
#endif
    /// <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
        /// seperate 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 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);
            }
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2231:OverloadOperatorEqualsOnOverridingValueTypeEquals")]
            public override bool Equals(object obj) {
                return (obj is TypedConstant) && Equals((TypedConstant)obj);
            }
        }
 
        /// <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<object>.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 {
            get { return _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.ContainsKey(value)) {
                _indexes.Add(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 (!lc.CanEmitBoundConstants) {
                throw Error.CannotCompileConstant(value);
            }
 
            LocalBuilder local;
            if (_cache.TryGetValue(new TypedConstant(value, type), out 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 (var reference in _references) {
                if (!lc.CanEmitBoundConstants) {
                    throw Error.CannotCompileConstant(reference.Key.Value);
                }
 
                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 (var 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) {
            Debug.Assert(lc.CanEmitBoundConstants); // this should've been checked already
 
            lc.EmitClosureArgument();
            lc.IL.Emit(OpCodes.Ldfld, typeof(Closure).GetField("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.EmitInt(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);
            }
        }
    }
}