提交 012c7fe9 编写于 作者: A Andy Gocke

Move MakeFrames logic into Analysis

This is part of the effort to move as much logic from LambdaRewriter
into Analysis as possible. The more logic there is in the Rewriter, the
more difficult it is to find calculation problems until the last
possible moment, and it's also very difficult to calculate useful
information for later analysis passes, like the final debug and closure
IDs for each environment.
上级 995b158e
......@@ -61,7 +61,7 @@ public override IEnumerable<string> PreprocessorSymbolNames
LanguageVersion languageVersion,
DocumentationMode documentationMode,
SourceCodeKind kind,
IEnumerable<string> preprocessorSymbols,
ImmutableArray<string> preprocessorSymbols,
IReadOnlyDictionary<string, string> features)
: base(kind, documentationMode)
{
......
......@@ -66,7 +66,7 @@ internal MethodWithBody(MethodSymbol method, BoundStatement body, ImportChain im
public readonly CSharpCompilation Compilation;
public LambdaFrame StaticLambdaFrame;
public ClosureEnvironment StaticLambdaFrame;
/// <summary>
/// A graph of method->method references for this(...) constructor initializers.
......
......@@ -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)
......
// 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
{
/// <summary>
/// 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.
/// </summary>
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)
/// <summary>
/// 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.
/// </summary>
internal readonly MethodSymbol OriginalContainingMethodOpt;
internal readonly FieldSymbol SingletonCache;
internal readonly MethodSymbol StaticConstructor;
public readonly IEnumerable<Symbol> CapturedVariables;
public override TypeKind TypeKind { get; }
internal override MethodSymbol Constructor { get; }
internal ClosureEnvironment(
IEnumerable<Symbol> 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; }
}
/// <summary>
/// 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.
/// </summary>
internal MethodSymbol ContainingMethod
{
get { return _containingMethod; }
}
public override ImmutableArray<Symbol> GetMembers()
{
var members = base.GetMembers();
if ((object)_staticConstructor != null)
if ((object)StaticConstructor != null)
{
members = ImmutableArray.Create<Symbol>(_staticConstructor, _singletonCache).AddRange(members);
members = ImmutableArray.Create<Symbol>(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;
}
}
......@@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed class LambdaFrameConstructor : SynthesizedInstanceConstructor, ISynthesizedMethodBodyImplementationSymbol
{
internal LambdaFrameConstructor(LambdaFrame frame)
internal LambdaFrameConstructor(ClosureEnvironment frame)
: base(frame)
{
}
......
......@@ -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<Scope> NestedScopes { get; } = ArrayBuilder<Scope>.GetInstance();
public readonly ArrayBuilder<Scope> NestedScopes = ArrayBuilder<Scope>.GetInstance();
/// <summary>
/// A list of all closures (all lambdas and local functions) declared in this scope.
/// </summary>
public ArrayBuilder<Closure> Closures { get; } = ArrayBuilder<Closure>.GetInstance();
public readonly ArrayBuilder<Closure> Closures = ArrayBuilder<Closure>.GetInstance();
/// <summary>
/// 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.
/// </remarks>
public SetWithInsertionOrder<Symbol> DeclaredVariables { get; } = new SetWithInsertionOrder<Symbol>();
public readonly SetWithInsertionOrder<Symbol> DeclaredVariables = new SetWithInsertionOrder<Symbol>();
/// <summary>
/// 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.
/// </summary>
public BoundNode BoundNode { get; }
public readonly BoundNode BoundNode;
/// <summary>
/// The closure that this scope is nested inside. Null if this scope is not nested
/// inside a closure.
/// </summary>
public Closure ContainingClosureOpt { get; }
public readonly Closure ContainingClosureOpt;
/// <summary>
/// Environments created in this scope to hold <see cref="DeclaredVariables"/>.
/// </summary>
public readonly ArrayBuilder<ClosureEnvironment> DeclaredEnvironments
= ArrayBuilder<ClosureEnvironment>.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
/// <summary>
/// The method symbol for the original lambda or local function.
/// </summary>
public MethodSymbol OriginalMethodSymbol { get; }
public readonly MethodSymbol OriginalMethodSymbol;
public readonly PooledHashSet<Symbol> CapturedVariables = PooledHashSet<Symbol>.GetInstance();
public PooledHashSet<Symbol> CapturedVariables { get; } = PooledHashSet<Symbol>.GetInstance();
public readonly ArrayBuilder<ClosureEnvironment> CapturedEnvironments
= ArrayBuilder<ClosureEnvironment>.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);
}
}
/// <summary>
......@@ -193,6 +226,19 @@ public static void VisitClosures(Scope scope, Action<Scope, Closure> action)
}
}
/// <summary>
/// Visit the tree with the given root and run the <paramref name="action"/>
/// </summary>
public static void VisitScopeTree(Scope treeRoot, Action<Scope> action)
{
action(treeRoot);
foreach (var nested in treeRoot.NestedScopes)
{
VisitScopeTree(nested, action);
}
}
/// <summary>
/// Builds a tree of <see cref="Scope"/> nodes corresponding to a given method.
/// <see cref="Build(BoundNode, MethodSymbol, HashSet{MethodSymbol}, DiagnosticBag)"/>
......@@ -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<Symbol>(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);
......
// 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));
/// <summary>
/// Any scope that a method that <see cref="CanTakeRefParameters(MethodSymbol)"/> doesn't close over.
/// If a scope is in this set, don't use a struct closure.
/// </summary>
public readonly PooledHashSet<BoundNode> ScopesThatCantBeStructs = PooledHashSet<BoundNode>.GetInstance();
/// <summary>
/// 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
/// </summary>
public readonly PooledHashSet<BoundNode> NeedsParentFrame = PooledHashSet<BoundNode>.GetInstance();
/// <summary>
/// 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.
/// <see cref="ComputeLambdaScopesAndFrameCaptures"/> needs to be called to compute this.
/// </summary>
public readonly SmallDictionary<MethodSymbol, BoundNode> LambdaScopes =
new SmallDictionary<MethodSymbol, BoundNode>(ReferenceEqualityComparer.Instance);
/// <summary>
/// The root of the scope tree for this method.
/// </summary>
public readonly Scope ScopeTree;
private Analysis(Scope scopeTree, PooledHashSet<MethodSymbol> 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<MethodSymbol> 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> closureDebugInfo,
DiagnosticBag diagnostics)
{
var methodsConvertedToDelegates = PooledHashSet<MethodSymbol>.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)
}
/// <summary>
/// Create the optimized plan for the location of lambda methods and whether scopes need access to parent scopes
/// </summary>
internal void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam)
/// Must be called only after <see cref="Closure.CapturedEnvironments"/>
/// 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.
/// </summary>
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> 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<Closure>();
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<Symbol> 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> 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;
}
/// <summary>
......@@ -347,7 +470,6 @@ Closure Helper(Scope scope)
public void Free()
{
MethodsConvertedToDelegates.Free();
ScopesThatCantBeStructs.Free();
NeedsParentFrame.Free();
ScopeTree.Free();
}
......
......@@ -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);
......
......@@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.CSharp
/// have captured variables. The result of this analysis is left in <see cref="_analysis"/>.
///
/// Then we make a frame, or compiler-generated class, represented by an instance of
/// <see cref="LambdaFrame"/> for each scope with captured variables. The generated frames are kept
/// <see cref="ClosureEnvironment"/> for each scope with captured variables. The generated frames are kept
/// in <see cref="_frames"/>. Each frame is given a single field for each captured
/// variable in the corresponding scope. These are maintained in <see cref="MethodToClassRewriter.proxies"/>.
///
......@@ -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<ParameterSymbol, ParameterSymbol> _parameterMap = new Dictionary<ParameterSymbol, ParameterSymbol>();
......@@ -93,7 +93,7 @@ public MappedLocalFunction(SynthesizedLambdaMethod symbol, ClosureKind closureKi
private readonly Dictionary<LocalFunctionSymbol, MappedLocalFunction> _localFunctionMap = new Dictionary<LocalFunctionSymbol, MappedLocalFunction>();
// for each block with lifted (captured) variables, the corresponding frame type
private readonly Dictionary<BoundNode, LambdaFrame> _frames = new Dictionary<BoundNode, LambdaFrame>();
private readonly Dictionary<BoundNode, ClosureEnvironment> _frames = new Dictionary<BoundNode, ClosureEnvironment>();
// 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);
/// <summary>
/// Create the frame types.
/// Adds <see cref="ClosureEnvironment"/> synthesized types to the compilation state
/// and creates hoisted fields for all locals captured by the environments.
/// </summary>
private void MakeFrames(ArrayBuilder<ClosureDebugInfo> 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<Analysis.Closure, bool> _onlyCapturesThisMemoTable;
/// <summary>
/// Helper for determining whether a local function transitively
/// only captures this (only captures this or other local functions
/// which only capture this).
/// </summary>
private bool OnlyCapturesThis(
Analysis.Closure closure,
Analysis.Scope scope,
PooledHashSet<LocalFunctionSymbol> 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<LocalFunctionSymbol>.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<Analysis.Closure, bool>();
}
_onlyCapturesThisMemoTable[closure] = result;
return result;
}
private LambdaFrame GetFrameForScope(Analysis.Scope scope, ArrayBuilder<ClosureDebugInfo> 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<Symbol>(),
_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<BoundStatement> result, A
/// <param name="frame">The frame for the translated node</param>
/// <param name="F">A function that computes the translation of the node. It receives lists of added statements and added symbols</param>
/// <returns>The translated statement, as returned from F</returns>
private BoundNode IntroduceFrame(BoundNode node, LambdaFrame frame, Func<ArrayBuilder<BoundExpression>, ArrayBuilder<LocalSymbol>, BoundNode> F)
private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func<ArrayBuilder<BoundExpression>, ArrayBuilder<LocalSymbol>, BoundNode> F)
{
var frameTypeParameters = ImmutableArray.Create(StaticCast<TypeSymbol>.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<ArrayBu
var prologue = ArrayBuilder<BoundExpression>.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<ArrayBu
BoundExpression left = new BoundFieldAccess(syntax, new BoundLocal(syntax, framePointer, null, frameType), frameParent, null);
BoundExpression right = FrameOfType(syntax, frameParent.Type as NamedTypeSymbol);
BoundExpression assignment = new BoundAssignmentOperator(syntax, left, right, left.Type);
if (_currentMethod.MethodKind == MethodKind.Constructor && capturedFrame.Type == _currentMethod.ContainingType && !_seenBaseCall)
{
// Containing method is a constructor
// Initialization statement for the "this" proxy must be inserted
// after the constructor initializer statement block
// This insertion will be done by the delegate F
Debug.Assert(_thisProxyInitDeferred == null);
_thisProxyInitDeferred = assignment;
}
else
{
prologue.Add(assignment);
}
prologue.Add(assignment);
if (CompilationState.Emitting)
{
......@@ -702,15 +590,16 @@ private BoundNode IntroduceFrame(BoundNode node, LambdaFrame frame, Func<ArrayBu
// Capture any parameters of this block. This would typically occur
// at the top level of a method or lambda with captured parameters.
// TODO: speed up the following by computing it in analysis.
var scope = Analysis.GetScopeWithMatchingBoundNode(_analysis.ScopeTree, node);
foreach (var variable in scope.DeclaredVariables)
foreach (var variable in frame.CapturedVariables)
{
InitVariableProxy(syntax, variable, framePointer, prologue);
}
Symbol oldInnermostFramePointer = _innermostFramePointer;
_innermostFramePointer = framePointer;
if (!framePointer.Type.IsValueType)
{
_innermostFramePointer = framePointer;
}
var addedLocals = ArrayBuilder<LocalSymbol>.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<LocalSymbol>.Empty,
sideEffects: ImmutableArray.Create<BoundExpression>(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<LocalSymbol>.Empty,
sideEffects: ImmutableArray.Create<BoundExpression>(rewritten),
value: _thisProxyInitDeferred,
type: rewritten.Type);
}
}
}
......@@ -961,7 +866,7 @@ private BoundSequence RewriteSequence(BoundSequence node, ArrayBuilder<BoundExpr
public override BoundNode VisitBlock(BoundBlock node)
{
LambdaFrame frame;
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out frame))
{
......@@ -1019,7 +924,7 @@ public override BoundNode VisitScope(BoundScope node)
public override BoundNode VisitCatchBlock(BoundCatchBlock node)
{
// Test if this frame has captured variables and requires the introduction of a closure class.
LambdaFrame frame;
ClosureEnvironment frame;
if (_frames.TryGetValue(node, out frame))
{
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
......@@ -1086,7 +991,7 @@ private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder<BoundExpressio
public override BoundNode VisitSequence(BoundSequence node)
{
LambdaFrame frame;
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out frame))
{
......@@ -1103,7 +1008,7 @@ public override BoundNode VisitSequence(BoundSequence node)
public override BoundNode VisitStatementList(BoundStatementList node)
{
LambdaFrame frame;
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class.
// That can occur for a BoundStatementList if it is the body of a method with captured parameters.
if (_frames.TryGetValue(node, out frame))
......@@ -1129,7 +1034,7 @@ public override BoundNode VisitStatementList(BoundStatementList node)
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
LambdaFrame frame;
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out frame))
{
......@@ -1200,7 +1105,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen
{
ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer;
LambdaFrame containerAsFrame;
ClosureEnvironment containerAsFrame;
BoundNode lambdaScope;
DebugId topLevelMethodId;
DebugId lambdaId;
......@@ -1216,32 +1121,6 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen
return new BoundNoOpStatement(node.Syntax, NoOpStatementFlavor.Default);
}
private DebugId GetTopLevelMethodId()
{
return slotAllocatorOpt?.MethodId ?? new DebugId(_topLevelMethodOrdinal, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal);
}
private DebugId GetClosureId(SyntaxNode syntax, ArrayBuilder<ClosureDebugInfo> 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<TypeSymbol> 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<TypeSymbol>);
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;
}
/// <summary>
/// Builds a list of all the struct-based closure environments that will
/// need to be passed as arguments to the method.
/// </summary>
private ImmutableArray<TypeSymbol> GetStructClosures(LambdaFrame containerAsFrame, BoundNode lambdaScope)
{
var structClosureParamBuilder = ArrayBuilder<TypeSymbol>.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;
......
......@@ -15,11 +15,11 @@ namespace Microsoft.CodeAnalysis.CSharp
internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISynthesizedMethodBodyImplementationSymbol
{
private readonly MethodSymbol _topLevelMethod;
private readonly ImmutableArray<TypeSymbol> _structClosures;
private readonly ImmutableArray<NamedTypeSymbol> _structEnvironments;
internal SynthesizedLambdaMethod(
NamedTypeSymbol containingType,
ImmutableArray<TypeSymbol> structClosures,
ImmutableArray<ClosureEnvironment> structEnvironments,
ClosureKind closureKind,
MethodSymbol topLevelMethod,
DebugId topLevelMethodId,
......@@ -43,15 +43,15 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy
TypeMap typeMap;
ImmutableArray<TypeParameterSymbol> typeParameters;
ImmutableArray<TypeParameterSymbol> 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<TypeSymbol>.GetInstance();
foreach (var closure in structClosures)
var constructedStructClosures = ArrayBuilder<NamedTypeSymbol>.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<NamedTypeSymbol>.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<ParameterSymbol> BaseMethodParameters => this.BaseMethod.Parameters;
protected override ImmutableArray<TypeSymbol> ExtraSynthesizedRefParameters => _structClosures;
internal int ExtraSynthesizedParameterCount => this._structClosures.IsDefault ? 0 : this._structClosures.Length;
protected override ImmutableArray<TypeSymbol> ExtraSynthesizedRefParameters
=> ImmutableArray<TypeSymbol>.CastUp(_structEnvironments);
internal int ExtraSynthesizedParameterCount => this._structEnvironments.IsDefault ? 0 : this._structEnvironments.Length;
internal override bool GenerateDebugInfo => !this.IsAsync;
internal override bool IsExpressionBodied => false;
......
......@@ -9,24 +9,10 @@ namespace Roslyn.Utilities
internal static class ImmutableArrayExtensions
{
internal static ImmutableArray<T> ToImmutableArrayOrEmpty<T>(this IEnumerable<T> items)
{
if (items == null)
{
return ImmutableArray.Create<T>();
}
return ImmutableArray.CreateRange<T>(items);
}
=> items == null ? ImmutableArray<T>.Empty : ImmutableArray.CreateRange(items);
internal static ImmutableArray<T> ToImmutableArrayOrEmpty<T>(this ImmutableArray<T> items)
{
if (items.IsDefault)
{
return ImmutableArray.Create<T>();
}
return items;
}
=> items.IsDefault ? ImmutableArray<T>.Empty : items;
// same as Array.BinarySearch but the ability to pass arbitrary value to the comparer without allocation
internal static int BinarySearch<TElement, TValue>(this ImmutableArray<TElement> array, TValue value, Func<TElement, TValue, int> comparer)
......
......@@ -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
/// </summary>
internal sealed class SetWithInsertionOrder<T> : IEnumerable<T>, IReadOnlySet<T>
{
private HashSet<T> _set = new HashSet<T>();
private uint _nextElementValue = 0;
private T[] _elements = null;
private HashSet<T> _set = null;
private ArrayBuilder<T> _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<T>();
_elements = new ArrayBuilder<T>();
}
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<T> GetEnumerator()
{
for (int i = 0; i < _nextElementValue; i++) yield return _elements[i];
}
=> _elements?.GetEnumerator() ?? SpecializedCollections.EmptyEnumerator<T>();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// An enumerable that yields the set's elements in insertion order.
/// </summary>
public SetWithInsertionOrder<T> InInsertionOrder => this;
public ImmutableArray<T> AsImmutable()
{
return (_elements == null) ? ImmutableArray<T>.Empty : ImmutableArray.Create(_elements, 0, (int)_nextElementValue);
}
public ImmutableArray<T> AsImmutable() => _elements.ToImmutableArrayOrEmpty();
}
}
......@@ -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
......
......@@ -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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册