1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
5 * This source code is subject to terms and conditions of the Microsoft Public License. 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 Microsoft Public License, 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 Microsoft Public License.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
15 using System; using Microsoft;
18 using System.Collections.Generic;
19 using System.Collections.ObjectModel;
20 using System.Diagnostics;
21 using System.Reflection.Emit;
22 using System.Runtime.CompilerServices;
24 using Microsoft.Runtime.CompilerServices;
29 using System.Dynamic.Utils;
31 using Microsoft.Scripting;
32 using Microsoft.Scripting.Utils;
36 namespace System.Linq.Expressions.Compiler {
38 namespace Microsoft.Linq.Expressions.Compiler {
40 internal enum VariableStorageKind {
46 /// CompilerScope is the data structure which the Compiler keeps information
47 /// related to compiling scopes. It stores the following information:
48 /// 1. Parent relationship (for resolving variables)
49 /// 2. Information about hoisted variables
50 /// 3. Information for resolving closures
52 /// Instances are produced by VariableBinder, which does a tree walk
53 /// looking for scope nodes: LambdaExpression and BlockExpression.
55 internal sealed partial class CompilerScope {
57 /// parent scope, if any
59 private CompilerScope _parent;
62 /// The expression node for this scope
63 /// Can be LambdaExpression, BlockExpression, or CatchBlock
65 internal readonly object Node;
68 /// True if this node corresponds to an IL method.
69 /// Can only be true if the Node is a LambdaExpression.
70 /// But inlined lambdas will have it set to false.
72 internal readonly bool IsMethod;
75 /// Does this scope (or any inner scope) close over variables from any
77 /// Populated by VariableBinder
79 internal bool NeedsClosure;
82 /// Variables defined in this scope, and whether they're hoisted or not
83 /// Populated by VariableBinder
85 internal readonly Dictionary<ParameterExpression, VariableStorageKind> Definitions = new Dictionary<ParameterExpression, VariableStorageKind>();
88 /// Each variable referenced within this scope, and how often it was referenced
89 /// Populated by VariableBinder
91 internal Dictionary<ParameterExpression, int> ReferenceCount;
94 /// Scopes whose variables were merged into this one
96 /// Created lazily as we create hundreds of compiler scopes w/o merging scopes when compiling rules.
98 internal Set<object> MergedScopes;
101 /// The scope's hoisted locals, if any.
102 /// Provides storage for variables that are referenced from nested lambdas
104 private HoistedLocals _hoistedLocals;
107 /// The closed over hoisted locals
109 private HoistedLocals _closureHoistedLocals;
112 /// Mutable dictionary that maps non-hoisted variables to either local
113 /// slots or argument slots
115 private readonly Dictionary<ParameterExpression, Storage> _locals = new Dictionary<ParameterExpression, Storage>();
117 internal CompilerScope(object node, bool isMethod) {
120 var variables = GetVariables(node);
122 Definitions = new Dictionary<ParameterExpression, VariableStorageKind>(variables.Count);
123 foreach (var v in variables) {
124 Definitions.Add(v, VariableStorageKind.Local);
129 /// This scope's hoisted locals, or the closed over locals, if any
130 /// Equivalent to: _hoistedLocals ?? _closureHoistedLocals
132 internal HoistedLocals NearestHoistedLocals {
133 get { return _hoistedLocals ?? _closureHoistedLocals; }
137 /// Called when entering a lambda/block. Performs all variable allocation
138 /// needed, including creating hoisted locals and IL locals for accessing
141 internal CompilerScope Enter(LambdaCompiler lc, CompilerScope parent) {
142 SetParent(lc, parent);
146 if (IsMethod && _closureHoistedLocals != null) {
147 EmitClosureAccess(lc, _closureHoistedLocals);
150 EmitNewHoistedLocals(lc);
153 EmitCachedVariables();
160 /// Frees unnamed locals, clears state associated with this compiler
162 internal CompilerScope Exit() {
163 // free scope's variables
165 foreach (Storage storage in _locals.Values) {
170 // Clear state that is associated with this parent
171 // (because the scope can be reused in another context)
172 CompilerScope parent = _parent;
174 _hoistedLocals = null;
175 _closureHoistedLocals = null;
181 #region LocalScopeExpression support
183 internal void EmitVariableAccess(LambdaCompiler lc, ReadOnlyCollection<ParameterExpression> vars) {
184 if (NearestHoistedLocals != null) {
185 // Find what array each variable is on & its index
186 var indexes = new List<long>(vars.Count);
188 foreach (var variable in vars) {
189 // For each variable, find what array it's defined on
191 HoistedLocals locals = NearestHoistedLocals;
192 while (!locals.Indexes.ContainsKey(variable)) {
194 locals = locals.Parent;
195 Debug.Assert(locals != null);
198 // combine the number of parents we walked, with the
199 // real index of variable to get the index to emit.
200 ulong index = (parents << 32) | (uint)locals.Indexes[variable];
202 indexes.Add((long)index);
205 if (indexes.Count > 0) {
206 EmitGet(NearestHoistedLocals.SelfVariable);
207 lc.EmitConstantArray(indexes.ToArray());
208 lc.IL.Emit(OpCodes.Call, typeof(RuntimeOps).GetMethod("CreateRuntimeVariables", new[] { typeof(object[]), typeof(long[]) }));
213 // No visible variables
214 lc.IL.Emit(OpCodes.Call, typeof(RuntimeOps).GetMethod("CreateRuntimeVariables", Type.EmptyTypes));
220 #region Variable access
223 /// Adds a new virtual variable corresponding to an IL local
225 internal void AddLocal(LambdaCompiler gen, ParameterExpression variable) {
226 _locals.Add(variable, new LocalStorage(gen, variable));
229 internal void EmitGet(ParameterExpression variable) {
230 ResolveVariable(variable).EmitLoad();
233 internal void EmitSet(ParameterExpression variable) {
234 ResolveVariable(variable).EmitStore();
237 internal void EmitAddressOf(ParameterExpression variable) {
238 ResolveVariable(variable).EmitAddress();
241 private Storage ResolveVariable(ParameterExpression variable) {
242 return ResolveVariable(variable, NearestHoistedLocals);
246 /// Resolve a local variable in this scope or a closed over scope
247 /// Throws if the variable is defined
249 private Storage ResolveVariable(ParameterExpression variable, HoistedLocals hoistedLocals) {
250 // Search IL locals and arguments, but only in this lambda
251 for (CompilerScope s = this; s != null; s = s._parent) {
253 if (s._locals.TryGetValue(variable, out storage)) {
257 // if this is a lambda, we're done
263 // search hoisted locals
264 for (HoistedLocals h = hoistedLocals; h != null; h = h.Parent) {
266 if (h.Indexes.TryGetValue(variable, out index)) {
267 return new ElementBoxStorage(
268 ResolveVariable(h.SelfVariable, hoistedLocals),
276 // If this is an unbound variable in the lambda, the error will be
277 // thrown from VariableBinder. So an error here is generally caused
278 // by an internal error, e.g. a scope was created but it bypassed
281 throw Error.UndefinedVariable(variable.Name, variable.Type, CurrentLambdaName);
286 private void SetParent(LambdaCompiler lc, CompilerScope parent) {
287 Debug.Assert(_parent == null && parent != this);
290 if (NeedsClosure && _parent != null) {
291 _closureHoistedLocals = _parent.NearestHoistedLocals;
294 var hoistedVars = GetVariables().Where(p => Definitions[p] == VariableStorageKind.Hoisted).ToReadOnly();
296 if (hoistedVars.Count > 0) {
297 _hoistedLocals = new HoistedLocals(_closureHoistedLocals, hoistedVars);
298 AddLocal(lc, _hoistedLocals.SelfVariable);
302 // Emits creation of the hoisted local storage
303 private void EmitNewHoistedLocals(LambdaCompiler lc) {
304 if (_hoistedLocals == null) {
309 lc.IL.EmitInt(_hoistedLocals.Variables.Count);
310 lc.IL.Emit(OpCodes.Newarr, typeof(object));
312 // initialize all elements
314 foreach (ParameterExpression v in _hoistedLocals.Variables) {
315 // array[i] = new StrongBox<T>(...);
316 lc.IL.Emit(OpCodes.Dup);
318 Type boxType = typeof(StrongBox<>).MakeGenericType(v.Type);
320 if (IsMethod && lc.Parameters.Contains(v)) {
321 // array[i] = new StrongBox<T>(argument);
322 int index = lc.Parameters.IndexOf(v);
323 lc.EmitLambdaArgument(index);
324 lc.IL.Emit(OpCodes.Newobj, boxType.GetConstructor(new Type[] { v.Type }));
325 } else if (v == _hoistedLocals.ParentVariable) {
326 // array[i] = new StrongBox<T>(closure.Locals);
327 ResolveVariable(v, _closureHoistedLocals).EmitLoad();
328 lc.IL.Emit(OpCodes.Newobj, boxType.GetConstructor(new Type[] { v.Type }));
330 // array[i] = new StrongBox<T>();
331 lc.IL.Emit(OpCodes.Newobj, boxType.GetConstructor(Type.EmptyTypes));
333 // if we want to cache this into a local, do it now
334 if (ShouldCache(v)) {
335 lc.IL.Emit(OpCodes.Dup);
336 CacheBoxToLocal(lc, v);
338 lc.IL.Emit(OpCodes.Stelem_Ref);
342 EmitSet(_hoistedLocals.SelfVariable);
345 // If hoisted variables are referenced "enough", we cache the
346 // StrongBox<T> in an IL local, which saves an array index and a cast
347 // when we go to look it up later
348 private void EmitCachedVariables() {
349 if (ReferenceCount == null) {
353 foreach (var refCount in ReferenceCount) {
354 if (ShouldCache(refCount.Key, refCount.Value)) {
355 var storage = ResolveVariable(refCount.Key) as ElementBoxStorage;
356 if (storage != null) {
357 storage.EmitLoadBox();
358 CacheBoxToLocal(storage.Compiler, refCount.Key);
364 private bool ShouldCache(ParameterExpression v, int refCount) {
365 // This caching is too aggressive in the face of conditionals and
366 // switch. Also, it is too conservative for variables used inside
368 return refCount > 2 && !_locals.ContainsKey(v);
371 private bool ShouldCache(ParameterExpression v) {
372 if (ReferenceCount == null) {
377 return ReferenceCount.TryGetValue(v, out refCount) && ShouldCache(v, refCount);
380 private void CacheBoxToLocal(LambdaCompiler lc, ParameterExpression v) {
381 Debug.Assert(ShouldCache(v) && !_locals.ContainsKey(v));
382 var local = new LocalBoxStorage(lc, v);
383 local.EmitStoreBox();
384 _locals.Add(v, local);
387 // Creates IL locals for accessing closures
388 private void EmitClosureAccess(LambdaCompiler lc, HoistedLocals locals) {
389 if (locals == null) {
393 EmitClosureToVariable(lc, locals);
395 while ((locals = locals.Parent) != null) {
396 var v = locals.SelfVariable;
397 var local = new LocalStorage(lc, v);
398 local.EmitStore(ResolveVariable(v));
399 _locals.Add(v, local);
403 private void EmitClosureToVariable(LambdaCompiler lc, HoistedLocals locals) {
404 lc.EmitClosureArgument();
405 lc.IL.Emit(OpCodes.Ldfld, typeof(Closure).GetField("Locals"));
406 AddLocal(lc, locals.SelfVariable);
407 EmitSet(locals.SelfVariable);
410 // Allocates slots for IL locals or IL arguments
411 private void AllocateLocals(LambdaCompiler lc) {
412 foreach (ParameterExpression v in GetVariables()) {
413 if (Definitions[v] == VariableStorageKind.Local) {
415 // If v is in lc.Parameters, it is a parameter.
416 // Otherwise, it is a local variable.
418 // Also, for inlined lambdas we'll create a local, which
419 // is possibly a byref local if the parameter is byref.
422 if (IsMethod && lc.Parameters.Contains(v)) {
423 s = new ArgumentStorage(lc, v);
425 s = new LocalStorage(lc, v);
432 private IList<ParameterExpression> GetVariables() {
433 var vars = GetVariables(Node);
434 if (MergedScopes == null) {
437 var list = new List<ParameterExpression>(vars);
438 foreach (var scope in MergedScopes) {
439 list.AddRange(GetVariables(scope));
444 private static IList<ParameterExpression> GetVariables(object scope) {
445 var lambda = scope as LambdaExpression;
446 if (lambda != null) {
447 return lambda.Parameters;
449 var block = scope as BlockExpression;
451 return block.Variables;
453 return new[] { ((CatchBlock)scope).Variable };
456 private string CurrentLambdaName {
458 CompilerScope s = this;
460 var lambda = s.Node as LambdaExpression;
461 if (lambda != null) {