| File: System\Linq\Expressions\Compiler\VariableBinder.cs | Web Access |
| Project: src\runtime\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Dynamic.Utils; namespace System.Linq.Expressions.Compiler { /// <summary> /// Determines if variables are closed over in nested lambdas and need to /// be hoisted. /// </summary> [RequiresDynamicCode(Expression.StrongBoxRequiresDynamicCode)] internal sealed class VariableBinder : ExpressionVisitor { private readonly AnalyzedTree _tree = new AnalyzedTree(); private readonly Stack<CompilerScope> _scopes = new Stack<CompilerScope>(); private readonly Stack<BoundConstants> _constants = new Stack<BoundConstants>(); private readonly StackGuard _guard = new StackGuard(); private bool _inQuote; internal static AnalyzedTree Bind(LambdaExpression lambda) { var binder = new VariableBinder(); binder.Visit(lambda); return binder._tree; } private VariableBinder() { } [return: NotNullIfNotNull(nameof(node))] public override Expression? Visit(Expression? node) { // When compiling deep trees, we run the risk of triggering a terminating StackOverflowException, // so we use the StackGuard utility here to probe for sufficient stack and continue the work on // another thread when we run out of stack space. if (!_guard.TryEnterOnCurrentStack()) { return _guard.RunOnEmptyStack((VariableBinder @this, Expression? e) => @this.Visit(e), this, node); } return base.Visit(node); } protected internal override Expression VisitConstant(ConstantExpression node) { // If we're in Quote, we can ignore constants completely if (_inQuote) { return node; } // Constants that can be emitted into IL don't need to be stored on // the delegate if (ILGen.CanEmitConstant(node.Value, node.Type)) { return node; } _constants.Peek().AddReference(node.Value!, node.Type); return node; } protected internal override Expression VisitUnary(UnaryExpression node) { if (node.NodeType == ExpressionType.Quote) { bool savedInQuote = _inQuote; _inQuote = true; Visit(node.Operand); _inQuote = savedInQuote; } else { Visit(node.Operand); } return node; } protected internal override Expression VisitLambda<T>(Expression<T> node) { _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, true)); _constants.Push(_tree.Constants[node] = new BoundConstants()); Visit(MergeScopes(node)); _constants.Pop(); _scopes.Pop(); return node; } protected internal override Expression VisitInvocation(InvocationExpression node) { LambdaExpression? lambda = node.LambdaOperand; // optimization: inline code for literal lambda's directly if (lambda != null) { // visit the lambda, but treat it like a scope associated with invocation _scopes.Push(_tree.Scopes[node] = new CompilerScope(lambda, false)); Visit(MergeScopes(lambda)); _scopes.Pop(); // visit the invoke's arguments for (int i = 0, n = node.ArgumentCount; i < n; i++) { Visit(node.GetArgument(i)); } return node; } return base.VisitInvocation(node); } protected internal override Expression VisitBlock(BlockExpression node) { if (node.Variables.Count == 0) { Visit(node.Expressions); return node; } _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, false)); Visit(MergeScopes(node)); _scopes.Pop(); return node; } protected override CatchBlock VisitCatchBlock(CatchBlock node) { if (node.Variable == null) { Visit(node.Filter); Visit(node.Body); return node; } _scopes.Push(_tree.Scopes[node] = new CompilerScope(node, false)); Visit(node.Filter); Visit(node.Body); _scopes.Pop(); return node; } // If the immediate child is another scope, merge it into this one // This is an optimization to save environment allocations and // array accesses. private ReadOnlyCollection<Expression> MergeScopes(Expression node) { ReadOnlyCollection<Expression> body; var lambda = node as LambdaExpression; if (lambda != null) { body = new ReadOnlyCollection<Expression>(new[] { lambda.Body }); } else { body = ((BlockExpression)node).Expressions; } CompilerScope currentScope = _scopes.Peek(); // A block body is mergeable if the body only contains one single block node containing variables, // and the child block has the same type as the parent block. while (body.Count == 1 && body[0].NodeType == ExpressionType.Block) { var block = (BlockExpression)body[0]; if (block.Variables.Count > 0) { // Make sure none of the variables are shadowed. If any // are, we can't merge it. foreach (ParameterExpression v in block.Variables) { if (currentScope.Definitions.ContainsKey(v)) { return body; } } // Otherwise, merge it currentScope.MergedScopes ??= new HashSet<BlockExpression>(ReferenceEqualityComparer.Instance); currentScope.MergedScopes.Add(block); foreach (ParameterExpression v in block.Variables) { currentScope.Definitions.Add(v, VariableStorageKind.Local); } } body = block.Expressions; } return body; } protected internal override Expression VisitParameter(ParameterExpression node) { Reference(node, VariableStorageKind.Local); // // Track reference count so we can emit it in a more optimal way if // it is used a lot. // CompilerScope? referenceScope = null; foreach (CompilerScope scope in _scopes) { // // There are two times we care about references: // 1. When we enter a lambda, we want to cache frequently // used variables // 2. When we enter a scope with closed-over variables, we // want to cache it immediately when we allocate the // closure slot for it // if (scope.IsMethod || scope.Definitions.ContainsKey(node)) { referenceScope = scope; break; } } Debug.Assert(referenceScope != null); referenceScope.ReferenceCount ??= new Dictionary<ParameterExpression, int>(); Helpers.IncrementCount(node, referenceScope.ReferenceCount); return node; } protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) { foreach (ParameterExpression v in node.Variables) { // Force hoisting of these variables Reference(v, VariableStorageKind.Hoisted); } return node; } private void Reference(ParameterExpression node, VariableStorageKind storage) { CompilerScope? definition = null; foreach (CompilerScope scope in _scopes) { if (scope.Definitions.ContainsKey(node)) { definition = scope; break; } scope.NeedsClosure = true; if (scope.IsMethod) { storage = VariableStorageKind.Hoisted; } } if (definition == null) { throw Error.UndefinedVariable(node.Name, node.Type, CurrentLambdaName); } if (storage == VariableStorageKind.Hoisted) { if (node.IsByRef) { throw Error.CannotCloseOverByRef(node.Name, CurrentLambdaName); } definition.Definitions[node] = VariableStorageKind.Hoisted; } } private string? CurrentLambdaName { get { foreach (CompilerScope scope in _scopes) { if (scope.Node is LambdaExpression lambda) { return lambda.Name; } } throw ContractUtils.Unreachable; } } } }