提交 caa78300 编写于 作者: A Andy Gocke 提交者: GitHub

Remove RemoveUnneededReferences from LamdaRewriter (#21367)

Currently, the lambda rewriter has an early optimization pass in
analysis that tries to find all local functions that only capture 'this'
and remove references to local functions that do the same. There are two
problems with this approach:

    1) Generally, removing information from the tree is a bad idea
    because it hurts further analysis passes that may have needed that
    information.

    2) The optimization strategy itself is very tricky and has a number
    of complex corner cases. This has lead to bugs, for example #19033.

This PR deletes the current method and adds a new optimization routine
at the end of the analysis, operating on assigned scopes and
environments rather than removing captured variable analysis. The new
optimization is as follows: if we end up with an environment containing
only 'this', the environment can be removed, all containing methods can
be moved to the top-level type, and all environments which capture the
'this' environment can instead directly capture the 'this' parameter.
This produces almost the same results as the previous optimization, but
is easier to validate as an algebraic equivalence.

The baseline changes come from the new optimization being less aggressive 
about moving functions which only capture 'this' to the top level. This
appears to be a wash -- some codegen gets slightly better, some gets
slightly worse.

Fixes #19033
Fixes #20577
上级 1c1fbc6b
...@@ -66,7 +66,7 @@ internal MethodWithBody(MethodSymbol method, BoundStatement body, ImportChain im ...@@ -66,7 +66,7 @@ internal MethodWithBody(MethodSymbol method, BoundStatement body, ImportChain im
public readonly CSharpCompilation Compilation; public readonly CSharpCompilation Compilation;
public ClosureEnvironment StaticLambdaFrame; public SynthesizedClosureEnvironment StaticLambdaFrame;
/// <summary> /// <summary>
/// A graph of method->method references for this(...) constructor initializers. /// A graph of method->method references for this(...) constructor initializers.
......
...@@ -31,7 +31,7 @@ private LambdaCapturedVariable(SynthesizedContainer frame, TypeSymbol type, stri ...@@ -31,7 +31,7 @@ private LambdaCapturedVariable(SynthesizedContainer frame, TypeSymbol type, stri
_isThis = isThisParameter; _isThis = isThisParameter;
} }
public static LambdaCapturedVariable Create(ClosureEnvironment frame, Symbol captured, ref int uniqueId) public static LambdaCapturedVariable Create(SynthesizedClosureEnvironment frame, Symbol captured, ref int uniqueId)
{ {
Debug.Assert(captured is LocalSymbol || captured is ParameterSymbol); Debug.Assert(captured is LocalSymbol || captured is ParameterSymbol);
...@@ -87,7 +87,7 @@ private static TypeSymbol GetCapturedVariableFieldType(SynthesizedContainer fram ...@@ -87,7 +87,7 @@ private static TypeSymbol GetCapturedVariableFieldType(SynthesizedContainer fram
if ((object)local != null) if ((object)local != null)
{ {
// if we're capturing a generic frame pointer, construct it with the new frame's type parameters // if we're capturing a generic frame pointer, construct it with the new frame's type parameters
var lambdaFrame = local.Type.OriginalDefinition as ClosureEnvironment; var lambdaFrame = local.Type.OriginalDefinition as SynthesizedClosureEnvironment;
if ((object)lambdaFrame != null) if ((object)lambdaFrame != null)
{ {
// lambdaFrame may have less generic type parameters than frame, so trim them down (the first N will always match) // lambdaFrame may have less generic type parameters than frame, so trim them down (the first N will always match)
......
...@@ -119,6 +119,23 @@ public sealed class Closure ...@@ -119,6 +119,23 @@ public sealed class Closure
public ClosureEnvironment ContainingEnvironmentOpt; public ClosureEnvironment ContainingEnvironmentOpt;
private bool _capturesThis;
/// <summary>
/// True if this closure directly or transitively captures 'this' (captures
/// a local function which directly or indirectly captures 'this').
/// Calculated in <see cref="MakeAndAssignEnvironments"/>.
/// </summary>
public bool CapturesThis
{
get => _capturesThis;
set
{
Debug.Assert(value);
_capturesThis = value;
}
}
public Closure(MethodSymbol symbol) public Closure(MethodSymbol symbol)
{ {
Debug.Assert(symbol != null); Debug.Assert(symbol != null);
...@@ -132,100 +149,73 @@ public void Free() ...@@ -132,100 +149,73 @@ public void Free()
} }
} }
public sealed class ClosureEnvironment
{
public readonly SetWithInsertionOrder<Symbol> CapturedVariables;
/// <summary> /// <summary>
/// Optimizes local functions such that if a local function only references other local functions /// Represents a <see cref="SynthesizedEnvironment"/> that had its environment
/// that capture no variables, we don't need to create capture environments for any of them. /// pointer (a local pointing to the environment) captured like a captured
/// variable. Assigned in
/// <see cref="ComputeLambdaScopesAndFrameCaptures(ParameterSymbol)"/>
/// </summary> /// </summary>
private void RemoveUnneededReferences(ParameterSymbol thisParam) public bool CapturesParent;
{
var methodGraph = new MultiDictionary<MethodSymbol, MethodSymbol>();
var capturesThis = new HashSet<MethodSymbol>();
var capturesVariable = new HashSet<MethodSymbol>();
var visitStack = new Stack<MethodSymbol>();
VisitClosures(ScopeTree, (scope, closure) =>
{
foreach (var capture in closure.CapturedVariables)
{
if (capture is MethodSymbol localFunc)
{
methodGraph.Add(localFunc, closure.OriginalMethodSymbol);
}
else if (capture == thisParam)
{
if (capturesThis.Add(closure.OriginalMethodSymbol))
{
visitStack.Push(closure.OriginalMethodSymbol);
}
}
else if (capturesVariable.Add(closure.OriginalMethodSymbol) &&
!capturesThis.Contains(closure.OriginalMethodSymbol))
{
visitStack.Push(closure.OriginalMethodSymbol);
}
}
});
while (visitStack.Count > 0) public readonly bool IsStruct;
{ internal SynthesizedClosureEnvironment SynthesizedEnvironment;
var current = visitStack.Pop();
var setToAddTo = capturesVariable.Contains(current) ? capturesVariable : capturesThis; public ClosureEnvironment(IEnumerable<Symbol> capturedVariables, bool isStruct)
foreach (var capturesCurrent in methodGraph[current])
{ {
if (setToAddTo.Add(capturesCurrent)) CapturedVariables = new SetWithInsertionOrder<Symbol>();
foreach (var item in capturedVariables)
{ {
visitStack.Push(capturesCurrent); CapturedVariables.Add(item);
} }
IsStruct = isStruct;
} }
} }
// True if there are any closures in the tree which /// <summary>
// capture 'this' and another variable /// Visit all closures in all nested scopes and run the <paramref name="action"/>.
bool captureMoreThanThis = false; /// </summary>
public static void VisitClosures(Scope scope, Action<Scope, Closure> action)
VisitClosures(ScopeTree, (scope, closure) =>
{
if (!capturesVariable.Contains(closure.OriginalMethodSymbol))
{
closure.CapturedVariables.Clear();
}
if (capturesThis.Contains(closure.OriginalMethodSymbol))
{ {
closure.CapturedVariables.Add(thisParam); foreach (var closure in scope.Closures)
if (closure.CapturedVariables.Count > 1)
{ {
captureMoreThanThis |= true; action(scope, closure);
}
} }
});
if (!captureMoreThanThis && capturesThis.Count > 0) foreach (var nested in scope.NestedScopes)
{ {
// If we have closures which capture 'this', and nothing else, we can VisitClosures(nested, action);
// 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> /// <summary>
/// Visit all closures in all nested scopes and run the <paramref name="action"/>. /// Visit all the closures and return true when the <paramref name="func"/> returns
/// true. Otherwise, returns false.
/// </summary> /// </summary>
public static void VisitClosures(Scope scope, Action<Scope, Closure> action) public static bool CheckClosures(Scope scope, Func<Scope, Closure, bool> func)
{ {
foreach (var closure in scope.Closures) foreach (var closure in scope.Closures)
{ {
action(scope, closure); if (func(scope, closure))
{
return true;
}
} }
foreach (var nested in scope.NestedScopes) foreach (var nested in scope.NestedScopes)
{ {
VisitClosures(nested, action); if (CheckClosures(nested, func))
{
return true;
} }
} }
return false;
}
/// <summary> /// <summary>
/// Visit the tree with the given root and run the <paramref name="action"/> /// Visit the tree with the given root and run the <paramref name="action"/>
/// </summary> /// </summary>
...@@ -491,6 +481,13 @@ private void AddIfCaptured(Symbol symbol, SyntaxNode syntax) ...@@ -491,6 +481,13 @@ private void AddIfCaptured(Symbol symbol, SyntaxNode syntax)
return; return;
} }
if (symbol is MethodSymbol method &&
_currentClosure.OriginalMethodSymbol == method)
{
// Is this recursion? If so there's no capturing
return;
}
if (symbol.ContainingSymbol != _currentClosure.OriginalMethodSymbol) if (symbol.ContainingSymbol != _currentClosure.OriginalMethodSymbol)
{ {
// Restricted types can't be hoisted, so they are not permitted to be captured // Restricted types can't be hoisted, so they are not permitted to be captured
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CodeGen;
...@@ -32,15 +33,6 @@ internal sealed partial class Analysis ...@@ -32,15 +33,6 @@ internal sealed partial class Analysis
// We can't rewrite delegate signatures // We can't rewrite delegate signatures
|| MethodsConvertedToDelegates.Contains(closure)); || MethodsConvertedToDelegates.Contains(closure));
/// <summary>
/// Blocks that are positioned between a block declaring some lifted variables
/// and a block that contains the lambda that lifts said variables.
/// If such block itself requires a closure, then it must lift parent frame pointer into the closure
/// in addition to whatever else needs to be lifted.
/// <see cref="ComputeLambdaScopesAndFrameCaptures"/> needs to be called to compute this.
/// </summary>
public readonly PooledHashSet<BoundNode> NeedsParentFrame = PooledHashSet<BoundNode>.GetInstance();
/// <summary> /// <summary>
/// The root of the scope tree for this method. /// The root of the scope tree for this method.
/// </summary> /// </summary>
...@@ -97,9 +89,9 @@ internal sealed partial class Analysis ...@@ -97,9 +89,9 @@ internal sealed partial class Analysis
slotAllocatorOpt, slotAllocatorOpt,
compilationState); compilationState);
analysis.RemoveUnneededReferences(method.ThisParameter); analysis.MakeAndAssignEnvironments();
analysis.MakeAndAssignEnvironments(closureDebugInfo);
analysis.ComputeLambdaScopesAndFrameCaptures(method.ThisParameter); analysis.ComputeLambdaScopesAndFrameCaptures(method.ThisParameter);
analysis.InlineThisOnlyEnvironments();
return analysis; return analysis;
} }
...@@ -146,104 +138,184 @@ private void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam) ...@@ -146,104 +138,184 @@ private void ComputeLambdaScopesAndFrameCaptures(ParameterSymbol thisParam)
{ {
VisitClosures(ScopeTree, (scope, closure) => VisitClosures(ScopeTree, (scope, closure) =>
{ {
if (closure.CapturedVariables.Count > 0) if (closure.CapturedEnvironments.Count > 0)
{ {
(Scope innermost, Scope outermost) = FindLambdaScopeRange(closure, scope); var capturedEnvs = PooledHashSet<ClosureEnvironment>.GetInstance();
RecordClosureScope(innermost, outermost, closure); capturedEnvs.AddAll(closure.CapturedEnvironments);
}
});
(Scope innermost, Scope outermost) FindLambdaScopeRange(Closure closure, Scope closureScope) // Find the nearest captured class environment, if one exists
var curScope = scope;
while (curScope != null)
{ {
// If the closure only captures this, put the method directly in the if (capturedEnvs.RemoveAll(curScope.DeclaredEnvironments))
// top-level method's containing type
if (closure.CapturedVariables.Count == 1 &&
closure.CapturedVariables.Single() is ParameterSymbol param &&
param.IsThis)
{ {
return (null, null); // Right now we only create one environment per scope
Debug.Assert(curScope.DeclaredEnvironments.Count == 1);
var env = curScope.DeclaredEnvironments[0];
if (!env.IsStruct)
{
closure.ContainingEnvironmentOpt = env;
break;
}
}
curScope = curScope.Parent;
} }
Scope innermost = null; // Now we need to walk up the scopes to find environment captures
Scope outermost = null; var oldEnv = curScope?.DeclaredEnvironments[0];
while (curScope != null)
var capturedVars = PooledHashSet<Symbol>.GetInstance(); {
capturedVars.AddAll(closure.CapturedVariables); if (capturedEnvs.Count == 0)
{
break;
}
// If any of the captured variables are local functions we'll need var envs = curScope.DeclaredEnvironments.Where(e => !e.IsStruct);
// to add the captured variables of that local function to the current if (!envs.IsEmpty())
// set. This has the effect of ensuring that if the local function
// captures anything "above" the current scope then parent frame
// is itself captured (so that the current lambda can call that
// local function).
foreach (var captured in closure.CapturedVariables)
{ {
if (captured is LocalFunctionSymbol localFunc) // Right now we only create one environment per scope
Debug.Assert(envs.IsSingle());
var env = envs.First();
Debug.Assert(!oldEnv.IsStruct);
oldEnv.CapturesParent = true;
oldEnv = env;
}
capturedEnvs.RemoveAll(curScope.DeclaredEnvironments);
curScope = curScope.Parent;
}
if (capturedEnvs.Count > 0)
{ {
var (found, _) = GetVisibleClosure(closureScope, localFunc); throw ExceptionUtilities.Unreachable;
capturedVars.AddAll(found.CapturedVariables);
} }
capturedEnvs.Free();
}
});
} }
for (var curScope = closureScope; /// <summary>
curScope != null && capturedVars.Count > 0; /// We may have ended up with a closure environment containing only
curScope = curScope.Parent) /// 'this'. This is basically equivalent to the containing type itself,
/// so we can inline the 'this' parameter into environments that
/// reference this one or lower closures directly onto the containing
/// type.
/// </summary>
private void InlineThisOnlyEnvironments()
{ {
if (!(capturedVars.RemoveAll(curScope.DeclaredVariables) || // First make sure 'this' even exists
capturedVars.RemoveAll(curScope.Closures.Select(c => c.OriginalMethodSymbol)))) if (!_topLevelMethod.TryGetThisParameter(out var thisParam) ||
thisParam == null)
{ {
continue; return;
} }
outermost = curScope; var topLevelEnvs = ScopeTree.DeclaredEnvironments;
if (innermost == null)
// If it does exist, 'this' is always in the top-level environment
if (topLevelEnvs.Count == 0)
{ {
innermost = curScope; return;
}
} }
// If any captured variables are left, they're captured above method scope Debug.Assert(topLevelEnvs.Count == 1);
if (capturedVars.Count > 0) var env = topLevelEnvs[0];
// The environment must contain only 'this' to be inlined
if (env.CapturedVariables.Count > 1 ||
!env.CapturedVariables.Contains(thisParam))
{ {
outermost = null; return;
} }
capturedVars.Free(); if (env.IsStruct)
{
// If everything that captures the 'this' environment
// lives in the containing type, we can remove the env
bool cantRemove = CheckClosures(ScopeTree, (scope, closure) =>
{
return closure.CapturedEnvironments.Contains(env) &&
closure.ContainingEnvironmentOpt != null;
});
return (innermost, outermost); if (!cantRemove)
{
RemoveEnv();
} }
}
void RecordClosureScope(Scope innermost, Scope outermost, Closure closure) else
{ {
// 1) if there is innermost scope, lambda goes there as we cannot go any higher. // Class-based 'this' closures can move member functions
// 2) scopes in [innermostScope, outermostScope) chain need to have access to the parent scope. // to the top-level type and environments which capture
// // the 'this' environment can capture 'this' directly
// Example: RemoveEnv();
// if a lambda captures a method's parameter and `this`, VisitClosures(ScopeTree, (scope, closure) =>
// its innermost scope is the root Scope (method locals and parameters) {
// and outermost Scope is null if (closure.ContainingEnvironmentOpt == env)
// 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)
{ {
closure.ContainingEnvironmentOpt = innermost.DeclaredEnvironments[0]; closure.ContainingEnvironmentOpt = null;
}
});
while (innermost != outermost) // Find all environments in the scope below that could
// capture the parent. If there are any, add 'this' to
// the list of captured variables and remove the parent
// link
VisitFirstLevelScopes(ScopeTree);
void VisitFirstLevelScopes(Scope scope)
{ {
NeedsParentFrame.Add(innermost.BoundNode); var classEnvs = scope.DeclaredEnvironments.Where(e => !e.IsStruct);
innermost = innermost.Parent; if (classEnvs.IsEmpty())
{
// Keep looking for nested environments
foreach (var nested in scope.NestedScopes)
{
VisitFirstLevelScopes(nested);
}
}
else
{
foreach (var declEnv in classEnvs)
{
if (declEnv.CapturesParent)
{
declEnv.CapturedVariables.Insert(0, thisParam);
declEnv.CapturesParent = false;
}
} }
} }
} }
} }
private void MakeAndAssignEnvironments(ArrayBuilder<ClosureDebugInfo> closureDebugInfo) void RemoveEnv()
{
topLevelEnvs.RemoveAt(topLevelEnvs.IndexOf(env));
VisitClosures(ScopeTree, (scope, closure) =>
{
var index = closure.CapturedEnvironments.IndexOf(env);
if (index >= 0)
{
closure.CapturedEnvironments.RemoveAt(index);
}
});
}
}
private void MakeAndAssignEnvironments()
{ {
VisitScopeTree(ScopeTree, scope => VisitScopeTree(ScopeTree, scope =>
{ {
if (scope.DeclaredVariables.Count > 0) // Currently all variables declared in the same scope are added
// to the same closure environment
var variablesInEnvironment = scope.DeclaredVariables;
// Don't create empty environments
if (variablesInEnvironment.Count == 0)
{ {
return;
}
// First walk the nested scopes to find all closures which // First walk the nested scopes to find all closures which
// capture variables from this scope. They all need to capture // capture variables from this scope. They all need to capture
// this environment. This includes closures which captured local // this environment. This includes closures which captured local
...@@ -253,6 +325,16 @@ private void MakeAndAssignEnvironments(ArrayBuilder<ClosureDebugInfo> closureDeb ...@@ -253,6 +325,16 @@ private void MakeAndAssignEnvironments(ArrayBuilder<ClosureDebugInfo> closureDeb
bool isStruct = true; bool isStruct = true;
var closures = new SetWithInsertionOrder<Closure>(); var closures = new SetWithInsertionOrder<Closure>();
bool addedItem; bool addedItem;
// This loop is O(n), where n is the length of the chain
// L_1 <- L_2 <- L_3 ...
// where L_1 represents a local function that directly captures the current
// environment, L_2 represents a local function that directly captures L_1,
// L_3 represents a local function that captures L_2, and so on.
//
// Each iteration of the loop runs a visitor that is proportional to the
// number of closures in nested scopes, so we hope that the total number
// of nested functions and function chains is small in any real-world code.
do do
{ {
addedItem = false; addedItem = false;
...@@ -270,43 +352,19 @@ private void MakeAndAssignEnvironments(ArrayBuilder<ClosureDebugInfo> closureDeb ...@@ -270,43 +352,19 @@ private void MakeAndAssignEnvironments(ArrayBuilder<ClosureDebugInfo> closureDeb
} while (addedItem == true); } while (addedItem == true);
// Next create the environment and add it to the declaration scope // Next create the environment and add it to the declaration scope
// Currently all variables declared in the same scope are added var env = new ClosureEnvironment(variablesInEnvironment, isStruct);
// to the same closure environment
var env = MakeEnvironment(scope, scope.DeclaredVariables, isStruct);
scope.DeclaredEnvironments.Add(env); scope.DeclaredEnvironments.Add(env);
_topLevelMethod.TryGetThisParameter(out var thisParam);
foreach (var closure in closures) foreach (var closure in closures)
{ {
closure.CapturedEnvironments.Add(env); closure.CapturedEnvironments.Add(env);
} if (thisParam != null && env.CapturedVariables.Contains(thisParam))
}
});
ClosureEnvironment MakeEnvironment(Scope scope, IEnumerable<Symbol> capturedVariables, bool isStruct)
{ {
var scopeBoundNode = scope.BoundNode; closure.CapturesThis = true;
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() internal DebugId GetTopLevelMethodId()
...@@ -314,7 +372,7 @@ internal DebugId GetTopLevelMethodId() ...@@ -314,7 +372,7 @@ internal DebugId GetTopLevelMethodId()
return _slotAllocatorOpt?.MethodId ?? new DebugId(_topLevelMethodOrdinal, _compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); return _slotAllocatorOpt?.MethodId ?? new DebugId(_topLevelMethodOrdinal, _compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal);
} }
private DebugId GetClosureId(SyntaxNode syntax, ArrayBuilder<ClosureDebugInfo> closureDebugInfo) internal DebugId GetClosureId(SyntaxNode syntax, ArrayBuilder<ClosureDebugInfo> closureDebugInfo)
{ {
Debug.Assert(syntax != null); Debug.Assert(syntax != null);
...@@ -470,7 +528,6 @@ Closure Helper(Scope scope) ...@@ -470,7 +528,6 @@ Closure Helper(Scope scope)
public void Free() public void Free()
{ {
MethodsConvertedToDelegates.Free(); MethodsConvertedToDelegates.Free();
NeedsParentFrame.Free();
ScopeTree.Free(); ScopeTree.Free();
} }
} }
......
...@@ -140,7 +140,7 @@ public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody) ...@@ -140,7 +140,7 @@ public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody)
_framePointers.TryGetValue(synthesizedLambda.ContainingType, out _innermostFramePointer); _framePointers.TryGetValue(synthesizedLambda.ContainingType, out _innermostFramePointer);
} }
var containerAsFrame = synthesizedLambda.ContainingType as ClosureEnvironment; var containerAsFrame = synthesizedLambda.ContainingType as SynthesizedClosureEnvironment;
// Includes type parameters from the containing type iff // Includes type parameters from the containing type iff
// the containing type is a frame. If it is a frame then // the containing type is a frame. If it is a frame then
...@@ -201,11 +201,11 @@ public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody) ...@@ -201,11 +201,11 @@ public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody)
// will always be a LambdaFrame, it's always a capture frame // will always be a LambdaFrame, it's always a capture frame
var frameType = (NamedTypeSymbol)loweredSymbol.Parameters[i].Type.OriginalDefinition; var frameType = (NamedTypeSymbol)loweredSymbol.Parameters[i].Type.OriginalDefinition;
Debug.Assert(frameType is ClosureEnvironment); Debug.Assert(frameType is SynthesizedClosureEnvironment);
if (frameType.Arity > 0) if (frameType.Arity > 0)
{ {
var typeParameters = ((ClosureEnvironment)frameType).ConstructedFromTypeParameters; var typeParameters = ((SynthesizedClosureEnvironment)frameType).ConstructedFromTypeParameters;
Debug.Assert(typeParameters.Length == frameType.Arity); Debug.Assert(typeParameters.Length == frameType.Arity);
var subst = this.TypeMap.SubstituteTypeParameters(typeParameters); var subst = this.TypeMap.SubstituteTypeParameters(typeParameters);
frameType = frameType.Construct(subst); frameType = frameType.Construct(subst);
......
...@@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.CSharp ...@@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.CSharp
/// have captured variables. The result of this analysis is left in <see cref="_analysis"/>. /// 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 /// Then we make a frame, or compiler-generated class, represented by an instance of
/// <see cref="ClosureEnvironment"/> for each scope with captured variables. The generated frames are kept /// <see cref="SynthesizedClosureEnvironment"/> 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 /// 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"/>. /// variable in the corresponding scope. These are maintained in <see cref="MethodToClassRewriter.proxies"/>.
/// ///
...@@ -73,7 +73,7 @@ internal sealed partial class LambdaRewriter : MethodToClassRewriter ...@@ -73,7 +73,7 @@ internal sealed partial class LambdaRewriter : MethodToClassRewriter
// lambda frame for static lambdas. // lambda frame for static lambdas.
// initialized lazily and could be null if there are no static lambdas // initialized lazily and could be null if there are no static lambdas
private ClosureEnvironment _lazyStaticLambdaFrame; private SynthesizedClosureEnvironment _lazyStaticLambdaFrame;
// A mapping from every lambda parameter to its corresponding method's parameter. // A mapping from every lambda parameter to its corresponding method's parameter.
private readonly Dictionary<ParameterSymbol, ParameterSymbol> _parameterMap = new Dictionary<ParameterSymbol, ParameterSymbol>(); private readonly Dictionary<ParameterSymbol, ParameterSymbol> _parameterMap = new Dictionary<ParameterSymbol, ParameterSymbol>();
...@@ -93,7 +93,7 @@ public MappedLocalFunction(SynthesizedLambdaMethod symbol, ClosureKind closureKi ...@@ -93,7 +93,7 @@ public MappedLocalFunction(SynthesizedLambdaMethod symbol, ClosureKind closureKi
private readonly Dictionary<LocalFunctionSymbol, MappedLocalFunction> _localFunctionMap = new Dictionary<LocalFunctionSymbol, MappedLocalFunction>(); private readonly Dictionary<LocalFunctionSymbol, MappedLocalFunction> _localFunctionMap = new Dictionary<LocalFunctionSymbol, MappedLocalFunction>();
// for each block with lifted (captured) variables, the corresponding frame type // for each block with lifted (captured) variables, the corresponding frame type
private readonly Dictionary<BoundNode, ClosureEnvironment> _frames = new Dictionary<BoundNode, ClosureEnvironment>(); private readonly Dictionary<BoundNode, Analysis.ClosureEnvironment> _frames = new Dictionary<BoundNode, Analysis.ClosureEnvironment>();
// the current set of frame pointers in scope. Each is either a local variable (where introduced), // 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. // or the "this" parameter when at the top level. Keys in this map are never constructed types.
...@@ -268,7 +268,7 @@ protected override bool NeedsProxy(Symbol localOrParameter) ...@@ -268,7 +268,7 @@ protected override bool NeedsProxy(Symbol localOrParameter)
diagnostics, diagnostics,
assignLocals); assignLocals);
rewriter.SynthesizeClosureEnvironments(); rewriter.SynthesizeClosureEnvironments(closureDebugInfoBuilder);
// First, lower everything but references (calls, delegate conversions) // First, lower everything but references (calls, delegate conversions)
// to local functions // to local functions
...@@ -341,10 +341,10 @@ protected override NamedTypeSymbol ContainingType ...@@ -341,10 +341,10 @@ protected override NamedTypeSymbol ContainingType
static partial void CheckLocalsDefined(BoundNode node); static partial void CheckLocalsDefined(BoundNode node);
/// <summary> /// <summary>
/// Adds <see cref="ClosureEnvironment"/> synthesized types to the compilation state /// Adds <see cref="SynthesizedClosureEnvironment"/> synthesized types to the compilation state
/// and creates hoisted fields for all locals captured by the environments. /// and creates hoisted fields for all locals captured by the environments.
/// </summary> /// </summary>
private void SynthesizeClosureEnvironments() private void SynthesizeClosureEnvironments(ArrayBuilder<ClosureDebugInfo> closureDebugInfo)
{ {
Analysis.VisitScopeTree(_analysis.ScopeTree, scope => Analysis.VisitScopeTree(_analysis.ScopeTree, scope =>
{ {
...@@ -356,32 +356,59 @@ private void SynthesizeClosureEnvironments() ...@@ -356,32 +356,59 @@ private void SynthesizeClosureEnvironments()
Debug.Assert(scope.DeclaredEnvironments.Count == 1); Debug.Assert(scope.DeclaredEnvironments.Count == 1);
var env = scope.DeclaredEnvironments[0]; var env = scope.DeclaredEnvironments[0];
var frame = MakeFrame(scope, env.IsStruct);
env.SynthesizedEnvironment = frame;
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(ContainingType, env); CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(ContainingType, frame);
if (env.Constructor != null) if (frame.Constructor != null)
{ {
AddSynthesizedMethod( AddSynthesizedMethod(
env.Constructor, frame.Constructor,
FlowAnalysisPass.AppendImplicitReturn( FlowAnalysisPass.AppendImplicitReturn(
MethodCompiler.BindMethodBody(env.Constructor, CompilationState, null), MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null),
env.Constructor)); frame.Constructor));
} }
foreach (var captured in env.CapturedVariables) foreach (var captured in env.CapturedVariables)
{ {
Debug.Assert(!proxies.ContainsKey(captured)); Debug.Assert(!proxies.ContainsKey(captured));
var hoistedField = LambdaCapturedVariable.Create(env, captured, ref _synthesizedFieldNameIdDispenser); var hoistedField = LambdaCapturedVariable.Create(frame, captured, ref _synthesizedFieldNameIdDispenser);
proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false)); proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false));
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(env, hoistedField); CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, hoistedField);
} }
_frames.Add(scope.BoundNode, env); _frames.Add(scope.BoundNode, env);
} }
}); });
SynthesizedClosureEnvironment MakeFrame(Analysis.Scope scope, bool isStruct)
{
var scopeBoundNode = scope.BoundNode;
var syntax = scopeBoundNode.Syntax;
Debug.Assert(syntax != null);
DebugId methodId = _analysis.GetTopLevelMethodId();
DebugId closureId = _analysis.GetClosureId(syntax, closureDebugInfo);
var containingMethod = scope.ContainingClosureOpt?.OriginalMethodSymbol ?? _topLevelMethod;
if ((object)_substitutedSourceMethod != null && containingMethod == _topLevelMethod)
{
containingMethod = _substitutedSourceMethod;
} }
private ClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambdaOrFunction lambda) return new SynthesizedClosureEnvironment(
_topLevelMethod,
containingMethod,
isStruct,
syntax,
methodId,
closureId);
}
}
private SynthesizedClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambdaOrFunction lambda)
{ {
if (_lazyStaticLambdaFrame == null) if (_lazyStaticLambdaFrame == null)
{ {
...@@ -406,8 +433,7 @@ private ClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambd ...@@ -406,8 +433,7 @@ private ClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambd
DebugId closureId = default(DebugId); 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 // 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); var containingMethod = isNonGeneric ? null : (_substitutedSourceMethod ?? _topLevelMethod);
_lazyStaticLambdaFrame = new ClosureEnvironment( _lazyStaticLambdaFrame = new SynthesizedClosureEnvironment(
SpecializedCollections.EmptyEnumerable<Symbol>(),
_topLevelMethod, _topLevelMethod,
containingMethod, containingMethod,
isStruct: false, isStruct: false,
...@@ -530,11 +556,12 @@ private static void InsertAndFreePrologue(ArrayBuilder<BoundStatement> result, A ...@@ -530,11 +556,12 @@ private static void InsertAndFreePrologue(ArrayBuilder<BoundStatement> result, A
/// Introduce a frame around the translation of the given node. /// Introduce a frame around the translation of the given node.
/// </summary> /// </summary>
/// <param name="node">The node whose translation should be translated to contain a frame</param> /// <param name="node">The node whose translation should be translated to contain a frame</param>
/// <param name="frame">The frame for the translated node</param> /// <param name="env">The environment 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> /// <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> /// <returns>The translated statement, as returned from F</returns>
private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func<ArrayBuilder<BoundExpression>, ArrayBuilder<LocalSymbol>, BoundNode> F) private BoundNode IntroduceFrame(BoundNode node, Analysis.ClosureEnvironment env, Func<ArrayBuilder<BoundExpression>, ArrayBuilder<LocalSymbol>, BoundNode> F)
{ {
var frame = env.SynthesizedEnvironment;
var frameTypeParameters = ImmutableArray.Create(StaticCast<TypeSymbol>.From(_currentTypeParameters).SelectAsArray(TypeMap.TypeSymbolAsTypeWithModifiers), 0, frame.Arity); var frameTypeParameters = ImmutableArray.Create(StaticCast<TypeSymbol>.From(_currentTypeParameters).SelectAsArray(TypeMap.TypeSymbolAsTypeWithModifiers), 0, frame.Arity);
NamedTypeSymbol frameType = frame.ConstructIfGeneric(frameTypeParameters); NamedTypeSymbol frameType = frame.ConstructIfGeneric(frameTypeParameters);
...@@ -562,14 +589,7 @@ private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func< ...@@ -562,14 +589,7 @@ private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func<
if ((object)_innermostFramePointer != null) if ((object)_innermostFramePointer != null)
{ {
proxies.TryGetValue(_innermostFramePointer, out oldInnermostFrameProxy); proxies.TryGetValue(_innermostFramePointer, out oldInnermostFrameProxy);
if (_analysis.NeedsParentFrame.Contains(node) && if (env.CapturesParent)
// If the frame pointer is a struct type that means the frame is a struct
// passed by-ref to a local function. Capturing parent frames for local
// functions is performed in RemapLambdaOrLocalFunction, rather than here
// (since struct frame pointers should never be captured, but instead be
// passed in a list to the needed local functions).
!(_innermostFramePointer.Kind == SymbolKind.Local &&
((LocalSymbol)_innermostFramePointer).Type.IsValueType))
{ {
var capturedFrame = LambdaCapturedVariable.Create(frame, _innermostFramePointer, ref _synthesizedFieldNameIdDispenser); var capturedFrame = LambdaCapturedVariable.Create(frame, _innermostFramePointer, ref _synthesizedFieldNameIdDispenser);
FieldSymbol frameParent = capturedFrame.AsMember(frameType); FieldSymbol frameParent = capturedFrame.AsMember(frameType);
...@@ -590,7 +610,7 @@ private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func< ...@@ -590,7 +610,7 @@ private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func<
// Capture any parameters of this block. This would typically occur // Capture any parameters of this block. This would typically occur
// at the top level of a method or lambda with captured parameters. // at the top level of a method or lambda with captured parameters.
foreach (var variable in frame.CapturedVariables) foreach (var variable in env.CapturedVariables)
{ {
InitVariableProxy(syntax, variable, framePointer, prologue); InitVariableProxy(syntax, variable, framePointer, prologue);
} }
...@@ -728,7 +748,7 @@ public override BoundNode VisitBaseReference(BoundBaseReference node) ...@@ -728,7 +748,7 @@ public override BoundNode VisitBaseReference(BoundBaseReference node)
out NamedTypeSymbol constructedFrame) out NamedTypeSymbol constructedFrame)
{ {
var translatedLambdaContainer = synthesizedMethod.ContainingType; var translatedLambdaContainer = synthesizedMethod.ContainingType;
var containerAsFrame = translatedLambdaContainer as ClosureEnvironment; var containerAsFrame = translatedLambdaContainer as SynthesizedClosureEnvironment;
// All of _currentTypeParameters might not be preserved here due to recursively calling upwards in the chain of local functions/lambdas // 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)); Debug.Assert((typeArgumentsOpt.IsDefault && !originalMethod.IsGenericMethod) || (typeArgumentsOpt.Length == originalMethod.Arity));
...@@ -866,9 +886,8 @@ private BoundSequence RewriteSequence(BoundSequence node, ArrayBuilder<BoundExpr ...@@ -866,9 +886,8 @@ private BoundSequence RewriteSequence(BoundSequence node, ArrayBuilder<BoundExpr
public override BoundNode VisitBlock(BoundBlock node) public override BoundNode VisitBlock(BoundBlock node)
{ {
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class. // Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out frame)) if (_frames.TryGetValue(node, out var frame))
{ {
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) => return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
RewriteBlock(node, prologue, newLocals)); RewriteBlock(node, prologue, newLocals));
...@@ -924,8 +943,7 @@ public override BoundNode VisitScope(BoundScope node) ...@@ -924,8 +943,7 @@ public override BoundNode VisitScope(BoundScope node)
public override BoundNode VisitCatchBlock(BoundCatchBlock node) public override BoundNode VisitCatchBlock(BoundCatchBlock node)
{ {
// Test if this frame has captured variables and requires the introduction of a closure class. // Test if this frame has captured variables and requires the introduction of a closure class.
ClosureEnvironment frame; if (_frames.TryGetValue(node, out var frame))
if (_frames.TryGetValue(node, out frame))
{ {
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) => return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{ {
...@@ -991,9 +1009,8 @@ private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder<BoundExpressio ...@@ -991,9 +1009,8 @@ private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder<BoundExpressio
public override BoundNode VisitSequence(BoundSequence node) public override BoundNode VisitSequence(BoundSequence node)
{ {
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class. // Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out frame)) if (_frames.TryGetValue(node, out var frame))
{ {
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) => return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{ {
...@@ -1008,10 +1025,9 @@ public override BoundNode VisitSequence(BoundSequence node) ...@@ -1008,10 +1025,9 @@ public override BoundNode VisitSequence(BoundSequence node)
public override BoundNode VisitStatementList(BoundStatementList node) public override BoundNode VisitStatementList(BoundStatementList node)
{ {
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class. // 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. // That can occur for a BoundStatementList if it is the body of a method with captured parameters.
if (_frames.TryGetValue(node, out frame)) if (_frames.TryGetValue(node, out var frame))
{ {
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) => return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{ {
...@@ -1034,9 +1050,8 @@ public override BoundNode VisitStatementList(BoundStatementList node) ...@@ -1034,9 +1050,8 @@ public override BoundNode VisitStatementList(BoundStatementList node)
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node) public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{ {
ClosureEnvironment frame;
// Test if this frame has captured variables and requires the introduction of a closure class. // Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out frame)) if (_frames.TryGetValue(node, out var frame))
{ {
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) => return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{ {
...@@ -1105,7 +1120,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen ...@@ -1105,7 +1120,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen
{ {
ClosureKind closureKind; ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer; NamedTypeSymbol translatedLambdaContainer;
ClosureEnvironment containerAsFrame; SynthesizedClosureEnvironment containerAsFrame;
BoundNode lambdaScope; BoundNode lambdaScope;
DebugId topLevelMethodId; DebugId topLevelMethodId;
DebugId lambdaId; DebugId lambdaId;
...@@ -1178,7 +1193,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos ...@@ -1178,7 +1193,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
IBoundLambdaOrFunction node, IBoundLambdaOrFunction node,
out ClosureKind closureKind, out ClosureKind closureKind,
out NamedTypeSymbol translatedLambdaContainer, out NamedTypeSymbol translatedLambdaContainer,
out ClosureEnvironment containerAsFrame, out SynthesizedClosureEnvironment containerAsFrame,
out BoundNode lambdaScope, out BoundNode lambdaScope,
out DebugId topLevelMethodId, out DebugId topLevelMethodId,
out DebugId lambdaId) out DebugId lambdaId)
...@@ -1186,23 +1201,13 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos ...@@ -1186,23 +1201,13 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
Analysis.Closure closure = Analysis.GetClosureInTree(_analysis.ScopeTree, node.Symbol); Analysis.Closure closure = Analysis.GetClosureInTree(_analysis.ScopeTree, node.Symbol);
var structClosures = closure.CapturedEnvironments var structClosures = closure.CapturedEnvironments
.Where(env => env.IsStructType()).AsImmutable(); .Where(env => env.IsStruct).Select(env => env.SynthesizedEnvironment).AsImmutable();
int closureOrdinal; int closureOrdinal;
if (closure.ContainingEnvironmentOpt != null) if (closure.ContainingEnvironmentOpt != null)
{ {
containerAsFrame = closure.ContainingEnvironmentOpt; containerAsFrame = closure.ContainingEnvironmentOpt?.SynthesizedEnvironment;
if (containerAsFrame?.IsValueType == true)
{
// Lower directly onto the containing type
containerAsFrame = null;
lambdaScope = null;
closureKind = ClosureKind.Static; // not exactly... but we've rewritten the receiver to be a by-ref parameter
translatedLambdaContainer = _topLevelMethod.ContainingType;
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
else
{
closureKind = ClosureKind.General; closureKind = ClosureKind.General;
translatedLambdaContainer = containerAsFrame; translatedLambdaContainer = containerAsFrame;
closureOrdinal = containerAsFrame.ClosureOrdinal; closureOrdinal = containerAsFrame.ClosureOrdinal;
...@@ -1218,8 +1223,15 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos ...@@ -1218,8 +1223,15 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
Debug.Assert(tmpScope != null); Debug.Assert(tmpScope != null);
lambdaScope = tmpScope; lambdaScope = tmpScope;
} }
else if (closure.CapturesThis)
{
lambdaScope = null;
containerAsFrame = null;
translatedLambdaContainer = _topLevelMethod.ContainingType;
closureKind = ClosureKind.ThisOnly;
closureOrdinal = LambdaDebugInfo.ThisOnlyClosureOrdinal;
} }
else if (closure.CapturedVariables.Count == 0) else if (closure.CapturedEnvironments.Count == 0)
{ {
if (_analysis.MethodsConvertedToDelegates.Contains(node.Symbol)) if (_analysis.MethodsConvertedToDelegates.Contains(node.Symbol))
{ {
...@@ -1238,11 +1250,12 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos ...@@ -1238,11 +1250,12 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
} }
else else
{ {
lambdaScope = null; // Lower directly onto the containing type
containerAsFrame = null; containerAsFrame = null;
lambdaScope = null;
closureKind = ClosureKind.Static; // not exactly... but we've rewritten the receiver to be a by-ref parameter
translatedLambdaContainer = _topLevelMethod.ContainingType; translatedLambdaContainer = _topLevelMethod.ContainingType;
closureKind = ClosureKind.ThisOnly; closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
closureOrdinal = LambdaDebugInfo.ThisOnlyClosureOrdinal;
} }
// Move the body of the lambda to a freshly generated synthetic method on its frame. // Move the body of the lambda to a freshly generated synthetic method on its frame.
...@@ -1338,7 +1351,7 @@ private BoundNode RewriteLambdaConversion(BoundLambda node) ...@@ -1338,7 +1351,7 @@ private BoundNode RewriteLambdaConversion(BoundLambda node)
ClosureKind closureKind; ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer; NamedTypeSymbol translatedLambdaContainer;
ClosureEnvironment containerAsFrame; SynthesizedClosureEnvironment containerAsFrame;
BoundNode lambdaScope; BoundNode lambdaScope;
DebugId topLevelMethodId; DebugId topLevelMethodId;
DebugId lambdaId; DebugId lambdaId;
......
...@@ -10,10 +10,9 @@ ...@@ -10,10 +10,9 @@
namespace Microsoft.CodeAnalysis.CSharp namespace Microsoft.CodeAnalysis.CSharp
{ {
/// <summary> /// <summary>
/// A class that represents the set of variables in a scope that have been /// The synthesized type added to a compilation to hold captured variables for closures.
/// captured by nested functions within that scope.
/// </summary> /// </summary>
internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMethodBodyImplementationSymbol internal sealed class SynthesizedClosureEnvironment : SynthesizedContainer, ISynthesizedMethodBodyImplementationSymbol
{ {
private readonly MethodSymbol _topLevelMethod; private readonly MethodSymbol _topLevelMethod;
internal readonly SyntaxNode ScopeSyntaxOpt; internal readonly SyntaxNode ScopeSyntaxOpt;
...@@ -25,13 +24,11 @@ internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMet ...@@ -25,13 +24,11 @@ internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMet
internal readonly MethodSymbol OriginalContainingMethodOpt; internal readonly MethodSymbol OriginalContainingMethodOpt;
internal readonly FieldSymbol SingletonCache; internal readonly FieldSymbol SingletonCache;
internal readonly MethodSymbol StaticConstructor; internal readonly MethodSymbol StaticConstructor;
public readonly IEnumerable<Symbol> CapturedVariables;
public override TypeKind TypeKind { get; } public override TypeKind TypeKind { get; }
internal override MethodSymbol Constructor { get; } internal override MethodSymbol Constructor { get; }
internal ClosureEnvironment( internal SynthesizedClosureEnvironment(
IEnumerable<Symbol> capturedVariables,
MethodSymbol topLevelMethod, MethodSymbol topLevelMethod,
MethodSymbol containingMethod, MethodSymbol containingMethod,
bool isStruct, bool isStruct,
...@@ -40,11 +37,10 @@ internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMet ...@@ -40,11 +37,10 @@ internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMet
DebugId closureId) DebugId closureId)
: base(MakeName(scopeSyntaxOpt, methodId, closureId), containingMethod) : base(MakeName(scopeSyntaxOpt, methodId, closureId), containingMethod)
{ {
CapturedVariables = capturedVariables;
TypeKind = isStruct ? TypeKind.Struct : TypeKind.Class; TypeKind = isStruct ? TypeKind.Struct : TypeKind.Class;
_topLevelMethod = topLevelMethod; _topLevelMethod = topLevelMethod;
OriginalContainingMethodOpt = containingMethod; OriginalContainingMethodOpt = containingMethod;
Constructor = isStruct ? null : new LambdaFrameConstructor(this); Constructor = isStruct ? null : new SynthesizedClosureEnvironmentConstructor(this);
this.ClosureOrdinal = closureId.Ordinal; this.ClosureOrdinal = closureId.Ordinal;
// static lambdas technically have the class scope so the scope syntax is null // static lambdas technically have the class scope so the scope syntax is null
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
namespace Microsoft.CodeAnalysis.CSharp namespace Microsoft.CodeAnalysis.CSharp
{ {
internal sealed class LambdaFrameConstructor : SynthesizedInstanceConstructor, ISynthesizedMethodBodyImplementationSymbol internal sealed class SynthesizedClosureEnvironmentConstructor : SynthesizedInstanceConstructor, ISynthesizedMethodBodyImplementationSymbol
{ {
internal LambdaFrameConstructor(ClosureEnvironment frame) internal SynthesizedClosureEnvironmentConstructor(SynthesizedClosureEnvironment frame)
: base(frame) : base(frame)
{ {
} }
......
...@@ -19,7 +19,7 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy ...@@ -19,7 +19,7 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy
internal SynthesizedLambdaMethod( internal SynthesizedLambdaMethod(
NamedTypeSymbol containingType, NamedTypeSymbol containingType,
ImmutableArray<ClosureEnvironment> structEnvironments, ImmutableArray<SynthesizedClosureEnvironment> structEnvironments,
ClosureKind closureKind, ClosureKind closureKind,
MethodSymbol topLevelMethod, MethodSymbol topLevelMethod,
DebugId topLevelMethodId, DebugId topLevelMethodId,
...@@ -43,9 +43,9 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy ...@@ -43,9 +43,9 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy
TypeMap typeMap; TypeMap typeMap;
ImmutableArray<TypeParameterSymbol> typeParameters; ImmutableArray<TypeParameterSymbol> typeParameters;
ImmutableArray<TypeParameterSymbol> constructedFromTypeParameters; ImmutableArray<TypeParameterSymbol> constructedFromTypeParameters;
ClosureEnvironment lambdaFrame; SynthesizedClosureEnvironment lambdaFrame;
lambdaFrame = this.ContainingType as ClosureEnvironment; lambdaFrame = this.ContainingType as SynthesizedClosureEnvironment;
switch (closureKind) switch (closureKind)
{ {
case ClosureKind.Singleton: // all type parameters on method (except the top level method's) case ClosureKind.Singleton: // all type parameters on method (except the top level method's)
......
...@@ -11,6 +11,53 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen ...@@ -11,6 +11,53 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{ {
public class CodeGenClosureLambdaTests : CSharpTestBase public class CodeGenClosureLambdaTests : CSharpTestBase
{ {
[Fact]
public void EnvironmentChainContainsUnusedEnvironment()
{
CompileAndVerify(@"
using System;
class C
{
void M(int x)
{
{
int y = 10;
Action f1 = () => Console.WriteLine(y);
{
int z = 5;
Action f2 = () => Console.WriteLine(z + x);
f2();
}
f1();
}
}
public static void Main() => new C().M(3);
}", expectedOutput: @"8
10");
}
[Fact]
public void CaptureThisAsFramePointer()
{
var comp = @"
using System;
using System.Collections.Generic;
class C
{
int _z = 0;
void M(IEnumerable<int> xs)
{
foreach (var x in xs)
{
Func<int, int> captureFunc = k => x + _z;
}
}
}";
CompileAndVerify(comp);
}
[Fact] [Fact]
public void StaticClosure01() public void StaticClosure01()
{ {
...@@ -3655,8 +3702,8 @@ .maxstack 2 ...@@ -3655,8 +3702,8 @@ .maxstack 2
IL_000d: ldarg.0 IL_000d: ldarg.0
IL_000e: call ""bool Program.c1.T()"" IL_000e: call ""bool Program.c1.T()""
IL_0013: brfalse.s IL_002e IL_0013: brfalse.s IL_002e
IL_0015: ldarg.0 IL_0015: ldloc.0
IL_0016: ldftn ""bool Program.c1.<Test>b__1_0(int)"" IL_0016: ldftn ""bool Program.c1.<>c__DisplayClass1_0.<Test>b__0(int)""
IL_001c: newobj ""System.Func<int, bool>..ctor(object, System.IntPtr)"" IL_001c: newobj ""System.Func<int, bool>..ctor(object, System.IntPtr)""
IL_0021: ldc.i4.s 42 IL_0021: ldc.i4.s 42
IL_0023: callvirt ""bool System.Func<int, bool>.Invoke(int)"" IL_0023: callvirt ""bool System.Func<int, bool>.Invoke(int)""
......
...@@ -30,6 +30,98 @@ public static IMethodSymbol FindLocalFunction(this CompilationVerifier verifier, ...@@ -30,6 +30,98 @@ public static IMethodSymbol FindLocalFunction(this CompilationVerifier verifier,
[CompilerTrait(CompilerFeature.LocalFunctions)] [CompilerTrait(CompilerFeature.LocalFunctions)]
public class CodeGenLocalFunctionTests : CSharpTestBase public class CodeGenLocalFunctionTests : CSharpTestBase
{ {
[Fact]
public void EnvironmentChainContainsStructEnvironment()
{
CompileAndVerify(@"
using System;
class C
{
void M(int x)
{
{
int y = 10;
void L() => Console.WriteLine(y);
{
int z = 5;
Action f2 = () => Console.WriteLine(z + x);
f2();
}
L();
}
}
public static void Main() => new C().M(3);
}", expectedOutput: @"8
10");
}
[Fact]
public void Repro20577()
{
var comp = CreateStandardCompilation(@"
using System.Linq;
public class Program {
public static void Main(string[] args) {
object v;
void AAA() {
object BBB(object v2) {
var a = v;
((object[])v2).Select(i => BBB(i));
return null;
}
}
}
}", references: new[] { LinqAssemblyRef });
CompileAndVerify(comp);
}
[Fact]
public void Repro19033()
{
CompileAndVerify(@"
using System;
class Program
{
void Q(int n = 0)
{
{
object mc;
string B(object map)
{
Action<int> a = _ => B(new object());
return n.ToString();
}
}
}
}");
}
[Fact]
public void Repro19033_2()
{
CompileAndVerify(@"
using System;
class C
{
static void F(Action a)
{
object x = null;
{
object y = null;
void G(object z)
{
F(() => G(x));
}
}
}
}");
}
[Fact] [Fact]
[WorkItem(18814, "https://github.com/dotnet/roslyn/issues/18814")] [WorkItem(18814, "https://github.com/dotnet/roslyn/issues/18814")]
[WorkItem(18918, "https://github.com/dotnet/roslyn/issues/18918")] [WorkItem(18918, "https://github.com/dotnet/roslyn/issues/18918")]
...@@ -78,7 +170,7 @@ void L5() ...@@ -78,7 +170,7 @@ void L5()
1"); 1");
verifier.VerifyIL("C.M()", @" verifier.VerifyIL("C.M()", @"
{ {
// Code size 46 (0x2e) // Code size 47 (0x2f)
.maxstack 2 .maxstack 2
.locals init (C.<>c__DisplayClass2_0 V_0) //CS$<>8__locals0 .locals init (C.<>c__DisplayClass2_0 V_0) //CS$<>8__locals0
IL_0000: ldloca.s V_0 IL_0000: ldloca.s V_0
...@@ -90,24 +182,24 @@ .maxstack 2 ...@@ -90,24 +182,24 @@ .maxstack 2
IL_0010: ldarg.0 IL_0010: ldarg.0
IL_0011: ldfld ""int C._x"" IL_0011: ldfld ""int C._x""
IL_0016: call ""void System.Console.WriteLine(int)"" IL_0016: call ""void System.Console.WriteLine(int)""
IL_001b: ldloca.s V_0 IL_001b: ldarg.0
IL_001d: call ""void C.<M>g__L12_0(ref C.<>c__DisplayClass2_0)"" IL_001c: ldloca.s V_0
IL_0022: ldarg.0 IL_001e: call ""void C.<M>g__L12_0(ref C.<>c__DisplayClass2_0)""
IL_0023: ldfld ""int C._x"" IL_0023: ldarg.0
IL_0028: call ""void System.Console.WriteLine(int)"" IL_0024: ldfld ""int C._x""
IL_002d: ret IL_0029: call ""void System.Console.WriteLine(int)""
IL_002e: ret
}"); }");
// L1 // L1
verifier.VerifyIL("C.<M>g__L12_0(ref C.<>c__DisplayClass2_0)", @" verifier.VerifyIL("C.<M>g__L12_0(ref C.<>c__DisplayClass2_0)", @"
{ {
// Code size 13 (0xd) // Code size 8 (0x8)
.maxstack 2 .maxstack 2
IL_0000: ldarg.0 IL_0000: ldarg.0
IL_0001: ldfld ""C C.<>c__DisplayClass2_0.<>4__this"" IL_0001: ldarg.1
IL_0006: ldarg.0 IL_0002: call ""void C.<M>g__L22_1(ref C.<>c__DisplayClass2_0)""
IL_0007: call ""void C.<M>g__L22_1(ref C.<>c__DisplayClass2_0)"" IL_0007: ret
IL_000c: ret
}"); }");
// L2 // L2
verifier.VerifyIL("C.<M>g__L22_1(ref C.<>c__DisplayClass2_0)", @" verifier.VerifyIL("C.<M>g__L22_1(ref C.<>c__DisplayClass2_0)", @"
...@@ -122,35 +214,34 @@ .maxstack 2 ...@@ -122,35 +214,34 @@ .maxstack 2
// Skip some... L5 // Skip some... L5
verifier.VerifyIL("C.<M>g__L52_4(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @" verifier.VerifyIL("C.<M>g__L52_4(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @"
{ {
// Code size 9 (0x9) // Code size 10 (0xa)
.maxstack 2 .maxstack 3
IL_0000: ldarg.0 IL_0000: ldarg.0
IL_0001: ldarg.1 IL_0001: ldarg.1
IL_0002: call ""int C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)"" IL_0002: ldarg.2
IL_0007: pop IL_0003: call ""int C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)""
IL_0008: ret IL_0008: pop
IL_0009: ret
}"); }");
// L6 // L6
verifier.VerifyIL("C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @" verifier.VerifyIL("C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @"
{ {
// Code size 35 (0x23) // Code size 25 (0x19)
.maxstack 4 .maxstack 4
.locals init (int V_0) .locals init (int V_0)
IL_0000: ldarg.1 IL_0000: ldarg.2
IL_0001: ldfld ""int C.<>c__DisplayClass2_1.var2"" IL_0001: ldfld ""int C.<>c__DisplayClass2_1.var2""
IL_0006: ldarg.1 IL_0006: ldarg.0
IL_0007: ldfld ""C C.<>c__DisplayClass2_1.<>4__this"" IL_0007: ldarg.0
IL_000c: ldarg.1 IL_0008: ldfld ""int C._x""
IL_000d: ldfld ""C C.<>c__DisplayClass2_1.<>4__this"" IL_000d: stloc.0
IL_0012: ldfld ""int C._x"" IL_000e: ldloc.0
IL_0017: stloc.0 IL_000f: ldc.i4.1
IL_0018: ldloc.0 IL_0010: add
IL_0019: ldc.i4.1 IL_0011: stfld ""int C._x""
IL_001a: add IL_0016: ldloc.0
IL_001b: stfld ""int C._x"" IL_0017: add
IL_0020: ldloc.0 IL_0018: ret
IL_0021: add
IL_0022: ret
}"); }");
} }
......
...@@ -1408,8 +1408,8 @@ public int F() ...@@ -1408,8 +1408,8 @@ public int F()
var reader0 = md0.MetadataReader; var reader0 = md0.MetadataReader;
CheckNames(reader0, reader0.GetTypeDefNames(), "<Module>", "C", "<>c__DisplayClass0_0", "<>c"); CheckNames(reader0, reader0.GetTypeDefNames(), "<Module>", "C", "<>c__DisplayClass0_0", "<>c");
CheckNames(reader0, reader0.GetMethodDefNames(), "F", ".ctor", "<F>b__0_1", ".ctor", "<F>b__2", ".cctor", ".ctor", "<F>b__0_0"); CheckNames(reader0, reader0.GetMethodDefNames(), "F", ".ctor", ".ctor", "<F>b__1", "<F>b__2", ".cctor", ".ctor", "<F>b__0_0");
CheckNames(reader0, reader0.GetFieldDefNames(), "a", "<>9", "<>9__0_0"); CheckNames(reader0, reader0.GetFieldDefNames(), "<>4__this", "a", "<>9", "<>9__0_0");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference( var diff1 = compilation1.EmitDifference(
...@@ -1420,13 +1420,13 @@ public int F() ...@@ -1420,13 +1420,13 @@ public int F()
var reader1 = diff1.GetMetadata().Reader; var reader1 = diff1.GetMetadata().Reader;
CheckNames(new[] { reader0, reader1 }, reader1.GetTypeDefNames(), "<>c__DisplayClass0#1_0#1"); CheckNames(new[] { reader0, reader1 }, reader1.GetTypeDefNames(), "<>c__DisplayClass0#1_0#1");
CheckNames(new[] { reader0, reader1 }, reader1.GetMethodDefNames(), ".ctor", "F", "<F>b__0#1_1#1", ".ctor", "<F>b__2#1", "<F>b__0#1_0#1"); CheckNames(new[] { reader0, reader1 }, reader1.GetMethodDefNames(), ".ctor", "F", ".ctor", "<F>b__1#1", "<F>b__2#1", "<F>b__0#1_0#1");
CheckNames(new[] { reader0, reader1 }, reader1.GetFieldDefNames(), "a", "<>9__0#1_0#1"); CheckNames(new[] { reader0, reader1 }, reader1.GetFieldDefNames(), "<>4__this", "a", "<>9__0#1_0#1");
diff1.VerifySynthesizedMembers( diff1.VerifySynthesizedMembers(
"C: {<F>b__0#1_1#1, <>c__DisplayClass0#1_0#1, <>c}", "C: {<>c__DisplayClass0#1_0#1, <>c}",
"C.<>c: {<>9__0#1_0#1, <F>b__0#1_0#1}", "C.<>c__DisplayClass0#1_0#1: {<>4__this, a, <F>b__1#1, <F>b__2#1}",
"C.<>c__DisplayClass0#1_0#1: {a, <F>b__2#1}"); "C.<>c: {<>9__0#1_0#1, <F>b__0#1_0#1}");
var diff2 = compilation2.EmitDifference( var diff2 = compilation2.EmitDifference(
diff1.NextGeneration, diff1.NextGeneration,
...@@ -1436,8 +1436,8 @@ public int F() ...@@ -1436,8 +1436,8 @@ public int F()
var reader2 = diff2.GetMetadata().Reader; var reader2 = diff2.GetMetadata().Reader;
CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetTypeDefNames(), "<>c__DisplayClass1#2_0#2"); CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetTypeDefNames(), "<>c__DisplayClass1#2_0#2");
CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetMethodDefNames(), ".ctor", "F", "<F>b__1#2_1#2", ".ctor", "<F>b__2#2", "<F>b__1#2_0#2"); CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetMethodDefNames(), ".ctor", "F", ".ctor", "<F>b__1#2", "<F>b__2#2", "<F>b__1#2_0#2");
CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetFieldDefNames(), "a", "<>9__1#2_0#2"); CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetFieldDefNames(), "<>4__this", "a", "<>9__1#2_0#2");
} }
[Fact] [Fact]
...@@ -1525,8 +1525,8 @@ public int F<T>() ...@@ -1525,8 +1525,8 @@ public int F<T>()
var reader0 = md0.MetadataReader; var reader0 = md0.MetadataReader;
CheckNames(reader0, reader0.GetTypeDefNames(), "<Module>", "C", "<>c__DisplayClass0_0`1", "<>c__0`1"); CheckNames(reader0, reader0.GetTypeDefNames(), "<Module>", "C", "<>c__DisplayClass0_0`1", "<>c__0`1");
CheckNames(reader0, reader0.GetMethodDefNames(), "F", ".ctor", "<F>b__0_1", ".ctor", "<F>b__2", ".cctor", ".ctor", "<F>b__0_0"); CheckNames(reader0, reader0.GetMethodDefNames(), "F", ".ctor", ".ctor", "<F>b__1", "<F>b__2", ".cctor", ".ctor", "<F>b__0_0");
CheckNames(reader0, reader0.GetFieldDefNames(), "a", "<>9", "<>9__0_0"); CheckNames(reader0, reader0.GetFieldDefNames(), "<>4__this", "a", "<>9", "<>9__0_0");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference( var diff1 = compilation1.EmitDifference(
...@@ -1537,13 +1537,13 @@ public int F<T>() ...@@ -1537,13 +1537,13 @@ public int F<T>()
var reader1 = diff1.GetMetadata().Reader; var reader1 = diff1.GetMetadata().Reader;
CheckNames(new[] { reader0, reader1 }, reader1.GetTypeDefNames(), "<>c__DisplayClass0#1_0#1`1", "<>c__0#1`1"); CheckNames(new[] { reader0, reader1 }, reader1.GetTypeDefNames(), "<>c__DisplayClass0#1_0#1`1", "<>c__0#1`1");
CheckNames(new[] { reader0, reader1 }, reader1.GetMethodDefNames(), "F", "<F>b__0#1_1#1", ".ctor", "<F>b__2#1", ".cctor", ".ctor", "<F>b__0#1_0#1"); CheckNames(new[] { reader0, reader1 }, reader1.GetMethodDefNames(), "F", ".ctor", "<F>b__1#1", "<F>b__2#1", ".cctor", ".ctor", "<F>b__0#1_0#1");
CheckNames(new[] { reader0, reader1 }, reader1.GetFieldDefNames(), "a", "<>9", "<>9__0#1_0#1"); CheckNames(new[] { reader0, reader1 }, reader1.GetFieldDefNames(), "<>4__this", "a", "<>9", "<>9__0#1_0#1");
diff1.VerifySynthesizedMembers( diff1.VerifySynthesizedMembers(
"C: {<F>b__0#1_1#1, <>c__DisplayClass0#1_0#1, <>c__0#1}",
"C.<>c__0#1<T>: {<>9__0#1_0#1, <F>b__0#1_0#1}", "C.<>c__0#1<T>: {<>9__0#1_0#1, <F>b__0#1_0#1}",
"C.<>c__DisplayClass0#1_0#1<T>: {a, <F>b__2#1}"); "C: {<>c__DisplayClass0#1_0#1, <>c__0#1}",
"C.<>c__DisplayClass0#1_0#1<T>: {<>4__this, a, <F>b__1#1, <F>b__2#1}");
var diff2 = compilation2.EmitDifference( var diff2 = compilation2.EmitDifference(
diff1.NextGeneration, diff1.NextGeneration,
...@@ -1553,8 +1553,8 @@ public int F<T>() ...@@ -1553,8 +1553,8 @@ public int F<T>()
var reader2 = diff2.GetMetadata().Reader; var reader2 = diff2.GetMetadata().Reader;
CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetTypeDefNames(), "<>c__DisplayClass1#2_0#2`1", "<>c__1#2`1"); CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetTypeDefNames(), "<>c__DisplayClass1#2_0#2`1", "<>c__1#2`1");
CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetMethodDefNames(), "F", "<F>b__1#2_1#2", ".ctor", "<F>b__2#2", ".cctor", ".ctor", "<F>b__1#2_0#2"); CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetMethodDefNames(), "F", ".ctor", "<F>b__1#2", "<F>b__2#2", ".cctor", ".ctor", "<F>b__1#2_0#2");
CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetFieldDefNames(), "a", "<>9", "<>9__1#2_0#2"); CheckNames(new[] { reader0, reader1, reader2 }, reader2.GetFieldDefNames(), "<>4__this", "a", "<>9", "<>9__1#2_0#2");
} }
[Fact] [Fact]
...@@ -1669,9 +1669,9 @@ public int F() ...@@ -1669,9 +1669,9 @@ public int F()
new SemanticEdit(SemanticEditKind.Update, main0, main1, preserveLocalVariables: true))); new SemanticEdit(SemanticEditKind.Update, main0, main1, preserveLocalVariables: true)));
diff1.VerifySynthesizedMembers( diff1.VerifySynthesizedMembers(
"C: {<F>b__1#1_1#1, <>c__DisplayClass1#1_0#1, <>c}",
"C.<>c: {<>9__1#1_0#1, <F>b__1#1_0#1}", "C.<>c: {<>9__1#1_0#1, <F>b__1#1_0#1}",
"C.<>c__DisplayClass1#1_0#1: {a, <F>b__2#1}"); "C.<>c__DisplayClass1#1_0#1: {<>4__this, a, <F>b__1#1, <F>b__2#1}",
"C: {<>c__DisplayClass1#1_0#1, <>c}");
var diff2 = compilation2.EmitDifference( var diff2 = compilation2.EmitDifference(
diff1.NextGeneration, diff1.NextGeneration,
...@@ -1680,10 +1680,10 @@ public int F() ...@@ -1680,10 +1680,10 @@ public int F()
new SemanticEdit(SemanticEditKind.Update, main1, main2, preserveLocalVariables: true))); new SemanticEdit(SemanticEditKind.Update, main1, main2, preserveLocalVariables: true)));
diff2.VerifySynthesizedMembers( diff2.VerifySynthesizedMembers(
"C: {<F>b__1#2_1#2, <>c__DisplayClass1#2_0#2, <>c, <F>b__1#1_1#1, <>c__DisplayClass1#1_0#1}", "C.<>c__DisplayClass1#2_0#2: {<>4__this, a, <F>b__1#2, <F>b__2#2}",
"C: {<>c__DisplayClass1#2_0#2, <>c, <>c__DisplayClass1#1_0#1}",
"C.<>c: {<>9__1#2_0#2, <F>b__1#2_0#2, <>9__1#1_0#1, <F>b__1#1_0#1}", "C.<>c: {<>9__1#2_0#2, <F>b__1#2_0#2, <>9__1#1_0#1, <F>b__1#1_0#1}",
"C.<>c__DisplayClass1#1_0#1: {a, <F>b__2#1}", "C.<>c__DisplayClass1#1_0#1: {<>4__this, a, <F>b__1#1, <F>b__2#1}");
"C.<>c__DisplayClass1#2_0#2: {a, <F>b__2#2}");
var diff3 = compilation3.EmitDifference( var diff3 = compilation3.EmitDifference(
diff2.NextGeneration, diff2.NextGeneration,
...@@ -1691,10 +1691,10 @@ public int F() ...@@ -1691,10 +1691,10 @@ public int F()
new SemanticEdit(SemanticEditKind.Update, main2, main3, preserveLocalVariables: true))); new SemanticEdit(SemanticEditKind.Update, main2, main3, preserveLocalVariables: true)));
diff3.VerifySynthesizedMembers( diff3.VerifySynthesizedMembers(
"C: {<F>b__1#2_1#2, <>c__DisplayClass1#2_0#2, <>c, <F>b__1#1_1#1, <>c__DisplayClass1#1_0#1}", "C.<>c__DisplayClass1#1_0#1: {<>4__this, a, <F>b__1#1, <F>b__2#1}",
"C.<>c: {<>9__1#2_0#2, <F>b__1#2_0#2, <>9__1#1_0#1, <F>b__1#1_0#1}", "C.<>c: {<>9__1#2_0#2, <F>b__1#2_0#2, <>9__1#1_0#1, <F>b__1#1_0#1}",
"C.<>c__DisplayClass1#1_0#1: {a, <F>b__2#1}", "C.<>c__DisplayClass1#2_0#2: {<>4__this, a, <F>b__1#2, <F>b__2#2}",
"C.<>c__DisplayClass1#2_0#2: {a, <F>b__2#2}"); "C: {<>c__DisplayClass1#2_0#2, <>c, <>c__DisplayClass1#1_0#1}");
} }
[Fact] [Fact]
......
...@@ -537,50 +537,50 @@ public C(int a, int b) : base(() => a) ...@@ -537,50 +537,50 @@ public C(int a, int b) : base(() => a)
<closure offset=""0"" /> <closure offset=""0"" />
<lambda offset=""-2"" closure=""0"" /> <lambda offset=""-2"" closure=""0"" />
<lambda offset=""41"" closure=""0"" /> <lambda offset=""41"" closure=""0"" />
<lambda offset=""63"" closure=""this"" /> <lambda offset=""63"" closure=""0"" />
<lambda offset=""87"" closure=""1"" /> <lambda offset=""87"" closure=""1"" />
</encLambdaMap> </encLambdaMap>
</customDebugInfo> </customDebugInfo>
<sequencePoints> <sequencePoints>
<entry offset=""0x0"" hidden=""true"" /> <entry offset=""0x0"" hidden=""true"" />
<entry offset=""0x14"" startLine=""13"" startColumn=""30"" endLine=""13"" endColumn=""43"" /> <entry offset=""0x14"" startLine=""13"" startColumn=""30"" endLine=""13"" endColumn=""43"" />
<entry offset=""0x27"" hidden=""true"" /> <entry offset=""0x2e"" hidden=""true"" />
<entry offset=""0x2d"" startLine=""14"" startColumn=""5"" endLine=""14"" endColumn=""6"" /> <entry offset=""0x34"" startLine=""14"" startColumn=""5"" endLine=""14"" endColumn=""6"" />
<entry offset=""0x2e"" startLine=""15"" startColumn=""9"" endLine=""15"" endColumn=""19"" /> <entry offset=""0x35"" startLine=""15"" startColumn=""9"" endLine=""15"" endColumn=""19"" />
<entry offset=""0x35"" startLine=""16"" startColumn=""9"" endLine=""16"" endColumn=""21"" /> <entry offset=""0x3c"" startLine=""16"" startColumn=""9"" endLine=""16"" endColumn=""21"" />
<entry offset=""0x47"" startLine=""17"" startColumn=""9"" endLine=""17"" endColumn=""23"" /> <entry offset=""0x4e"" startLine=""17"" startColumn=""9"" endLine=""17"" endColumn=""23"" />
<entry offset=""0x59"" startLine=""18"" startColumn=""9"" endLine=""18"" endColumn=""21"" /> <entry offset=""0x60"" startLine=""18"" startColumn=""9"" endLine=""18"" endColumn=""21"" />
<entry offset=""0x6b"" startLine=""19"" startColumn=""5"" endLine=""19"" endColumn=""6"" /> <entry offset=""0x72"" startLine=""19"" startColumn=""5"" endLine=""19"" endColumn=""6"" />
</sequencePoints> </sequencePoints>
<scope startOffset=""0x0"" endOffset=""0x6c""> <scope startOffset=""0x0"" endOffset=""0x73"">
<local name=""CS$&lt;&gt;8__locals0"" il_index=""0"" il_start=""0x0"" il_end=""0x6c"" attributes=""0"" /> <local name=""CS$&lt;&gt;8__locals0"" il_index=""0"" il_start=""0x0"" il_end=""0x73"" attributes=""0"" />
<scope startOffset=""0x27"" endOffset=""0x6c""> <scope startOffset=""0x2e"" endOffset=""0x73"">
<local name=""CS$&lt;&gt;8__locals1"" il_index=""1"" il_start=""0x27"" il_end=""0x6c"" attributes=""0"" /> <local name=""CS$&lt;&gt;8__locals1"" il_index=""1"" il_start=""0x2e"" il_end=""0x73"" attributes=""0"" />
</scope> </scope>
</scope> </scope>
</method> </method>
<method containingType=""C"" name=""&lt;.ctor&gt;b__3_2""> <method containingType=""C+&lt;&gt;c__DisplayClass3_0"" name=""&lt;.ctor&gt;b__0"">
<customDebugInfo> <customDebugInfo>
<forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" /> <forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" />
</customDebugInfo> </customDebugInfo>
<sequencePoints> <sequencePoints>
<entry offset=""0x0"" startLine=""17"" startColumn=""19"" endLine=""17"" endColumn=""22"" /> <entry offset=""0x0"" startLine=""13"" startColumn=""41"" endLine=""13"" endColumn=""42"" />
</sequencePoints> </sequencePoints>
</method> </method>
<method containingType=""C+&lt;&gt;c__DisplayClass3_0"" name=""&lt;.ctor&gt;b__0""> <method containingType=""C+&lt;&gt;c__DisplayClass3_0"" name=""&lt;.ctor&gt;b__1"">
<customDebugInfo> <customDebugInfo>
<forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" /> <forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" />
</customDebugInfo> </customDebugInfo>
<sequencePoints> <sequencePoints>
<entry offset=""0x0"" startLine=""13"" startColumn=""41"" endLine=""13"" endColumn=""42"" /> <entry offset=""0x0"" startLine=""16"" startColumn=""19"" endLine=""16"" endColumn=""20"" />
</sequencePoints> </sequencePoints>
</method> </method>
<method containingType=""C+&lt;&gt;c__DisplayClass3_0"" name=""&lt;.ctor&gt;b__1""> <method containingType=""C+&lt;&gt;c__DisplayClass3_0"" name=""&lt;.ctor&gt;b__2"">
<customDebugInfo> <customDebugInfo>
<forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" /> <forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" />
</customDebugInfo> </customDebugInfo>
<sequencePoints> <sequencePoints>
<entry offset=""0x0"" startLine=""16"" startColumn=""19"" endLine=""16"" endColumn=""20"" /> <entry offset=""0x0"" startLine=""17"" startColumn=""19"" endLine=""17"" endColumn=""22"" />
</sequencePoints> </sequencePoints>
</method> </method>
<method containingType=""C+&lt;&gt;c__DisplayClass3_1"" name=""&lt;.ctor&gt;b__3""> <method containingType=""C+&lt;&gt;c__DisplayClass3_1"" name=""&lt;.ctor&gt;b__3"">
......
...@@ -37,6 +37,36 @@ public bool Add(T value) ...@@ -37,6 +37,36 @@ public bool Add(T value)
return true; return true;
} }
public bool Insert(int index, T value)
{
if (_set == null)
{
if (index > 0)
{
throw new IndexOutOfRangeException();
}
Add(value);
}
else
{
if (!_set.Add(value))
{
return false;
}
try
{
_elements.Insert(index, value);
}
catch
{
_set.Remove(value);
throw;
}
}
return true;
}
public bool Remove(T value) public bool Remove(T value)
{ {
if (!_set.Remove(value)) if (!_set.Remove(value))
...@@ -57,5 +87,7 @@ public IEnumerator<T> GetEnumerator() ...@@ -57,5 +87,7 @@ public IEnumerator<T> GetEnumerator()
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public ImmutableArray<T> AsImmutable() => _elements.ToImmutableArrayOrEmpty(); public ImmutableArray<T> AsImmutable() => _elements.ToImmutableArrayOrEmpty();
public T this[int i] => _elements[i];
} }
} }
...@@ -123,7 +123,7 @@ int G() ...@@ -123,7 +123,7 @@ int G()
// Code size 7 (0x7) // Code size 7 (0x7)
.maxstack 1 .maxstack 1
.locals init (int V_0) .locals init (int V_0)
IL_0000: ldarg.0 IL_0000: ldarg.1
IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this"" IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this""
IL_0006: ret IL_0006: ret
}"); }");
...@@ -132,7 +132,7 @@ .locals init (int V_0) ...@@ -132,7 +132,7 @@ .locals init (int V_0)
// Code size 7 (0x7) // Code size 7 (0x7)
.maxstack 1 .maxstack 1
.locals init (int V_0) .locals init (int V_0)
IL_0000: ldarg.0 IL_0000: ldarg.1
IL_0001: ldfld ""int C.<>c__DisplayClass1_0.y"" IL_0001: ldfld ""int C.<>c__DisplayClass1_0.y""
IL_0006: ret IL_0006: ret
}"); }");
...@@ -146,7 +146,7 @@ .locals init (int V_0) ...@@ -146,7 +146,7 @@ .locals init (int V_0)
// Code size 13 (0xd) // Code size 13 (0xd)
.maxstack 2 .maxstack 2
.locals init (int V_0) .locals init (int V_0)
IL_0000: ldarg.0 IL_0000: ldarg.1
IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this"" IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this""
IL_0006: ldc.i4.1 IL_0006: ldc.i4.1
IL_0007: callvirt ""void C.F(int)"" IL_0007: callvirt ""void C.F(int)""
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册