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); if (referenceScope.ReferenceCount == null) { referenceScope.ReferenceCount = new Dictionary <ParameterExpression, int>(); } Helpers.IncrementCount(node, referenceScope.ReferenceCount); return(node); }
private void EnterScope(object node) { if (HasVariables(node) && (_scope.MergedScopes == null || !_scope.MergedScopes.Contains(node))) { CompilerScope scope; if (!_tree.Scopes.TryGetValue(node, out scope)) { // // Very often, we want to compile nodes as reductions // rather than as IL, but usually they need to allocate // some IL locals. To support this, we allow emitting a // BlockExpression that was not bound by VariableBinder. // This works as long as the variables are only used // locally -- i.e. not closed over. // // User-created blocks will never hit this case; only our // internally reduced nodes will. // scope = new CompilerScope(node, false) { NeedsClosure = _scope.NeedsClosure }; } _scope = scope.Enter(this, _scope); Debug.Assert(_scope.Node == node); } }
private void SetParent(LambdaCompiler lc, CompilerScope parent) { Debug.Assert(_parent == null && parent != this); _parent = parent; if (NeedsClosure && _parent != null) { _closureHoistedLocals = _parent.NearestHoistedLocals; } var hoistedVars = GetVariables().Where(p => Definitions[p] == VariableStorageKind.Hoisted).ToReadOnly(); if (hoistedVars.Count > 0) { _hoistedLocals = new HoistedLocals(_closureHoistedLocals, hoistedVars); AddLocal(lc, _hoistedLocals.SelfVariable); } }
/// <summary> /// Frees unnamed locals, clears state associated with this compiler /// </summary> internal CompilerScope Exit() { // free scope's variables if (!IsMethod) { foreach (Storage storage in _locals.Values) { storage.FreeLocal(); } } // Clear state that is associated with this parent // (because the scope can be reused in another context) CompilerScope parent = _parent; _parent = null; _hoistedLocals = null; _closureHoistedLocals = null; _locals.Clear(); return parent; }
/// <summary> /// Called when entering a lambda/block. Performs all variable allocation /// needed, including creating hoisted locals and IL locals for accessing /// parent locals /// </summary> internal CompilerScope Enter(LambdaCompiler lc, CompilerScope parent) { SetParent(lc, parent); AllocateLocals(lc); if (IsMethod && _closureHoistedLocals != null) { EmitClosureAccess(lc, _closureHoistedLocals); } EmitNewHoistedLocals(lc); if (IsMethod) { EmitCachedVariables(); } return this; }
public CompilerScope(CompilerScope parent, CompilerScopeType type) { this.Parent = parent; this.Type = type; }
/// <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)); }