diff --git a/src/Compilers/CSharp/Portable/CSharpParseOptions.cs b/src/Compilers/CSharp/Portable/CSharpParseOptions.cs index 25cd68f968b69bcc5f1ba3d4c3df04158d63bafb..66ad37bc0006e61a69fd5e4e39b33772c9d34249 100644 --- a/src/Compilers/CSharp/Portable/CSharpParseOptions.cs +++ b/src/Compilers/CSharp/Portable/CSharpParseOptions.cs @@ -61,7 +61,7 @@ public override IEnumerable PreprocessorSymbolNames LanguageVersion languageVersion, DocumentationMode documentationMode, SourceCodeKind kind, - IEnumerable preprocessorSymbols, + ImmutableArray preprocessorSymbols, IReadOnlyDictionary features) : base(kind, documentationMode) { diff --git a/src/Compilers/CSharp/Portable/Compiler/TypeCompilationState.cs b/src/Compilers/CSharp/Portable/Compiler/TypeCompilationState.cs index a81b758e6f0171c70a9a39dbe2a650f0738e3f43..2898d62ca6289237aff4cea2eca601aee0819e59 100644 --- a/src/Compilers/CSharp/Portable/Compiler/TypeCompilationState.cs +++ b/src/Compilers/CSharp/Portable/Compiler/TypeCompilationState.cs @@ -66,7 +66,7 @@ internal MethodWithBody(MethodSymbol method, BoundStatement body, ImportChain im public readonly CSharpCompilation Compilation; - public LambdaFrame StaticLambdaFrame; + public ClosureEnvironment StaticLambdaFrame; /// /// A graph of method->method references for this(...) constructor initializers. diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaCapturedVariable.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaCapturedVariable.cs index 80f126174377651e0b151879fe002b1c8ee84667..a67f98a89bffea9e6cc87250231005a2cd99b448 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaCapturedVariable.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaCapturedVariable.cs @@ -31,7 +31,7 @@ private LambdaCapturedVariable(SynthesizedContainer frame, TypeSymbol type, stri _isThis = isThisParameter; } - public static LambdaCapturedVariable Create(LambdaFrame frame, Symbol captured, ref int uniqueId) + public static LambdaCapturedVariable Create(ClosureEnvironment frame, Symbol captured, ref int uniqueId) { Debug.Assert(captured is LocalSymbol || captured is ParameterSymbol); @@ -87,7 +87,7 @@ private static TypeSymbol GetCapturedVariableFieldType(SynthesizedContainer fram if ((object)local != null) { // if we're capturing a generic frame pointer, construct it with the new frame's type parameters - var lambdaFrame = local.Type.OriginalDefinition as LambdaFrame; + var lambdaFrame = local.Type.OriginalDefinition as ClosureEnvironment; if ((object)lambdaFrame != null) { // lambdaFrame may have less generic type parameters than frame, so trim them down (the first N will always match) diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs index 43c5bd3a8729e334f54629a7fdc673ce45d1896f..5ef934ebdc892d137d2699aae6267abf94b8210f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs @@ -1,44 +1,58 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { /// /// A class that represents the set of variables in a scope that have been - /// captured by lambdas within that scope. + /// captured by nested functions within that scope. /// - internal sealed class LambdaFrame : SynthesizedContainer, ISynthesizedMethodBodyImplementationSymbol + internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMethodBodyImplementationSymbol { - private readonly TypeKind _typeKind; private readonly MethodSymbol _topLevelMethod; - private readonly MethodSymbol _containingMethod; - private readonly MethodSymbol _constructor; - private readonly MethodSymbol _staticConstructor; - private readonly FieldSymbol _singletonCache; internal readonly SyntaxNode ScopeSyntaxOpt; internal readonly int ClosureOrdinal; - - internal LambdaFrame(MethodSymbol topLevelMethod, MethodSymbol containingMethod, bool isStruct, SyntaxNode scopeSyntaxOpt, DebugId methodId, DebugId closureId) + /// + /// The closest method/lambda that this frame is originally from. Null if nongeneric static closure. + /// Useful because this frame's type parameters are constructed from this method and all methods containing this method. + /// + internal readonly MethodSymbol OriginalContainingMethodOpt; + internal readonly FieldSymbol SingletonCache; + internal readonly MethodSymbol StaticConstructor; + public readonly IEnumerable CapturedVariables; + + public override TypeKind TypeKind { get; } + internal override MethodSymbol Constructor { get; } + + internal ClosureEnvironment( + IEnumerable capturedVariables, + MethodSymbol topLevelMethod, + MethodSymbol containingMethod, + bool isStruct, + SyntaxNode scopeSyntaxOpt, + DebugId methodId, + DebugId closureId) : base(MakeName(scopeSyntaxOpt, methodId, closureId), containingMethod) { - _typeKind = isStruct ? TypeKind.Struct : TypeKind.Class; + CapturedVariables = capturedVariables; + TypeKind = isStruct ? TypeKind.Struct : TypeKind.Class; _topLevelMethod = topLevelMethod; - _containingMethod = containingMethod; - _constructor = isStruct ? null : new LambdaFrameConstructor(this); + OriginalContainingMethodOpt = containingMethod; + Constructor = isStruct ? null : new LambdaFrameConstructor(this); this.ClosureOrdinal = closureId.Ordinal; // static lambdas technically have the class scope so the scope syntax is null if (scopeSyntaxOpt == null) { - _staticConstructor = new SynthesizedStaticConstructor(this); + StaticConstructor = new SynthesizedStaticConstructor(this); var cacheVariableName = GeneratedNames.MakeCachedFrameInstanceFieldName(); - _singletonCache = new SynthesizedLambdaCacheFieldSymbol(this, this, cacheVariableName, topLevelMethod, isReadOnly: true, isStatic: true); + SingletonCache = new SynthesizedLambdaCacheFieldSymbol(this, this, cacheVariableName, topLevelMethod, isReadOnly: true, isStatic: true); } AssertIsClosureScopeSyntax(scopeSyntaxOpt); @@ -77,69 +91,25 @@ private static void AssertIsClosureScopeSyntax(SyntaxNode syntaxOpt) throw ExceptionUtilities.UnexpectedValue(syntaxOpt.Kind()); } - public override TypeKind TypeKind - { - get { return _typeKind; } - } - - internal override MethodSymbol Constructor - { - get { return _constructor; } - } - - internal MethodSymbol StaticConstructor - { - get { return _staticConstructor; } - } - - /// - /// The closest method/lambda that this frame is originally from. Null if nongeneric static closure. - /// Useful because this frame's type parameters are constructed from this method and all methods containing this method. - /// - internal MethodSymbol ContainingMethod - { - get { return _containingMethod; } - } - public override ImmutableArray GetMembers() { var members = base.GetMembers(); - if ((object)_staticConstructor != null) + if ((object)StaticConstructor != null) { - members = ImmutableArray.Create(_staticConstructor, _singletonCache).AddRange(members); + members = ImmutableArray.Create(StaticConstructor, SingletonCache).AddRange(members); } return members; } - internal FieldSymbol SingletonCache - { - get { return _singletonCache; } - } - // display classes for static lambdas do not have any data and can be serialized. - internal override bool IsSerializable - { - get { return (object)_singletonCache != null; } - } + internal override bool IsSerializable => (object)SingletonCache != null; - public override Symbol ContainingSymbol - { - get { return _topLevelMethod.ContainingSymbol; } - } + public override Symbol ContainingSymbol => _topLevelMethod.ContainingSymbol; - bool ISynthesizedMethodBodyImplementationSymbol.HasMethodBodyDependency - { - get - { - // the lambda method contains user code from the lambda: - return true; - } - } + // The lambda method contains user code from the lambda + bool ISynthesizedMethodBodyImplementationSymbol.HasMethodBodyDependency => true; - IMethodSymbol ISynthesizedMethodBodyImplementationSymbol.Method - { - get { return _topLevelMethod; } - } + IMethodSymbol ISynthesizedMethodBodyImplementationSymbol.Method => _topLevelMethod; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrameConstructor.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrameConstructor.cs index b5e90ce0f5a283555c1269eaa7b8c87d730a50e8..b64c048f75971e26639626a02885aac7e2384f62 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrameConstructor.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrameConstructor.cs @@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis.CSharp { internal sealed class LambdaFrameConstructor : SynthesizedInstanceConstructor, ISynthesizedMethodBodyImplementationSymbol { - internal LambdaFrameConstructor(LambdaFrame frame) + internal LambdaFrameConstructor(ClosureEnvironment frame) : base(frame) { } diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.Tree.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.Tree.cs index 386dc38534b59e635d134016a3ee48ec570e8466..f776ab258519f5b1f558052a25c8371963de383c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.Tree.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.Tree.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -25,14 +26,14 @@ internal sealed partial class Analysis : BoundTreeWalkerWithStackGuardWithoutRec [DebuggerDisplay("{ToString(), nq}")] public sealed class Scope { - public Scope Parent { get; } + public readonly Scope Parent; - public ArrayBuilder NestedScopes { get; } = ArrayBuilder.GetInstance(); + public readonly ArrayBuilder NestedScopes = ArrayBuilder.GetInstance(); /// /// A list of all closures (all lambdas and local functions) declared in this scope. /// - public ArrayBuilder Closures { get; } = ArrayBuilder.GetInstance(); + public readonly ArrayBuilder Closures = ArrayBuilder.GetInstance(); /// /// A list of all locals or parameters that were declared in this scope and captured @@ -45,7 +46,7 @@ public sealed class Scope /// non-deterministic compilation, and if we generate duplicate proxies we'll generate /// wasteful code in the best case and incorrect code in the worst. /// - public SetWithInsertionOrder DeclaredVariables { get; } = new SetWithInsertionOrder(); + public readonly SetWithInsertionOrder DeclaredVariables = new SetWithInsertionOrder(); /// /// The bound node representing this scope. This roughly corresponds to the bound @@ -53,13 +54,19 @@ public sealed class Scope /// methods/closures are introduced into their Body's scope and do not get their /// own scope. /// - public BoundNode BoundNode { get; } + public readonly BoundNode BoundNode; /// /// The closure that this scope is nested inside. Null if this scope is not nested /// inside a closure. /// - public Closure ContainingClosureOpt { get; } + public readonly Closure ContainingClosureOpt; + + /// + /// Environments created in this scope to hold . + /// + public readonly ArrayBuilder DeclaredEnvironments + = ArrayBuilder.GetInstance(); public Scope(Scope parent, BoundNode boundNode, Closure containingClosure) { @@ -83,6 +90,7 @@ public void Free() closure.Free(); } Closures.Free(); + DeclaredEnvironments.Free(); } public override string ToString() => BoundNode.Syntax.GetText().ToString(); @@ -102,9 +110,14 @@ public sealed class Closure /// /// The method symbol for the original lambda or local function. /// - public MethodSymbol OriginalMethodSymbol { get; } + public readonly MethodSymbol OriginalMethodSymbol; + + public readonly PooledHashSet CapturedVariables = PooledHashSet.GetInstance(); - public PooledHashSet CapturedVariables { get; } = PooledHashSet.GetInstance(); + public readonly ArrayBuilder CapturedEnvironments + = ArrayBuilder.GetInstance(); + + public ClosureEnvironment ContainingEnvironmentOpt; public Closure(MethodSymbol symbol) { @@ -115,6 +128,7 @@ public Closure(MethodSymbol symbol) public void Free() { CapturedVariables.Free(); + CapturedEnvironments.Free(); } } @@ -164,17 +178,36 @@ private void RemoveUnneededReferences(ParameterSymbol thisParam) } } + // True if there are any closures in the tree which + // capture 'this' and another variable + bool captureMoreThanThis = false; + VisitClosures(ScopeTree, (scope, closure) => { if (!capturesVariable.Contains(closure.OriginalMethodSymbol)) { closure.CapturedVariables.Clear(); } + if (capturesThis.Contains(closure.OriginalMethodSymbol)) { closure.CapturedVariables.Add(thisParam); + if (closure.CapturedVariables.Count > 1) + { + captureMoreThanThis |= true; + } } }); + + if (!captureMoreThanThis && capturesThis.Count > 0) + { + // If we have closures which capture 'this', and nothing else, we can + // remove 'this' from the declared variables list, since we don't need + // to create an environment to hold 'this' (since we can emit the + // lowered methods directly onto the containing class) + bool removed = ScopeTree.DeclaredVariables.Remove(thisParam); + Debug.Assert(removed); + } } /// @@ -193,6 +226,19 @@ public static void VisitClosures(Scope scope, Action action) } } + /// + /// Visit the tree with the given root and run the + /// + public static void VisitScopeTree(Scope treeRoot, Action action) + { + action(treeRoot); + + foreach (var nested in treeRoot.NestedScopes) + { + VisitScopeTree(nested, action); + } + } + /// /// Builds a tree of nodes corresponding to a given method. /// @@ -267,6 +313,12 @@ private void Build() { // Set up the current method locals DeclareLocals(_currentScope, _topLevelMethod.Parameters); + // Treat 'this' as a formal parameter of the top-level method + if (_topLevelMethod.TryGetThisParameter(out var thisParam)) + { + DeclareLocals(_currentScope, ImmutableArray.Create(thisParam)); + } + Visit(_currentScope.BoundNode); } @@ -468,12 +520,6 @@ private void AddIfCaptured(Symbol symbol, SyntaxNode syntax) return; } - // The 'this' parameter isn't declared in method scope - if (symbol is ParameterSymbol param && param.IsThis) - { - return; - } - if (_localToScope.TryGetValue(symbol, out var declScope)) { declScope.DeclaredVariables.Add(symbol); diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs index 8e3e0d936d140979c595dad87482bc393085e131..4c77933cd3c64eff8030e1b216830d0cc8d775d0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -29,12 +32,6 @@ internal sealed partial class Analysis // We can't rewrite delegate signatures || MethodsConvertedToDelegates.Contains(closure)); - /// - /// Any scope that a method that doesn't close over. - /// If a scope is in this set, don't use a struct closure. - /// - public readonly PooledHashSet ScopesThatCantBeStructs = PooledHashSet.GetInstance(); - /// /// Blocks that are positioned between a block declaring some lifted variables /// and a block that contains the lambda that lifts said variables. @@ -44,29 +41,44 @@ internal sealed partial class Analysis /// public readonly PooledHashSet NeedsParentFrame = PooledHashSet.GetInstance(); - /// - /// Optimized locations of lambdas. - /// - /// Lambda does not need to be placed in a frame that corresponds to its lexical scope if lambda does not reference any local state in that scope. - /// It is advantageous to place lambdas higher in the scope tree, ideally in the innermost scope of all scopes that contain variables captured by a given lambda. - /// Doing so reduces indirections needed when captured locals are accessed. For example locals from the innermost scope can be accessed with no indirection at all. - /// needs to be called to compute this. - /// - public readonly SmallDictionary LambdaScopes = - new SmallDictionary(ReferenceEqualityComparer.Instance); - /// /// The root of the scope tree for this method. /// public readonly Scope ScopeTree; - private Analysis(Scope scopeTree, PooledHashSet methodsConvertedToDelegates) + private readonly MethodSymbol _topLevelMethod; + private readonly int _topLevelMethodOrdinal; + private readonly MethodSymbol _substitutedSourceMethod; + private readonly VariableSlotAllocator _slotAllocatorOpt; + private readonly TypeCompilationState _compilationState; + + private Analysis( + Scope scopeTree, + PooledHashSet methodsConvertedToDelegates, + MethodSymbol topLevelMethod, + int topLevelMethodOrdinal, + MethodSymbol substitutedSourceMethod, + VariableSlotAllocator slotAllocatorOpt, + TypeCompilationState compilationState) { ScopeTree = scopeTree; MethodsConvertedToDelegates = methodsConvertedToDelegates; + _topLevelMethod = topLevelMethod; + _topLevelMethodOrdinal = topLevelMethodOrdinal; + _substitutedSourceMethod = substitutedSourceMethod; + _slotAllocatorOpt = slotAllocatorOpt; + _compilationState = compilationState; } - public static Analysis Analyze(BoundNode node, MethodSymbol method, DiagnosticBag diagnostics) + public static Analysis Analyze( + BoundNode node, + MethodSymbol method, + int topLevelMethodOrdinal, + MethodSymbol substitutedSourceMethod, + VariableSlotAllocator slotAllocatorOpt, + TypeCompilationState compilationState, + ArrayBuilder closureDebugInfo, + DiagnosticBag diagnostics) { var methodsConvertedToDelegates = PooledHashSet.GetInstance(); var scopeTree = ScopeTreeBuilder.Build( @@ -74,7 +86,21 @@ public static Analysis Analyze(BoundNode node, MethodSymbol method, DiagnosticBa method, methodsConvertedToDelegates, diagnostics); - return new Analysis(scopeTree, methodsConvertedToDelegates); + Debug.Assert(scopeTree != null); + + var analysis = new Analysis( + scopeTree, + methodsConvertedToDelegates, + method, + topLevelMethodOrdinal, + substitutedSourceMethod, + slotAllocatorOpt, + compilationState); + + analysis.RemoveUnneededReferences(method.ThisParameter); + analysis.MakeAndAssignEnvironments(closureDebugInfo); + analysis.ComputeLambdaScopesAndFrameCaptures(method.ThisParameter); + return analysis; } private static BoundNode FindNodeToAnalyze(BoundNode node) @@ -107,12 +133,17 @@ private static BoundNode FindNodeToAnalyze(BoundNode node) } /// - /// Create the optimized plan for the location of lambda methods and whether scopes need access to parent scopes - /// - internal void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam) + /// Must be called only after + /// has been calculated. + /// + /// Finds the most optimal capture environment to place a closure in. + /// This roughly corresponds to the 'highest' Scope in the tree where all + /// the captured variables for this closure are in scope. This minimizes + /// the number of indirections we may have to traverse to access captured + /// variables. + /// + private void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam) { - RemoveUnneededReferences(thisParam); - VisitClosures(ScopeTree, (scope, closure) => { if (closure.CapturedVariables.Count > 0) @@ -124,6 +155,15 @@ internal void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam) (Scope innermost, Scope outermost) FindLambdaScopeRange(Closure closure, Scope closureScope) { + // If the closure only captures this, put the method directly in the + // top-level method's containing type + if (closure.CapturedVariables.Count == 1 && + closure.CapturedVariables.Single() is ParameterSymbol param && + param.IsThis) + { + return (null, null); + } + Scope innermost = null; Scope outermost = null; @@ -149,8 +189,8 @@ internal void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam) curScope != null && capturedVars.Count > 0; curScope = curScope.Parent) { - if (!(capturedVars.Overlaps(curScope.DeclaredVariables) || - capturedVars.Overlaps(curScope.Closures.Select(c => c.OriginalMethodSymbol)))) + if (!(capturedVars.RemoveAll(curScope.DeclaredVariables) || + capturedVars.RemoveAll(curScope.Closures.Select(c => c.OriginalMethodSymbol)))) { continue; } @@ -160,9 +200,6 @@ internal void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam) { innermost = curScope; } - - capturedVars.RemoveAll(curScope.DeclaredVariables); - capturedVars.RemoveAll(curScope.Closures.Select(c => c.OriginalMethodSymbol)); } // If any captured variables are left, they're captured above method scope @@ -183,33 +220,119 @@ void RecordClosureScope(Scope innermost, Scope outermost, Closure closure) // // Example: // if a lambda captures a method's parameter and `this`, - // its innermost scope depth is 0 (method locals and parameters) - // and outermost scope is -1 + // its innermost scope is the root Scope (method locals and parameters) + // and outermost Scope is null // Such lambda will be placed in a closure frame that corresponds to the method's outer block // and this frame will also lift original `this` as a field when created by its parent. // Note that it is completely irrelevant how deeply the lexical scope of the lambda was originally nested. if (innermost != null) { - LambdaScopes.Add(closure.OriginalMethodSymbol, innermost.BoundNode); - - // Disable struct closures on methods converted to delegates, as well as on async and iterator methods. - var markAsNoStruct = !CanTakeRefParameters(closure.OriginalMethodSymbol); - if (markAsNoStruct) - { - ScopesThatCantBeStructs.Add(innermost.BoundNode); - } + closure.ContainingEnvironmentOpt = innermost.DeclaredEnvironments[0]; while (innermost != outermost) { NeedsParentFrame.Add(innermost.BoundNode); innermost = innermost.Parent; - if (markAsNoStruct && innermost != null) + } + } + } + } + + private void MakeAndAssignEnvironments(ArrayBuilder closureDebugInfo) + { + VisitScopeTree(ScopeTree, scope => + { + if (scope.DeclaredVariables.Count > 0) + { + // 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. + bool isStruct = true; + var closures = new SetWithInsertionOrder(); + bool addedItem; + do + { + addedItem = false; + VisitClosures(scope, (closureScope, closure) => { - ScopesThatCantBeStructs.Add(innermost.BoundNode); - } + 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 + // Currently all variables declared in the same scope are added + // to the same closure environment + var env = MakeEnvironment(scope, scope.DeclaredVariables, isStruct); + scope.DeclaredEnvironments.Add(env); + + foreach (var closure in closures) + { + closure.CapturedEnvironments.Add(env); } } + }); + + ClosureEnvironment MakeEnvironment(Scope scope, IEnumerable capturedVariables, bool isStruct) + { + var scopeBoundNode = scope.BoundNode; + + var syntax = scopeBoundNode.Syntax; + Debug.Assert(syntax != null); + + DebugId methodId = GetTopLevelMethodId(); + DebugId closureId = GetClosureId(syntax, closureDebugInfo); + + var containingMethod = scope.ContainingClosureOpt?.OriginalMethodSymbol ?? _topLevelMethod; + if ((object)_substitutedSourceMethod != null && containingMethod == _topLevelMethod) + { + containingMethod = _substitutedSourceMethod; + } + + return new ClosureEnvironment( + capturedVariables, + _topLevelMethod, + containingMethod, + isStruct, + syntax, + methodId, + closureId); + } + } + + internal DebugId GetTopLevelMethodId() + { + return _slotAllocatorOpt?.MethodId ?? new DebugId(_topLevelMethodOrdinal, _compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); + } + + private DebugId GetClosureId(SyntaxNode syntax, ArrayBuilder closureDebugInfo) + { + Debug.Assert(syntax != null); + + DebugId closureId; + DebugId previousClosureId; + if (_slotAllocatorOpt != null && _slotAllocatorOpt.TryGetPreviousClosure(syntax, out previousClosureId)) + { + closureId = previousClosureId; } + else + { + closureId = new DebugId(closureDebugInfo.Count, _compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); + } + + int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree); + closureDebugInfo.Add(new ClosureDebugInfo(syntaxOffset, closureId)); + + return closureId; } /// @@ -347,7 +470,6 @@ Closure Helper(Scope scope) public void Free() { MethodsConvertedToDelegates.Free(); - ScopesThatCantBeStructs.Free(); NeedsParentFrame.Free(); ScopeTree.Free(); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.LocalFunctionReferenceRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.LocalFunctionReferenceRewriter.cs index ecd59c21f17184c471180fac0d7e52dbb3ed9080..ffc7842e988367d7392475e253541d03396b6174 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.LocalFunctionReferenceRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.LocalFunctionReferenceRewriter.cs @@ -140,7 +140,7 @@ public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody) _framePointers.TryGetValue(synthesizedLambda.ContainingType, out _innermostFramePointer); } - var containerAsFrame = synthesizedLambda.ContainingType as LambdaFrame; + var containerAsFrame = synthesizedLambda.ContainingType as ClosureEnvironment; // Includes type parameters from the containing type iff // the containing type is a frame. If it is a frame then @@ -201,11 +201,11 @@ public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody) // will always be a LambdaFrame, it's always a capture frame var frameType = (NamedTypeSymbol)loweredSymbol.Parameters[i].Type.OriginalDefinition; - Debug.Assert(frameType is LambdaFrame); + Debug.Assert(frameType is ClosureEnvironment); if (frameType.Arity > 0) { - var typeParameters = ((LambdaFrame)frameType).ConstructedFromTypeParameters; + var typeParameters = ((ClosureEnvironment)frameType).ConstructedFromTypeParameters; Debug.Assert(typeParameters.Length == frameType.Arity); var subst = this.TypeMap.SubstituteTypeParameters(typeParameters); frameType = frameType.Construct(subst); diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs index b0e9ffca56a3876cd6ac9f6765ee2f5a2475db84..0b00ee2c34454a8b89180accd098b792311fdae9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs @@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.CSharp /// have captured variables. The result of this analysis is left in . /// /// Then we make a frame, or compiler-generated class, represented by an instance of - /// for each scope with captured variables. The generated frames are kept + /// for each scope with captured variables. The generated frames are kept /// in . Each frame is given a single field for each captured /// variable in the corresponding scope. These are maintained in . /// @@ -73,7 +73,7 @@ internal sealed partial class LambdaRewriter : MethodToClassRewriter // lambda frame for static lambdas. // initialized lazily and could be null if there are no static lambdas - private LambdaFrame _lazyStaticLambdaFrame; + private ClosureEnvironment _lazyStaticLambdaFrame; // A mapping from every lambda parameter to its corresponding method's parameter. private readonly Dictionary _parameterMap = new Dictionary(); @@ -93,7 +93,7 @@ public MappedLocalFunction(SynthesizedLambdaMethod symbol, ClosureKind closureKi private readonly Dictionary _localFunctionMap = new Dictionary(); // for each block with lifted (captured) variables, the corresponding frame type - private readonly Dictionary _frames = new Dictionary(); + private readonly Dictionary _frames = new Dictionary(); // the current set of frame pointers in scope. Each is either a local variable (where introduced), // or the "this" parameter when at the top level. Keys in this map are never constructed types. @@ -187,7 +187,8 @@ public MappedLocalFunction(SynthesizedLambdaMethod symbol, ClosureKind closureKi _assignLocals = assignLocals; _currentTypeParameters = method.TypeParameters; _currentLambdaBodyTypeMap = TypeMap.Empty; - _innermostFramePointer = _currentFrameThis = thisParameterOpt; + _innermostFramePointer = null; + _currentFrameThis = thisParameterOpt; _framePointers[thisType] = thisParameterOpt; _seenBaseCall = method.MethodKind != MethodKind.Constructor; // only used for ctors _synthesizedFieldNameIdDispenser = 1; @@ -243,7 +244,15 @@ protected override bool NeedsProxy(Symbol localOrParameter) Debug.Assert(((object)thisParameter == null) || (thisParameter.Type == thisType)); Debug.Assert(compilationState.ModuleBuilderOpt != null); - var analysis = Analysis.Analyze(loweredBody, method, diagnostics); + var analysis = Analysis.Analyze( + loweredBody, + method, + methodOrdinal, + substitutedSourceMethod, + slotAllocatorOpt, + compilationState, + closureDebugInfoBuilder, + diagnostics); CheckLocalsDefined(loweredBody); var rewriter = new LambdaRewriter( @@ -259,8 +268,7 @@ protected override bool NeedsProxy(Symbol localOrParameter) diagnostics, assignLocals); - analysis.ComputeLambdaScopesAndFrameCaptures(thisParameter); - rewriter.MakeFrames(closureDebugInfoBuilder); + rewriter.SynthesizeClosureEnvironments(); // First, lower everything but references (calls, delegate conversions) // to local functions @@ -333,161 +341,47 @@ protected override NamedTypeSymbol ContainingType static partial void CheckLocalsDefined(BoundNode node); /// - /// Create the frame types. + /// Adds synthesized types to the compilation state + /// and creates hoisted fields for all locals captured by the environments. /// - private void MakeFrames(ArrayBuilder closureDebugInfo) + private void SynthesizeClosureEnvironments() { - Analysis.VisitClosures(_analysis.ScopeTree, (scope, closure) => + Analysis.VisitScopeTree(_analysis.ScopeTree, scope => { - var capturedVars = closure.CapturedVariables; - MethodSymbol closureSymbol = closure.OriginalMethodSymbol; - bool canTakeRefParams = _analysis.CanTakeRefParameters(closureSymbol); - - if (canTakeRefParams && OnlyCapturesThis(closure, scope)) + if (scope.DeclaredEnvironments.Count > 0) { - return; - } + Debug.Assert(!_frames.ContainsKey(scope.BoundNode)); + // At the moment, all variables declared in the same + // scope always get assigned to the same environment + Debug.Assert(scope.DeclaredEnvironments.Count == 1); - foreach (var captured in capturedVars) - { - var declarationScope = Analysis.GetVariableDeclarationScope(scope, captured); - if (declarationScope == null) - { - continue; - } - - // If this is a local function that can take ref params, skip - // frame creation for local function calls. This is semantically - // important because otherwise we may create a struct frame which - // is empty, which crashes in emit. - // This is not valid for lambdas or local functions which can't take - // take ref params since they will be lowered into their own frames. - if (canTakeRefParams && captured.Kind == SymbolKind.Method) - { - continue; - } - - LambdaFrame frame = GetFrameForScope(declarationScope, closureDebugInfo); - - if (captured.Kind != SymbolKind.Method && !proxies.ContainsKey(captured)) - { - var hoistedField = LambdaCapturedVariable.Create(frame, captured, ref _synthesizedFieldNameIdDispenser); - proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false)); - CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, hoistedField); - } - } - }); - } - - private SmallDictionary _onlyCapturesThisMemoTable; - /// - /// Helper for determining whether a local function transitively - /// only captures this (only captures this or other local functions - /// which only capture this). - /// - private bool OnlyCapturesThis( - Analysis.Closure closure, - Analysis.Scope scope, - PooledHashSet localFuncsInProgress = null) - { - Debug.Assert(closure != null); - Debug.Assert(scope != null); - - bool result = false; - if (_onlyCapturesThisMemoTable?.TryGetValue(closure, out result) == true) - { - return result; - } - - result = true; - foreach (var captured in closure.CapturedVariables) - { - var param = captured as ParameterSymbol; - if (param != null && param.IsThis) - { - continue; - } + var env = scope.DeclaredEnvironments[0]; - var localFunc = captured as LocalFunctionSymbol; - if (localFunc != null) - { - bool freePool = false; - if (localFuncsInProgress == null) - { - localFuncsInProgress = PooledHashSet.GetInstance(); - freePool = true; - } - else if (localFuncsInProgress.Contains(localFunc)) + CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(ContainingType, env); + if (env.Constructor != null) { - continue; + AddSynthesizedMethod( + env.Constructor, + FlowAnalysisPass.AppendImplicitReturn( + MethodCompiler.BindMethodBody(env.Constructor, CompilationState, null), + env.Constructor)); } - localFuncsInProgress.Add(localFunc); - var (found, foundScope) = Analysis.GetVisibleClosure(scope, localFunc); - bool transitivelyTrue = OnlyCapturesThis(found, foundScope, localFuncsInProgress); - - if (freePool) + foreach (var captured in env.CapturedVariables) { - localFuncsInProgress.Free(); - localFuncsInProgress = null; - } + Debug.Assert(!proxies.ContainsKey(captured)); - if (transitivelyTrue) - { - continue; + var hoistedField = LambdaCapturedVariable.Create(env, captured, ref _synthesizedFieldNameIdDispenser); + proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false)); + CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(env, hoistedField); } - } - result = false; - break; - } - - if (_onlyCapturesThisMemoTable == null) - { - _onlyCapturesThisMemoTable = new SmallDictionary(); - } - - _onlyCapturesThisMemoTable[closure] = result; - return result; - } - - private LambdaFrame GetFrameForScope(Analysis.Scope scope, ArrayBuilder closureDebugInfo) - { - var scopeBoundNode = scope.BoundNode; - LambdaFrame frame; - if (!_frames.TryGetValue(scopeBoundNode, out frame)) - { - var syntax = scopeBoundNode.Syntax; - Debug.Assert(syntax != null); - - DebugId methodId = GetTopLevelMethodId(); - DebugId closureId = GetClosureId(syntax, closureDebugInfo); - - var canBeStruct = !_analysis.ScopesThatCantBeStructs.Contains(scopeBoundNode); - - var containingMethod = scope.ContainingClosureOpt?.OriginalMethodSymbol ?? _topLevelMethod; - if (_substitutedSourceMethod != null && containingMethod == _topLevelMethod) - { - containingMethod = _substitutedSourceMethod; - } - frame = new LambdaFrame(_topLevelMethod, containingMethod, canBeStruct, syntax, methodId, closureId); - _frames.Add(scopeBoundNode, frame); - - CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(ContainingType, frame); - if (frame.Constructor != null) - { - AddSynthesizedMethod( - frame.Constructor, - FlowAnalysisPass.AppendImplicitReturn( - MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null), - frame.Constructor)); + _frames.Add(scope.BoundNode, env); } - } - - return frame; + }); } - private LambdaFrame GetStaticFrame(DiagnosticBag diagnostics, IBoundLambdaOrFunction lambda) + private ClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambdaOrFunction lambda) { if (_lazyStaticLambdaFrame == null) { @@ -506,13 +400,20 @@ private LambdaFrame GetStaticFrame(DiagnosticBag diagnostics, IBoundLambdaOrFunc } else { - methodId = GetTopLevelMethodId(); + methodId = _analysis.GetTopLevelMethodId(); } DebugId closureId = default(DebugId); // using _topLevelMethod as containing member because the static frame does not have generic parameters, except for the top level method's var containingMethod = isNonGeneric ? null : (_substitutedSourceMethod ?? _topLevelMethod); - _lazyStaticLambdaFrame = new LambdaFrame(_topLevelMethod, containingMethod, isStruct: false, scopeSyntaxOpt: null, methodId: methodId, closureId: closureId); + _lazyStaticLambdaFrame = new ClosureEnvironment( + SpecializedCollections.EmptyEnumerable(), + _topLevelMethod, + containingMethod, + isStruct: false, + scopeSyntaxOpt: null, + methodId: methodId, + closureId: closureId); // non-generic static lambdas can share the frame if (isNonGeneric) @@ -632,7 +533,7 @@ private static void InsertAndFreePrologue(ArrayBuilder result, A /// The frame for the translated node /// A function that computes the translation of the node. It receives lists of added statements and added symbols /// The translated statement, as returned from F - private BoundNode IntroduceFrame(BoundNode node, LambdaFrame frame, Func, ArrayBuilder, BoundNode> F) + private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func, ArrayBuilder, BoundNode> F) { var frameTypeParameters = ImmutableArray.Create(StaticCast.From(_currentTypeParameters).SelectAsArray(TypeMap.TypeSymbolAsTypeWithModifiers), 0, frame.Arity); NamedTypeSymbol frameType = frame.ConstructIfGeneric(frameTypeParameters); @@ -646,7 +547,7 @@ private BoundNode IntroduceFrame(BoundNode node, LambdaFrame frame, Func.GetInstance(); - if (frame.Constructor != null) + if ((object)frame.Constructor != null) { MethodSymbol constructor = frame.Constructor.AsMember(frameType); Debug.Assert(frameType == constructor.ContainingType); @@ -675,20 +576,7 @@ private BoundNode IntroduceFrame(BoundNode node, LambdaFrame frame, Func.GetInstance(); addedLocals.Add(framePointer); _framePointers.Add(frame, framePointer); @@ -775,7 +664,20 @@ private void InitVariableProxy(SyntaxNode syntax, Symbol symbol, LocalSymbol fra var left = proxy.Replacement(syntax, frameType1 => new BoundLocal(syntax, framePointer, null, framePointer.Type)); var assignToProxy = new BoundAssignmentOperator(syntax, left, value, value.Type); - prologue.Add(assignToProxy); + if (_currentMethod.MethodKind == MethodKind.Constructor && + symbol == _currentMethod.ThisParameter && + !_seenBaseCall) + { + // Containing method is a constructor + // Initialization statement for the "this" proxy must be inserted + // after the constructor initializer statement block + Debug.Assert(_thisProxyInitDeferred == null); + _thisProxyInitDeferred = assignToProxy; + } + else + { + prologue.Add(assignToProxy); + } } } @@ -826,7 +728,7 @@ public override BoundNode VisitBaseReference(BoundBaseReference node) out NamedTypeSymbol constructedFrame) { var translatedLambdaContainer = synthesizedMethod.ContainingType; - var containerAsFrame = translatedLambdaContainer as LambdaFrame; + var containerAsFrame = translatedLambdaContainer as ClosureEnvironment; // All of _currentTypeParameters might not be preserved here due to recursively calling upwards in the chain of local functions/lambdas Debug.Assert((typeArgumentsOpt.IsDefault && !originalMethod.IsGenericMethod) || (typeArgumentsOpt.Length == originalMethod.Arity)); @@ -915,17 +817,20 @@ public override BoundNode VisitCall(BoundCall node) // Check if we need to init the 'this' proxy in a ctor call if (!_seenBaseCall) { - _seenBaseCall = _currentMethod == _topLevelMethod && node.IsConstructorInitializer(); - if (_seenBaseCall && _thisProxyInitDeferred != null) + if (_currentMethod == _topLevelMethod && node.IsConstructorInitializer()) { - // Insert the this proxy assignment after the ctor call. - // Create bound sequence: { ctor call, thisProxyInitDeferred } - return new BoundSequence( - syntax: node.Syntax, - locals: ImmutableArray.Empty, - sideEffects: ImmutableArray.Create(rewritten), - value: _thisProxyInitDeferred, - type: rewritten.Type); + _seenBaseCall = true; + if (_thisProxyInitDeferred != null) + { + // Insert the this proxy assignment after the ctor call. + // Create bound sequence: { ctor call, thisProxyInitDeferred } + return new BoundSequence( + syntax: node.Syntax, + locals: ImmutableArray.Empty, + sideEffects: ImmutableArray.Create(rewritten), + value: _thisProxyInitDeferred, + type: rewritten.Type); + } } } @@ -961,7 +866,7 @@ private BoundSequence RewriteSequence(BoundSequence node, ArrayBuilder prologue, ArrayBuilder newLocals) => @@ -1086,7 +991,7 @@ private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder closureDebugInfo) - { - Debug.Assert(syntax != null); - - DebugId closureId; - DebugId previousClosureId; - if (slotAllocatorOpt != null && slotAllocatorOpt.TryGetPreviousClosure(syntax, out previousClosureId)) - { - closureId = previousClosureId; - } - else - { - closureId = new DebugId(closureDebugInfo.Count, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); - } - - int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree); - closureDebugInfo.Add(new ClosureDebugInfo(syntaxOffset, closureId)); - - return closureId; - } - private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int closureOrdinal) { Debug.Assert(syntax != null); @@ -1299,19 +1178,19 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos IBoundLambdaOrFunction node, out ClosureKind closureKind, out NamedTypeSymbol translatedLambdaContainer, - out LambdaFrame containerAsFrame, + out ClosureEnvironment containerAsFrame, out BoundNode lambdaScope, out DebugId topLevelMethodId, out DebugId lambdaId) { - ImmutableArray structClosures; + Analysis.Closure closure = Analysis.GetClosureInTree(_analysis.ScopeTree, node.Symbol); + + var structClosures = closure.CapturedEnvironments + .Where(env => env.IsStructType()).AsImmutable(); int closureOrdinal; - if (_analysis.LambdaScopes.TryGetValue(node.Symbol, out lambdaScope)) + if (closure.ContainingEnvironmentOpt != null) { - containerAsFrame = _frames[lambdaScope]; - structClosures = _analysis.CanTakeRefParameters(node.Symbol) - ? GetStructClosures(containerAsFrame, lambdaScope) - : default(ImmutableArray); + containerAsFrame = closure.ContainingEnvironmentOpt; if (containerAsFrame?.IsValueType == true) { @@ -1327,9 +1206,20 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos closureKind = ClosureKind.General; translatedLambdaContainer = containerAsFrame; closureOrdinal = containerAsFrame.ClosureOrdinal; + // Find the scope of the containing environment + BoundNode tmpScope = null; + Analysis.VisitScopeTree(_analysis.ScopeTree, scope => + { + if (scope.DeclaredEnvironments.Contains(closure.ContainingEnvironmentOpt)) + { + tmpScope = scope.BoundNode; + } + }); + Debug.Assert(tmpScope != null); + lambdaScope = tmpScope; } } - else if (Analysis.GetClosureInTree(_analysis.ScopeTree, node.Symbol).CapturedVariables.Count == 0) + else if (closure.CapturedVariables.Count == 0) { if (_analysis.MethodsConvertedToDelegates.Contains(node.Symbol)) { @@ -1344,28 +1234,11 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos closureKind = ClosureKind.Static; closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal; } - structClosures = default; + lambdaScope = null; } else { - // GetStructClosures is currently overly aggressive in gathering - // closures since the only information it has at this point is - // NeedsParentFrame, which doesn't say what exactly is needed from - // the parent frame. If `this` is captured, that's enough to mark - // NeedsParentFrame for the current closure, so we need to gather - // struct closures for all intermediate frames, even if they only - // strictly need `this`. - if (_analysis.CanTakeRefParameters(node.Symbol)) - { - lambdaScope = Analysis.GetScopeParent(_analysis.ScopeTree, node.Body)?.BoundNode; - _ = _frames.TryGetValue(lambdaScope, out containerAsFrame); - structClosures = GetStructClosures(containerAsFrame, lambdaScope); - } - else - { - structClosures = default; - } - + lambdaScope = null; containerAsFrame = null; translatedLambdaContainer = _topLevelMethod.ContainingType; closureKind = ClosureKind.ThisOnly; @@ -1373,7 +1246,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos } // Move the body of the lambda to a freshly generated synthetic method on its frame. - topLevelMethodId = GetTopLevelMethodId(); + topLevelMethodId = _analysis.GetTopLevelMethodId(); lambdaId = GetLambdaId(node.Syntax, closureKind, closureOrdinal); var synthesizedMethod = new SynthesizedLambdaMethod(translatedLambdaContainer, structClosures, closureKind, _topLevelMethod, topLevelMethodId, node, lambdaId); @@ -1411,7 +1284,6 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos else { _currentFrameThis = synthesizedMethod.ThisParameter; - _innermostFramePointer = null; _framePointers.TryGetValue(translatedLambdaContainer, out _innermostFramePointer); } @@ -1435,52 +1307,6 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos return synthesizedMethod; } - /// - /// Builds a list of all the struct-based closure environments that will - /// need to be passed as arguments to the method. - /// - private ImmutableArray GetStructClosures(LambdaFrame containerAsFrame, BoundNode lambdaScope) - { - var structClosureParamBuilder = ArrayBuilder.GetInstance(); - while (containerAsFrame?.IsValueType == true || - _analysis.NeedsParentFrame.Contains(lambdaScope)) - { - if (containerAsFrame?.IsValueType == true) - { - structClosureParamBuilder.Add(containerAsFrame); - } - if (_analysis.NeedsParentFrame.Contains(lambdaScope) - && FindParentFrame(ref containerAsFrame, ref lambdaScope)) - { - continue; - } - - // can happen when scope no longer needs parent frame, or we're at the outermost level and the "parent frame" is top level "this". - break; - } - - // Reverse it because we're going from inner to outer, and parameters are in order of outer to inner - structClosureParamBuilder.ReverseContents(); - return structClosureParamBuilder.ToImmutableAndFree(); - - bool FindParentFrame(ref LambdaFrame container, ref BoundNode scope) - { - while (true) - { - scope = Analysis.GetScopeParent(_analysis.ScopeTree, scope)?.BoundNode; - if (scope == null) - { - return false; - } - - if (_frames.TryGetValue(scope, out container)) - { - return true; - } - } - } - } - private void AddSynthesizedMethod(MethodSymbol method, BoundStatement body) { if (_synthesizedMethods == null) @@ -1512,7 +1338,7 @@ private BoundNode RewriteLambdaConversion(BoundLambda node) ClosureKind closureKind; NamedTypeSymbol translatedLambdaContainer; - LambdaFrame containerAsFrame; + ClosureEnvironment containerAsFrame; BoundNode lambdaScope; DebugId topLevelMethodId; DebugId lambdaId; diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs index f8cd67bdc9cccf353e3b051b3139a7b6543c4c3f..1eee93f9df38baa65859cf9d153ab54c7128338b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs @@ -15,11 +15,11 @@ namespace Microsoft.CodeAnalysis.CSharp internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISynthesizedMethodBodyImplementationSymbol { private readonly MethodSymbol _topLevelMethod; - private readonly ImmutableArray _structClosures; + private readonly ImmutableArray _structEnvironments; internal SynthesizedLambdaMethod( NamedTypeSymbol containingType, - ImmutableArray structClosures, + ImmutableArray structEnvironments, ClosureKind closureKind, MethodSymbol topLevelMethod, DebugId topLevelMethodId, @@ -43,15 +43,15 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy TypeMap typeMap; ImmutableArray typeParameters; ImmutableArray constructedFromTypeParameters; - LambdaFrame lambdaFrame; + ClosureEnvironment lambdaFrame; - lambdaFrame = this.ContainingType as LambdaFrame; + lambdaFrame = this.ContainingType as ClosureEnvironment; switch (closureKind) { case ClosureKind.Singleton: // all type parameters on method (except the top level method's) case ClosureKind.General: // only lambda's type parameters on method (rest on class) Debug.Assert(lambdaFrame != null); - typeMap = lambdaFrame.TypeMap.WithConcatAlphaRename(lambdaNode.Symbol, this, out typeParameters, out constructedFromTypeParameters, lambdaFrame.ContainingMethod); + typeMap = lambdaFrame.TypeMap.WithConcatAlphaRename(lambdaNode.Symbol, this, out typeParameters, out constructedFromTypeParameters, lambdaFrame.OriginalContainingMethodOpt); break; case ClosureKind.ThisOnly: // all type parameters on method case ClosureKind.Static: @@ -62,28 +62,30 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy throw ExceptionUtilities.UnexpectedValue(closureKind); } - if (!structClosures.IsDefaultOrEmpty && typeParameters.Length != 0) + if (!structEnvironments.IsDefaultOrEmpty && typeParameters.Length != 0) { - var constructedStructClosures = ArrayBuilder.GetInstance(); - foreach (var closure in structClosures) + var constructedStructClosures = ArrayBuilder.GetInstance(); + foreach (var env in structEnvironments) { - var frame = (LambdaFrame)closure; NamedTypeSymbol constructed; - if (frame.Arity == 0) + if (env.Arity == 0) { - constructed = frame; + constructed = env; } else { - var originals = frame.ConstructedFromTypeParameters; + var originals = env.ConstructedFromTypeParameters; var newArgs = typeMap.SubstituteTypeParameters(originals); - constructed = frame.Construct(newArgs); + constructed = env.Construct(newArgs); } constructedStructClosures.Add(constructed); } - structClosures = constructedStructClosures.ToImmutableAndFree(); + _structEnvironments = constructedStructClosures.ToImmutableAndFree(); + } + else + { + _structEnvironments = ImmutableArray.CastUp(structEnvironments); } - _structClosures = structClosures; AssignTypeMapAndTypeParameters(typeMap, typeParameters); } @@ -125,8 +127,9 @@ private static string MakeName(string topLevelMethodName, DebugId topLevelMethod // UNDONE: names from the delegate. Does it really matter? protected override ImmutableArray BaseMethodParameters => this.BaseMethod.Parameters; - protected override ImmutableArray ExtraSynthesizedRefParameters => _structClosures; - internal int ExtraSynthesizedParameterCount => this._structClosures.IsDefault ? 0 : this._structClosures.Length; + protected override ImmutableArray ExtraSynthesizedRefParameters + => ImmutableArray.CastUp(_structEnvironments); + internal int ExtraSynthesizedParameterCount => this._structEnvironments.IsDefault ? 0 : this._structEnvironments.Length; internal override bool GenerateDebugInfo => !this.IsAsync; internal override bool IsExpressionBodied => false; diff --git a/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs index 2eee158e23f086ce2dbd39c87367c56dd9808882..34ca8ee7671fab92892ecbb1e9f55d1af56226c8 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs @@ -9,24 +9,10 @@ namespace Roslyn.Utilities internal static class ImmutableArrayExtensions { internal static ImmutableArray ToImmutableArrayOrEmpty(this IEnumerable items) - { - if (items == null) - { - return ImmutableArray.Create(); - } - - return ImmutableArray.CreateRange(items); - } + => items == null ? ImmutableArray.Empty : ImmutableArray.CreateRange(items); internal static ImmutableArray ToImmutableArrayOrEmpty(this ImmutableArray items) - { - if (items.IsDefault) - { - return ImmutableArray.Create(); - } - - return items; - } + => items.IsDefault ? ImmutableArray.Empty : items; // same as Array.BinarySearch but the ability to pass arbitrary value to the comparer without allocation internal static int BinarySearch(this ImmutableArray array, TValue value, Func comparer) diff --git a/src/Compilers/Core/Portable/InternalUtilities/SetWithInsertionOrder.cs b/src/Compilers/Core/Portable/InternalUtilities/SetWithInsertionOrder.cs index 9fc12c3d1f692292f6cfb3ebe32c3e072c83ea9a..97145d508a68e0ab77a6aa9fc19147cdc2964499 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/SetWithInsertionOrder.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/SetWithInsertionOrder.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; namespace Roslyn.Utilities { @@ -16,46 +17,45 @@ namespace Roslyn.Utilities /// internal sealed class SetWithInsertionOrder : IEnumerable, IReadOnlySet { - private HashSet _set = new HashSet(); - private uint _nextElementValue = 0; - private T[] _elements = null; + private HashSet _set = null; + private ArrayBuilder _elements = null; public bool Add(T value) { - if (!_set.Add(value)) return false; - var thisValue = _nextElementValue++; - if (_elements == null) + if (_set == null) { - _elements = new T[10]; + _set = new HashSet(); + _elements = new ArrayBuilder(); } - else if (_elements.Length <= thisValue) + + if (!_set.Add(value)) { - Array.Resize(ref _elements, _elements.Length * 2); + return false; } - _elements[thisValue] = value; + _elements.Add(value); + return true; + } + + public bool Remove(T value) + { + if (!_set.Remove(value)) + { + return false; + } + _elements.RemoveAt(_elements.IndexOf(value)); return true; } - public int Count => (int)_nextElementValue; + public int Count => _elements?.Count ?? 0; - public bool Contains(T value) => _set.Contains(value); + public bool Contains(T value) => _set?.Contains(value) ?? false; public IEnumerator GetEnumerator() - { - for (int i = 0; i < _nextElementValue; i++) yield return _elements[i]; - } + => _elements?.GetEnumerator() ?? SpecializedCollections.EmptyEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - /// - /// An enumerable that yields the set's elements in insertion order. - /// - public SetWithInsertionOrder InInsertionOrder => this; - - public ImmutableArray AsImmutable() - { - return (_elements == null) ? ImmutableArray.Empty : ImmutableArray.Create(_elements, 0, (int)_nextElementValue); - } + public ImmutableArray AsImmutable() => _elements.ToImmutableArrayOrEmpty(); } } diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol.vb index 9f2d11c928cf2404c6ba15d3d6a4e1ce2f42b861..f78d6cff48547c512cd0ff178b4265fd4bf7085a 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol.vb @@ -1312,7 +1312,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols MakeDeclaredInterfacesInPart(syntaxRef.SyntaxTree, syntaxRef.GetVisualBasicSyntax(), interfaces, basesBeingResolved, diagnostics) Next - Return interfaces.InInsertionOrder.AsImmutable + Return interfaces.AsImmutable End Function Private Function GetInheritsLocation(base As NamedTypeSymbol) As Location diff --git a/src/Compilers/VisualBasic/Portable/VisualBasicParseOptions.vb b/src/Compilers/VisualBasic/Portable/VisualBasicParseOptions.vb index 180e6e5e39bcf6b9e9b137a5b124b65d3601c3c2..841805d3e18f01d4a51daaae9ecfca575d02eaa2 100644 --- a/src/Compilers/VisualBasic/Portable/VisualBasicParseOptions.vb +++ b/src/Compilers/VisualBasic/Portable/VisualBasicParseOptions.vb @@ -47,7 +47,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic languageVersion As LanguageVersion, documentationMode As DocumentationMode, kind As SourceCodeKind, - preprocessorSymbols As IEnumerable(Of KeyValuePair(Of String, Object)), + preprocessorSymbols As ImmutableArray(Of KeyValuePair(Of String, Object)), features As ImmutableDictionary(Of String, String)) MyBase.New(kind, documentationMode)