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 * ***************************************************************************/
16 using System.Collections.Generic;
17 using System.Collections.ObjectModel;
18 using System.Diagnostics;
19 using System.Dynamic.Utils;
26 namespace Microsoft.Scripting.Ast.Compiler {
28 namespace System.Linq.Expressions.Compiler {
31 /// Determines if variables are closed over in nested lambdas and need to
34 internal sealed class VariableBinder : ExpressionVisitor {
35 private readonly AnalyzedTree _tree = new AnalyzedTree();
36 private readonly Stack<CompilerScope> _scopes = new Stack<CompilerScope>();
37 private readonly Stack<BoundConstants> _constants = new Stack<BoundConstants>();
38 private bool _inQuote;
40 internal static AnalyzedTree Bind(LambdaExpression lambda) {
41 var binder = new VariableBinder();
46 private VariableBinder() {
49 protected internal override Expression VisitConstant(ConstantExpression node) {
50 // If we're in Quote, we can ignore constants completely
55 // Constants that can be emitted into IL don't need to be stored on
57 if (ILGen.CanEmitConstant(node.Value, node.Type)) {
61 _constants.Peek().AddReference(node.Value, node.Type);
65 protected internal override Expression VisitUnary(UnaryExpression node) {
66 if (node.NodeType == ExpressionType.Quote) {
67 bool savedInQuote = _inQuote;
70 _inQuote = savedInQuote;
77 protected internal override Expression VisitLambda<T>(Expression<T> node) {
78 _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, true));
79 _constants.Push(_tree.Constants[node] = new BoundConstants());
80 Visit(MergeScopes(node));
86 protected internal override Expression VisitInvocation(InvocationExpression node) {
87 LambdaExpression lambda = node.LambdaOperand;
89 // optimization: inline code for literal lambda's directly
91 // visit the lambda, but treat it more like a scope
92 _scopes.Push(_tree.Scopes[lambda] = new CompilerScope(lambda, false));
93 Visit(MergeScopes(lambda));
95 // visit the invoke's arguments
96 Visit(node.Arguments);
100 return base.VisitInvocation(node);
103 protected internal override Expression VisitBlock(BlockExpression node) {
104 if (node.Variables.Count == 0) {
105 Visit(node.Expressions);
108 _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, false));
109 Visit(MergeScopes(node));
114 protected override CatchBlock VisitCatchBlock(CatchBlock node) {
115 if (node.Variable == null) {
119 _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, false));
125 // If the immediate child is another scope, merge it into this one
126 // This is an optimization to save environment allocations and
128 private ReadOnlyCollection<Expression> MergeScopes(Expression node) {
129 ReadOnlyCollection<Expression> body;
130 var lambda = node as LambdaExpression;
131 if (lambda != null) {
132 body = new ReadOnlyCollection<Expression>(new[] { lambda.Body });
134 body = ((BlockExpression)node).Expressions;
137 var currentScope = _scopes.Peek();
139 // A block body is mergeable if the body only contains one single block node containing variables,
140 // and the child block has the same type as the parent block.
141 while (body.Count == 1 && body[0].NodeType == ExpressionType.Block) {
142 var block = (BlockExpression)body[0];
144 if (block.Variables.Count > 0) {
145 // Make sure none of the variables are shadowed. If any
146 // are, we can't merge it.
147 foreach (var v in block.Variables) {
148 if (currentScope.Definitions.ContainsKey(v)) {
153 // Otherwise, merge it
154 if (currentScope.MergedScopes == null) {
155 currentScope.MergedScopes = new Set<object>(ReferenceEqualityComparer<object>.Instance);
157 currentScope.MergedScopes.Add(block);
158 foreach (var v in block.Variables) {
159 currentScope.Definitions.Add(v, VariableStorageKind.Local);
163 body = block.Expressions;
169 protected internal override Expression VisitParameter(ParameterExpression node) {
170 Reference(node, VariableStorageKind.Local);
173 // Track reference count so we can emit it in a more optimal way if
176 CompilerScope referenceScope = null;
177 foreach (CompilerScope scope in _scopes) {
179 // There are two times we care about references:
180 // 1. When we enter a lambda, we want to cache frequently
182 // 2. When we enter a scope with closed-over variables, we
183 // want to cache it immediately when we allocate the
184 // closure slot for it
186 if (scope.IsMethod || scope.Definitions.ContainsKey(node)) {
187 referenceScope = scope;
192 Debug.Assert(referenceScope != null);
193 if (referenceScope.ReferenceCount == null) {
194 referenceScope.ReferenceCount = new Dictionary<ParameterExpression, int>();
197 Helpers.IncrementCount(node, referenceScope.ReferenceCount);
201 protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) {
202 foreach (var v in node.Variables) {
203 // Force hoisting of these variables
204 Reference(v, VariableStorageKind.Hoisted);
209 private void Reference(ParameterExpression node, VariableStorageKind storage) {
210 CompilerScope definition = null;
211 foreach (CompilerScope scope in _scopes) {
212 if (scope.Definitions.ContainsKey(node)) {
216 scope.NeedsClosure = true;
217 if (scope.IsMethod) {
218 storage = VariableStorageKind.Hoisted;
221 if (definition == null) {
222 throw Error.UndefinedVariable(node.Name, node.Type, CurrentLambdaName);
224 if (storage == VariableStorageKind.Hoisted) {
226 throw Error.CannotCloseOverByRef(node.Name, CurrentLambdaName);
228 definition.Definitions[node] = VariableStorageKind.Hoisted;
232 private string CurrentLambdaName {
234 foreach (var scope in _scopes) {
235 var lambda = scope.Node as LambdaExpression;
236 if (lambda != null) {
240 throw ContractUtils.Unreachable;