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.Linq.Expressions;
19 using Microsoft.Scripting.Ast;
23 using System.Collections.Generic;
24 using System.Runtime.CompilerServices;
25 using Microsoft.Scripting.Utils;
26 using AstUtils = Microsoft.Scripting.Ast.Utils;
28 namespace Microsoft.Scripting.Interpreter {
31 /// Visits a LambdaExpression, replacing the constants with direct accesses
32 /// to their StrongBox fields. This is very similar to what
33 /// ExpressionQuoter does for LambdaCompiler.
35 /// Also inserts debug information tracking similar to what the interpreter
38 internal sealed class LightLambdaClosureVisitor : ExpressionVisitor {
40 /// Local variable mapping.
42 private readonly Dictionary<ParameterExpression, LocalVariable> _closureVars;
45 /// The variable that holds onto the StrongBox{object}[] closure from
48 private readonly ParameterExpression _closureArray;
51 /// A stack of variables that are defined in nested scopes. We search
52 /// this first when resolving a variable in case a nested scope shadows
53 /// one of our variable instances.
55 private readonly Stack<HashSet<ParameterExpression>> _shadowedVars = new Stack<HashSet<ParameterExpression>>();
57 private LightLambdaClosureVisitor(Dictionary<ParameterExpression, LocalVariable> closureVariables, ParameterExpression closureArray) {
58 Assert.NotNull(closureVariables, closureArray);
59 _closureArray = closureArray;
60 _closureVars = closureVariables;
64 /// Walks the lambda and produces a higher order function, which can be
65 /// used to bind the lambda to a closure array from the interpreter.
67 /// <param name="lambda">The lambda to bind.</param>
68 /// <param name="closureVariables">Variables which are being accessed defined in the outer scope.</param>
69 /// <returns>A delegate that can be called to produce a delegate bound to the passed in closure array.</returns>
70 internal static Func<StrongBox<object>[], Delegate> BindLambda(LambdaExpression lambda, Dictionary<ParameterExpression, LocalVariable> closureVariables) {
72 var closure = Expression.Parameter(typeof(StrongBox<object>[]), "closure");
73 var visitor = new LightLambdaClosureVisitor(closureVariables, closure);
75 // 2. Visit the lambda
76 lambda = (LambdaExpression)visitor.Visit(lambda);
78 // 3. Create a higher-order function which fills in the parameters
79 var result = Expression.Lambda<Func<StrongBox<object>[], Delegate>>(lambda, closure);
82 return result.Compile();
87 protected override Expression VisitLambda<T>(Expression<T> node) {
88 _shadowedVars.Push(new HashSet<ParameterExpression>(node.Parameters));
89 Expression b = Visit(node.Body);
94 return Expression.Lambda<T>(b, node.Name, node.TailCall, node.Parameters);
97 protected override Expression VisitBlock(BlockExpression node) {
98 if (node.Variables.Count > 0) {
99 _shadowedVars.Push(new HashSet<ParameterExpression>(node.Variables));
101 var b = Visit(node.Expressions);
102 if (node.Variables.Count > 0) {
105 if (b == node.Expressions) {
108 return Expression.Block(node.Variables, b);
111 protected override CatchBlock VisitCatchBlock(CatchBlock node) {
112 if (node.Variable != null) {
113 _shadowedVars.Push(new HashSet<ParameterExpression>(new[] { node.Variable }));
115 Expression b = Visit(node.Body);
116 Expression f = Visit(node.Filter);
117 if (node.Variable != null) {
120 if (b == node.Body && f == node.Filter) {
123 return Expression.MakeCatchBlock(node.Test, node.Variable, b, f);
126 protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) {
127 int count = node.Variables.Count;
128 var boxes = new List<Expression>();
129 var vars = new List<ParameterExpression>();
130 var indexes = new int[count];
131 for (int i = 0; i < count; i++) {
132 Expression box = GetClosureItem(node.Variables[i], false);
134 indexes[i] = vars.Count;
135 vars.Add(node.Variables[i]);
137 indexes[i] = -1 - boxes.Count;
142 // No variables were rewritten. Just return the original node.
143 if (boxes.Count == 0) {
147 var boxesArray = Expression.NewArrayInit(typeof(IStrongBox), boxes);
149 // All of them were rewritten. Just return the array, wrapped in a
150 // read-only collection.
151 if (vars.Count == 0) {
152 return Expression.Invoke(
153 Expression.Constant((Func<IStrongBox[], IRuntimeVariables>)RuntimeVariables.Create),
158 // Otherwise, we need to return an object that merges them
159 Func<IRuntimeVariables, IRuntimeVariables, int[], IRuntimeVariables> helper = MergedRuntimeVariables.Create;
160 return Expression.Invoke(AstUtils.Constant(helper), Expression.RuntimeVariables(vars), boxesArray, AstUtils.Constant(indexes));
163 protected override Expression VisitParameter(ParameterExpression node) {
164 Expression closureItem = GetClosureItem(node, true);
165 if (closureItem == null) {
168 // Convert can go away if we switch to strongly typed StrongBox
169 return Ast.Utils.Convert(closureItem, node.Type);
172 protected override Expression VisitBinary(BinaryExpression node) {
173 if (node.NodeType == ExpressionType.Assign &&
174 node.Left.NodeType == ExpressionType.Parameter) {
176 var variable = (ParameterExpression)node.Left;
177 Expression closureItem = GetClosureItem(variable, true);
178 if (closureItem != null) {
179 // We need to convert to object to store the value in the box.
180 return Expression.Block(
182 Expression.Assign(variable, Visit(node.Right)),
183 Expression.Assign(closureItem, Ast.Utils.Convert(variable, typeof(object))),
188 return base.VisitBinary(node);
191 private Expression GetClosureItem(ParameterExpression variable, bool unbox) {
192 // Skip variables that are shadowed by a nested scope/lambda
193 foreach (HashSet<ParameterExpression> hidden in _shadowedVars) {
194 if (hidden.Contains(variable)) {
200 if (!_closureVars.TryGetValue(variable, out loc)) {
201 throw new InvalidOperationException("unbound variable: " + variable.Name);
204 var result = loc.LoadFromArray(null, _closureArray);
205 return (unbox) ? LightCompiler.Unbox(result) : result;
208 protected override Expression VisitExtension(Expression node) {
209 // Reduce extensions now so we can find embedded variables
210 return Visit(node.ReduceExtensions());
214 #region MergedRuntimeVariables
217 /// Provides a list of variables, supporing read/write of the values
219 private sealed class MergedRuntimeVariables : IRuntimeVariables {
220 private readonly IRuntimeVariables _first;
221 private readonly IRuntimeVariables _second;
223 // For reach item, the index into the first or second list
224 // Positive values mean the first array, negative means the second
225 private readonly int[] _indexes;
227 private MergedRuntimeVariables(IRuntimeVariables first, IRuntimeVariables second, int[] indexes) {
233 internal static IRuntimeVariables Create(IRuntimeVariables first, IRuntimeVariables second, int[] indexes) {
234 return new MergedRuntimeVariables(first, second, indexes);
237 int IRuntimeVariables.Count {
238 get { return _indexes.Length; }
241 object IRuntimeVariables.this[int index] {
243 index = _indexes[index];
244 return (index >= 0) ? _first[index] : _second[-1 - index];
247 index = _indexes[index];
249 _first[index] = value;
251 _second[-1 - index] = value;