1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6 * copy of the license can be found in the License.html file at the root of this distribution. If
7 * you cannot locate the Apache License, Version 2.0, please send an email to
8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9 * by the terms of the Apache License, Version 2.0.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
17 using System.Collections.Generic;
18 using System.Diagnostics;
19 using System.Reflection.Emit;
20 using System.Runtime.CompilerServices;
21 using System.Dynamic.Utils;
24 namespace Microsoft.Scripting.Ast.Compiler {
26 namespace System.Linq.Expressions.Compiler {
29 /// This type tracks "runtime" constants--live objects that appear in
30 /// ConstantExpression nodes and must be bound to the delegate.
32 internal sealed class BoundConstants {
35 /// Constants can emit themselves as different types
36 /// For caching purposes, we need to treat each distinct Type as a
37 /// seperate thing to cache. (If we have to cast it on the way out, it
38 /// ends up using a JIT temp and defeats the purpose of caching the
41 private struct TypedConstant : IEquatable<TypedConstant> {
42 internal readonly object Value;
43 internal readonly Type Type;
45 internal TypedConstant(object value, Type type) {
50 public override int GetHashCode() {
51 return ReferenceEqualityComparer<object>.Instance.GetHashCode(Value) ^ Type.GetHashCode();
54 public bool Equals(TypedConstant other) {
55 return object.ReferenceEquals(Value, other.Value) && Type.Equals(other.Type);
58 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2231:OverloadOperatorEqualsOnOverridingValueTypeEquals")]
59 public override bool Equals(object obj) {
60 return (obj is TypedConstant) && Equals((TypedConstant)obj);
65 /// The list of constants in the order they appear in the constant array
67 private readonly List<object> _values = new List<object>();
70 /// The index of each constant in the constant array
72 private readonly Dictionary<object, int> _indexes = new Dictionary<object, int>(ReferenceEqualityComparer<object>.Instance);
75 /// Each constant referenced within this lambda, and how often it was referenced
77 private readonly Dictionary<TypedConstant, int> _references = new Dictionary<TypedConstant, int>();
80 /// IL locals for storing frequently used constants
82 private readonly Dictionary<TypedConstant, LocalBuilder> _cache = new Dictionary<TypedConstant, LocalBuilder>();
85 get { return _values.Count; }
88 internal object[] ToArray() {
89 return _values.ToArray();
93 /// Called by VariableBinder. Adds the constant to the list (if needed)
94 /// and increases the reference count by one
96 internal void AddReference(object value, Type type) {
97 if (!_indexes.ContainsKey(value)) {
98 _indexes.Add(value, _values.Count);
101 Helpers.IncrementCount(new TypedConstant(value, type), _references);
105 /// Emits a live object as a constant
107 internal void EmitConstant(LambdaCompiler lc, object value, Type type) {
108 Debug.Assert(!ILGen.CanEmitConstant(value, type));
110 if (!lc.CanEmitBoundConstants) {
111 throw Error.CannotCompileConstant(value);
115 if (_cache.TryGetValue(new TypedConstant(value, type), out local)) {
116 lc.IL.Emit(OpCodes.Ldloc, local);
119 EmitConstantsArray(lc);
120 EmitConstantFromArray(lc, value, type);
124 /// Emit code to cache frequently used constants into IL locals,
125 /// instead of pulling them out of the array each time
127 internal void EmitCacheConstants(LambdaCompiler lc) {
129 foreach (var reference in _references) {
130 if (!lc.CanEmitBoundConstants) {
131 throw Error.CannotCompileConstant(reference.Key.Value);
134 if (ShouldCache(reference.Value)) {
141 EmitConstantsArray(lc);
143 // The same lambda can be in multiple places in the tree, so we
144 // need to clear any locals from last time.
147 foreach (var reference in _references) {
148 if (ShouldCache(reference.Value)) {
150 // Dup array to keep it on the stack
151 lc.IL.Emit(OpCodes.Dup);
153 LocalBuilder local = lc.IL.DeclareLocal(reference.Key.Type);
154 EmitConstantFromArray(lc, reference.Key.Value, local.LocalType);
155 lc.IL.Emit(OpCodes.Stloc, local);
156 _cache.Add(reference.Key, local);
161 private static bool ShouldCache(int refCount) {
162 // This caching is too aggressive in the face of conditionals and
163 // switch. Also, it is too conservative for variables used inside
168 private static void EmitConstantsArray(LambdaCompiler lc) {
169 Debug.Assert(lc.CanEmitBoundConstants); // this should've been checked already
171 lc.EmitClosureArgument();
172 lc.IL.Emit(OpCodes.Ldfld, typeof(Closure).GetField("Constants"));
175 private void EmitConstantFromArray(LambdaCompiler lc, object value, Type type) {
177 if (!_indexes.TryGetValue(value, out index)) {
178 _indexes.Add(value, index = _values.Count);
182 lc.IL.EmitInt(index);
183 lc.IL.Emit(OpCodes.Ldelem_Ref);
184 if (type.IsValueType) {
185 lc.IL.Emit(OpCodes.Unbox_Any, type);
186 } else if (type != typeof(object)) {
187 lc.IL.Emit(OpCodes.Castclass, type);