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;
22 using System.Dynamic.Utils;
24 using Microsoft.Scripting.Utils;
28 namespace System.Linq.Expressions.Compiler {
30 namespace Microsoft.Linq.Expressions.Compiler {
33 /// Determines if variables are closed over in nested lambdas and need to
36 internal sealed class VariableBinder : ExpressionVisitor {
37 private readonly AnalyzedTree _tree = new AnalyzedTree();
38 private readonly Stack<CompilerScope> _scopes = new Stack<CompilerScope>();
39 private readonly Stack<BoundConstants> _constants = new Stack<BoundConstants>();
40 private bool _inQuote;
42 internal static AnalyzedTree Bind(LambdaExpression lambda) {
43 var binder = new VariableBinder();
48 private VariableBinder() {
51 protected internal override Expression VisitConstant(ConstantExpression node) {
52 // If we're in Quote, we can ignore constants completely
57 // Constants that can be emitted into IL don't need to be stored on
59 if (ILGen.CanEmitConstant(node.Value, node.Type)) {
63 _constants.Peek().AddReference(node.Value, node.Type);
67 protected internal override Expression VisitUnary(UnaryExpression node) {
68 if (node.NodeType == ExpressionType.Quote) {
69 bool savedInQuote = _inQuote;
72 _inQuote = savedInQuote;
79 protected internal override Expression VisitLambda<T>(Expression<T> node) {
80 _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, true));
81 _constants.Push(_tree.Constants[node] = new BoundConstants());
82 Visit(MergeScopes(node));
88 protected internal override Expression VisitInvocation(InvocationExpression node) {
89 LambdaExpression lambda = node.LambdaOperand;
91 // optimization: inline code for literal lambda's directly
93 // visit the lambda, but treat it more like a scope
94 _scopes.Push(_tree.Scopes[lambda] = new CompilerScope(lambda, false));
95 Visit(MergeScopes(lambda));
97 // visit the invoke's arguments
98 Visit(node.Arguments);
102 return base.VisitInvocation(node);
105 protected internal override Expression VisitBlock(BlockExpression node) {
106 if (node.Variables.Count == 0) {
107 Visit(node.Expressions);
110 _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, false));
111 Visit(MergeScopes(node));
116 protected override CatchBlock VisitCatchBlock(CatchBlock node) {
117 if (node.Variable == null) {
121 _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, false));
127 // If the immediate child is another scope, merge it into this one
128 // This is an optimization to save environment allocations and
130 private ReadOnlyCollection<Expression> MergeScopes(Expression node) {
131 ReadOnlyCollection<Expression> body;
132 var lambda = node as LambdaExpression;
133 if (lambda != null) {
134 body = new ReadOnlyCollection<Expression>(new[] { lambda.Body });
136 body = ((BlockExpression)node).Expressions;
139 var currentScope = _scopes.Peek();
141 // A block body is mergeable if the body only contains one single block node containing variables,
142 // and the child block has the same type as the parent block.
143 while (body.Count == 1 && body[0].NodeType == ExpressionType.Block) {
144 var block = (BlockExpression)body[0];
146 if (block.Variables.Count > 0) {
147 // Make sure none of the variables are shadowed. If any
148 // are, we can't merge it.
149 foreach (var v in block.Variables) {
150 if (currentScope.Definitions.ContainsKey(v)) {
155 // Otherwise, merge it
156 if (currentScope.MergedScopes == null) {
157 currentScope.MergedScopes = new Set<object>(ReferenceEqualityComparer<object>.Instance);
159 currentScope.MergedScopes.Add(block);
160 foreach (var v in block.Variables) {
161 currentScope.Definitions.Add(v, VariableStorageKind.Local);
165 body = block.Expressions;
171 protected internal override Expression VisitParameter(ParameterExpression node) {
172 Reference(node, VariableStorageKind.Local);
175 // Track reference count so we can emit it in a more optimal way if
178 CompilerScope referenceScope = null;
179 foreach (CompilerScope scope in _scopes) {
181 // There are two times we care about references:
182 // 1. When we enter a lambda, we want to cache frequently
184 // 2. When we enter a scope with closed-over variables, we
185 // want to cache it immediately when we allocate the
186 // closure slot for it
188 if (scope.IsMethod || scope.Definitions.ContainsKey(node)) {
189 referenceScope = scope;
194 Debug.Assert(referenceScope != null);
195 if (referenceScope.ReferenceCount == null) {
196 referenceScope.ReferenceCount = new Dictionary<ParameterExpression, int>();
199 Helpers.IncrementCount(node, referenceScope.ReferenceCount);
203 protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) {
204 foreach (var v in node.Variables) {
205 // Force hoisting of these variables
206 Reference(v, VariableStorageKind.Hoisted);
211 private void Reference(ParameterExpression node, VariableStorageKind storage) {
212 CompilerScope definition = null;
213 foreach (CompilerScope scope in _scopes) {
214 if (scope.Definitions.ContainsKey(node)) {
218 scope.NeedsClosure = true;
219 if (scope.IsMethod) {
220 storage = VariableStorageKind.Hoisted;
223 if (definition == null) {
224 throw Error.UndefinedVariable(node.Name, node.Type, CurrentLambdaName);
226 if (storage == VariableStorageKind.Hoisted) {
228 throw Error.CannotCloseOverByRef(node.Name, CurrentLambdaName);
230 definition.Definitions[node] = VariableStorageKind.Hoisted;
234 private string CurrentLambdaName {
236 foreach (var scope in _scopes) {
237 var lambda = scope.Node as LambdaExpression;
238 if (lambda != null) {
242 throw ContractUtils.Unreachable;