private void MakeAndAssignEnvironments() { VisitScopeTree(ScopeTree, scope => { // Currently all variables declared in the same scope are added // to the same closure environment var variablesInEnvironment = scope.DeclaredVariables; // Don't create empty environments if (variablesInEnvironment.Count == 0) { return; } // First walk the nested scopes to find all closures which // capture variables from this scope. They all need to capture // this environment. This includes closures which captured local // functions that capture those variables, so multiple passes may // be needed. This will also decide if the environment is a struct // or a class. // If we are in a variant interface, runtime might not consider the // method synthesized directly within the interface as variant safe. // For simplicity we do not perform precise analysis whether this would // definitely be the case. If we are in a variant interface, we always force // creation of a display class. bool isStruct = VarianceSafety.GetEnclosingVariantInterface(_topLevelMethod) is null; var closures = new SetWithInsertionOrder <Closure>(); bool addedItem; // This loop is O(n), where n is the length of the chain // L_1 <- L_2 <- L_3 ... // where L_1 represents a local function that directly captures the current // environment, L_2 represents a local function that directly captures L_1, // L_3 represents a local function that captures L_2, and so on. // // Each iteration of the loop runs a visitor that is proportional to the // number of closures in nested scopes, so we hope that the total number // of nested functions and function chains is small in any real-world code. do { addedItem = false; VisitClosures(scope, (closureScope, closure) => { if (!closures.Contains(closure) && (closure.CapturedVariables.Overlaps(scope.DeclaredVariables) || closure.CapturedVariables.Overlaps(closures.Select(c => c.OriginalMethodSymbol)))) { closures.Add(closure); addedItem = true; isStruct &= CanTakeRefParameters(closure.OriginalMethodSymbol); } }); } while (addedItem == true); // Next create the environment and add it to the declaration scope var env = new ClosureEnvironment(variablesInEnvironment, isStruct); scope.DeclaredEnvironments.Add(env); _topLevelMethod.TryGetThisParameter(out var thisParam); foreach (var closure in closures) { closure.CapturedEnvironments.Add(env); if (thisParam != null && env.CapturedVariables.Contains(thisParam)) { closure.CapturesThis = true; } } }); }
/// <summary> /// We may have ended up with a closure environment containing only /// 'this'. This is basically equivalent to the containing type itself, /// so we can inline the 'this' parameter into environments that /// reference this one or lower closures directly onto the containing /// type. /// </summary> private void InlineThisOnlyEnvironments() { // First make sure 'this' even exists if (!_topLevelMethod.TryGetThisParameter(out var thisParam) || thisParam == null) { return; } var topLevelEnvs = ScopeTree.DeclaredEnvironments; // If it does exist, 'this' is always in the top-level environment if (topLevelEnvs.Count == 0) { return; } Debug.Assert(topLevelEnvs.Count == 1); var env = topLevelEnvs[0]; // The environment must contain only 'this' to be inlined if (env.CapturedVariables.Count > 1 || !env.CapturedVariables.Contains(thisParam)) { return; } if (env.IsStruct) { // If everything that captures the 'this' environment // lives in the containing type, we can remove the env bool cantRemove = CheckClosures(ScopeTree, (scope, closure) => { return(closure.CapturedEnvironments.Contains(env) && closure.ContainingEnvironmentOpt != null); }); if (!cantRemove) { RemoveEnv(); } } // If we are in a variant interface, runtime might not consider the // method synthesized directly within the interface as variant safe. // For simplicity we do not perform precise analysis whether this would // definitely be the case. If we are in a variant interface, we always force // creation of a display class. else if (VarianceSafety.GetEnclosingVariantInterface(_topLevelMethod) is null) { // Class-based 'this' closures can move member functions to // the top-level type and environments which capture the 'this' // environment can capture 'this' directly. // Note: the top-level type is treated as the initial containing // environment, so by removing the 'this' environment, all // nested environments which captured a pointer to the 'this' // environment will now capture 'this' RemoveEnv(); VisitClosures(ScopeTree, (scope, closure) => { if (closure.ContainingEnvironmentOpt == env) { closure.ContainingEnvironmentOpt = null; } }); } void RemoveEnv() { topLevelEnvs.RemoveAt(topLevelEnvs.IndexOf(env)); VisitClosures(ScopeTree, (scope, closure) => { var index = closure.CapturedEnvironments.IndexOf(env); if (index >= 0) { closure.CapturedEnvironments.RemoveAt(index); } }); } }