/// <summary> /// Visit block expressions for scope tracking and closure emitting purposes. /// </summary> /// <param name="node">The block expression to rewrite.</param> /// <returns>The rewritten expression.</returns> protected override Expression VisitBlock(BlockExpression node) { // // Introduce a new scope and keep track of the original scope in order to restore // it after visiting child expressions. // var currentScope = _scope; try { var scope = _analysis[node]; _scope = new CompilerScope(currentScope, scope); if (!scope.HasHoistedLocals) { // // In case there are no hoisted locals, we don't need to use a builder to // instantiate a closure. Simply visit the child expressions and update. // var expressions = Visit(node.Expressions); return(node.Update(node.Variables, expressions)); } else { // // In case there are hoisted locals, enter the scope to obtain a builder, // append the visited child expressions, and return a new block. This will // add the statements needed to instantiate the closure and link it to its // parent (if any). // var expressions = node.Expressions; var n = expressions.Count; // // Note we don't copy hoisted locals during Enter because the storage // location of these is superseded by the storage field in the closure. // var builder = _scope.Enter(count: n, copyLocals: false); for (var i = 0; i < n; i++) { builder.Append(Visit(expressions[i])); } // // Note that we don't update the original block here; the builder will // create a new block anyway, and we can simply use that as our substitute, // provided the original (non-hoisted) locals are declared. // return(builder.Finish(declareLocals: true)); } } finally { _scope = currentScope; } }
/// <summary> /// Visit lambda expression for scope tracking and closure emitting purposes. /// </summary> /// <typeparam name="T">The type of the delegate represented by the lambda expression.</typeparam> /// <param name="node">The lambda expression to rewrite.</param> /// <returns>The rewritten lambda expression.</returns> protected override Expression VisitLambda <T>(Expression <T> node) { // // Allocate a slot for information about the rewritten lambda expression and thunk // type. We add a temporary empty value to the dictionary slot here in order to // boost the count of entries. This causes the index to be logically numbered in // the order the lambda expressions were visited. // var index = _lambdas.Count; _lambdas[index] = default; // // Introduce a new scope and keep track of the original scope in order to restore // it after visiting child expressions. // var currentScope = _scope; try { var scope = _analysis[node]; _scope = new CompilerScope(currentScope, scope); var body = default(Expression); if (!scope.HasHoistedLocals && !scope.NeedsClosure) { body = Visit(node.Body); } else { // // Visit the body and append it to the builder. Note that hoisted locals // (i.e. parameters) are copied into the closure. // var builder = _scope.Enter(count: 1, copyLocals: true); builder.Append(Visit(node.Body)); body = builder.Finish(declareLocals: false); } // // Use the closure parameter of the original scope (i.e. the parent to the // scope representing the lambda) to derive information about the thunk type. // var closureParam = currentScope.Closure; var thunkType = _thunkFactory.GetThunkType(typeof(T), closureParam.Type); var innerDelegateType = GetInnerDelegateType(thunkType); // // Construct a lambda that's parameterized on the closure that's passed in, // using the inner delegate type that the thunk expects. This becomes the // rewritten lambda expression, which will be passed to the constructor of // the thunk type in GetMethodTable. // var parameters = new ParameterExpression[node.Parameters.Count + 1]; parameters[0] = closureParam; node.Parameters.CopyTo(parameters, index: 1); var lambda = Expression.Lambda(innerDelegateType, body, parameters); _lambdas[index] = new LambdaInfo(lambda, thunkType); // // Inside the expression tree, replace the lambda by a call to CreateDelegate // on the thunk. The thunk is retrieved from the method table that's passed to // the top-level lambda by indexing into the Thunks array. // var createDelegate = thunkType.GetMethod(nameof(ActionThunk <object> .CreateDelegate)); var methodTable = currentScope.Bind(_methodTable); var createDelegateCall = Expression.Call( Expression.Convert( Expression.ArrayIndex( Expression.Field( methodTable, nameof(MethodTable.Thunks) ), Expression.Constant(index) ), thunkType ), createDelegate, closureParam ); return(createDelegateCall); } finally { _scope = currentScope; } }
/// <summary> /// Visit catch blocks for scope tracking and closure emitting purposes. /// </summary> /// <param name="node">The catch block to rewrite.</param> /// <returns>The rewritten catch block.</returns> protected override CatchBlock VisitCatchBlock(CatchBlock node) { // // Store the filter and body in two locals. These will get overwritten by the // result of recursive visits and/or rewrites using the builder (see below), prior // to their usage to update the original expression. // var filter = node.Filter; var body = node.Body; // // Introduce a new scope and keep track of the original scope in order to restore // it after visiting child expressions. // var currentScope = _scope; try { var scope = _analysis[node]; _scope = new CompilerScope(currentScope, scope); if (!scope.HasHoistedLocals) { // // In case there is no hoisted local, we don't need to use a builder to // instantiate a closure. Simply visit the child expressions. // filter = Visit(filter); body = Visit(body); } else { // // CONSIDER: Refine the analysis step in order to keep track of the child // expressions where the variable needs to be hoisted into a // closure. Right now, we may use an unnecessary closure. // // // In case there is a hoisted local, enter the scope to obtain a builder, // append the visited child expressions, and return a new block. This will // add the statements needed to instantiate the closure and link it to its // parent (if any). // // Note that the original variable holding the caught exception instance is // retained, so: // // * if hoisted, we copy it to the closure in Enter -and- // * we don't re-declare it in Finish. // if (filter != null) { var builder = _scope.Enter(count: 1, copyLocals: true); builder.Append(Visit(filter)); filter = builder.Finish(declareLocals: false); } if (body != null) { var builder = _scope.Enter(count: 1, copyLocals: true); builder.Append(Visit(body)); body = builder.Finish(declareLocals: false); } } } finally { _scope = currentScope; } // // Simply update the expression using the rewritten child expressions. Note that // the variable remains unchanged; its contents could be copied by the rewritten // child expressions in order to hoist it into a closure. // return(node.Update(node.Variable, filter, body)); }