提交 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
public readonly CSharpCompilation Compilation;
public ClosureEnvironment StaticLambdaFrame;
public SynthesizedClosureEnvironment 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(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);
......@@ -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 ClosureEnvironment;
var lambdaFrame = local.Type.OriginalDefinition as SynthesizedClosureEnvironment;
if ((object)lambdaFrame != null)
{
// 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
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)
{
Debug.Assert(symbol != null);
......@@ -132,81 +149,29 @@ public void Free()
}
}
/// <summary>
/// Optimizes local functions such that if a local function only references other local functions
/// that capture no variables, we don't need to create capture environments for any of them.
/// </summary>
private void RemoveUnneededReferences(ParameterSymbol thisParam)
public sealed class ClosureEnvironment
{
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)
{
var current = visitStack.Pop();
var setToAddTo = capturesVariable.Contains(current) ? capturesVariable : capturesThis;
foreach (var capturesCurrent in methodGraph[current])
{
if (setToAddTo.Add(capturesCurrent))
{
visitStack.Push(capturesCurrent);
}
}
}
public readonly SetWithInsertionOrder<Symbol> CapturedVariables;
/// <summary>
/// Represents a <see cref="SynthesizedEnvironment"/> that had its environment
/// pointer (a local pointing to the environment) captured like a captured
/// variable. Assigned in
/// <see cref="ComputeLambdaScopesAndFrameCaptures(ParameterSymbol)"/>
/// </summary>
public bool CapturesParent;
// True if there are any closures in the tree which
// capture 'this' and another variable
bool captureMoreThanThis = false;
public readonly bool IsStruct;
internal SynthesizedClosureEnvironment SynthesizedEnvironment;
VisitClosures(ScopeTree, (scope, closure) =>
public ClosureEnvironment(IEnumerable<Symbol> capturedVariables, bool isStruct)
{
if (!capturesVariable.Contains(closure.OriginalMethodSymbol))
{
closure.CapturedVariables.Clear();
}
if (capturesThis.Contains(closure.OriginalMethodSymbol))
CapturedVariables = new SetWithInsertionOrder<Symbol>();
foreach (var item in capturedVariables)
{
closure.CapturedVariables.Add(thisParam);
if (closure.CapturedVariables.Count > 1)
{
captureMoreThanThis |= true;
}
CapturedVariables.Add(item);
}
});
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);
IsStruct = isStruct;
}
}
......@@ -226,6 +191,31 @@ public static void VisitClosures(Scope scope, Action<Scope, Closure> action)
}
}
/// <summary>
/// Visit all the closures and return true when the <paramref name="func"/> returns
/// true. Otherwise, returns false.
/// </summary>
public static bool CheckClosures(Scope scope, Func<Scope, Closure, bool> func)
{
foreach (var closure in scope.Closures)
{
if (func(scope, closure))
{
return true;
}
}
foreach (var nested in scope.NestedScopes)
{
if (CheckClosures(nested, func))
{
return true;
}
}
return false;
}
/// <summary>
/// Visit the tree with the given root and run the <paramref name="action"/>
/// </summary>
......@@ -491,6 +481,13 @@ private void AddIfCaptured(Symbol symbol, SyntaxNode syntax)
return;
}
if (symbol is MethodSymbol method &&
_currentClosure.OriginalMethodSymbol == method)
{
// Is this recursion? If so there's no capturing
return;
}
if (symbol.ContainingSymbol != _currentClosure.OriginalMethodSymbol)
{
// Restricted types can't be hoisted, so they are not permitted to be captured
......
......@@ -140,7 +140,7 @@ public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody)
_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
// 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 ClosureEnvironment);
Debug.Assert(frameType is SynthesizedClosureEnvironment);
if (frameType.Arity > 0)
{
var typeParameters = ((ClosureEnvironment)frameType).ConstructedFromTypeParameters;
var typeParameters = ((SynthesizedClosureEnvironment)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="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
/// 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 ClosureEnvironment _lazyStaticLambdaFrame;
private SynthesizedClosureEnvironment _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, 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),
// 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)
diagnostics,
assignLocals);
rewriter.SynthesizeClosureEnvironments();
rewriter.SynthesizeClosureEnvironments(closureDebugInfoBuilder);
// First, lower everything but references (calls, delegate conversions)
// to local functions
......@@ -341,10 +341,10 @@ protected override NamedTypeSymbol ContainingType
static partial void CheckLocalsDefined(BoundNode node);
/// <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.
/// </summary>
private void SynthesizeClosureEnvironments()
private void SynthesizeClosureEnvironments(ArrayBuilder<ClosureDebugInfo> closureDebugInfo)
{
Analysis.VisitScopeTree(_analysis.ScopeTree, scope =>
{
......@@ -356,32 +356,59 @@ private void SynthesizeClosureEnvironments()
Debug.Assert(scope.DeclaredEnvironments.Count == 1);
var env = scope.DeclaredEnvironments[0];
var frame = MakeFrame(scope, env.IsStruct);
env.SynthesizedEnvironment = frame;
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(ContainingType, env);
if (env.Constructor != null)
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(ContainingType, frame);
if (frame.Constructor != null)
{
AddSynthesizedMethod(
env.Constructor,
frame.Constructor,
FlowAnalysisPass.AppendImplicitReturn(
MethodCompiler.BindMethodBody(env.Constructor, CompilationState, null),
env.Constructor));
MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null),
frame.Constructor));
}
foreach (var captured in env.CapturedVariables)
{
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));
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(env, hoistedField);
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, hoistedField);
}
_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;
}
return new SynthesizedClosureEnvironment(
_topLevelMethod,
containingMethod,
isStruct,
syntax,
methodId,
closureId);
}
}
private ClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambdaOrFunction lambda)
private SynthesizedClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambdaOrFunction lambda)
{
if (_lazyStaticLambdaFrame == null)
{
......@@ -406,8 +433,7 @@ private ClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, IBoundLambd
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 ClosureEnvironment(
SpecializedCollections.EmptyEnumerable<Symbol>(),
_lazyStaticLambdaFrame = new SynthesizedClosureEnvironment(
_topLevelMethod,
containingMethod,
isStruct: false,
......@@ -530,11 +556,12 @@ private static void InsertAndFreePrologue(ArrayBuilder<BoundStatement> result, A
/// Introduce a frame around the translation of the given node.
/// </summary>
/// <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>
/// <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);
NamedTypeSymbol frameType = frame.ConstructIfGeneric(frameTypeParameters);
......@@ -562,14 +589,7 @@ private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func<
if ((object)_innermostFramePointer != null)
{
proxies.TryGetValue(_innermostFramePointer, out oldInnermostFrameProxy);
if (_analysis.NeedsParentFrame.Contains(node) &&
// 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))
if (env.CapturesParent)
{
var capturedFrame = LambdaCapturedVariable.Create(frame, _innermostFramePointer, ref _synthesizedFieldNameIdDispenser);
FieldSymbol frameParent = capturedFrame.AsMember(frameType);
......@@ -590,7 +610,7 @@ private BoundNode IntroduceFrame(BoundNode node, ClosureEnvironment frame, Func<
// Capture any parameters of this block. This would typically occur
// 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);
}
......@@ -728,7 +748,7 @@ public override BoundNode VisitBaseReference(BoundBaseReference node)
out NamedTypeSymbol constructedFrame)
{
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
Debug.Assert((typeArgumentsOpt.IsDefault && !originalMethod.IsGenericMethod) || (typeArgumentsOpt.Length == originalMethod.Arity));
......@@ -866,9 +886,8 @@ private BoundSequence RewriteSequence(BoundSequence node, ArrayBuilder<BoundExpr
public override BoundNode VisitBlock(BoundBlock node)
{
ClosureEnvironment frame;
// 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) =>
RewriteBlock(node, prologue, newLocals));
......@@ -924,8 +943,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.
ClosureEnvironment frame;
if (_frames.TryGetValue(node, out frame))
if (_frames.TryGetValue(node, out var frame))
{
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{
......@@ -991,9 +1009,8 @@ private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder<BoundExpressio
public override BoundNode VisitSequence(BoundSequence node)
{
ClosureEnvironment frame;
// 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) =>
{
......@@ -1008,10 +1025,9 @@ public override BoundNode VisitSequence(BoundSequence node)
public override BoundNode VisitStatementList(BoundStatementList node)
{
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))
if (_frames.TryGetValue(node, out var frame))
{
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{
......@@ -1034,9 +1050,8 @@ public override BoundNode VisitStatementList(BoundStatementList node)
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
ClosureEnvironment frame;
// 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) =>
{
......@@ -1105,7 +1120,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen
{
ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer;
ClosureEnvironment containerAsFrame;
SynthesizedClosureEnvironment containerAsFrame;
BoundNode lambdaScope;
DebugId topLevelMethodId;
DebugId lambdaId;
......@@ -1178,7 +1193,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
IBoundLambdaOrFunction node,
out ClosureKind closureKind,
out NamedTypeSymbol translatedLambdaContainer,
out ClosureEnvironment containerAsFrame,
out SynthesizedClosureEnvironment containerAsFrame,
out BoundNode lambdaScope,
out DebugId topLevelMethodId,
out DebugId lambdaId)
......@@ -1186,40 +1201,37 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
Analysis.Closure closure = Analysis.GetClosureInTree(_analysis.ScopeTree, node.Symbol);
var structClosures = closure.CapturedEnvironments
.Where(env => env.IsStructType()).AsImmutable();
.Where(env => env.IsStruct).Select(env => env.SynthesizedEnvironment).AsImmutable();
int closureOrdinal;
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;
translatedLambdaContainer = containerAsFrame;
closureOrdinal = containerAsFrame.ClosureOrdinal;
// Find the scope of the containing environment
BoundNode tmpScope = null;
Analysis.VisitScopeTree(_analysis.ScopeTree, scope =>
{
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))
{
if (scope.DeclaredEnvironments.Contains(closure.ContainingEnvironmentOpt))
{
tmpScope = scope.BoundNode;
}
});
Debug.Assert(tmpScope != null);
lambdaScope = tmpScope;
}
tmpScope = scope.BoundNode;
}
});
Debug.Assert(tmpScope != null);
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))
{
......@@ -1238,11 +1250,12 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
}
else
{
lambdaScope = null;
// 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;
closureKind = ClosureKind.ThisOnly;
closureOrdinal = LambdaDebugInfo.ThisOnlyClosureOrdinal;
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
// Move the body of the lambda to a freshly generated synthetic method on its frame.
......@@ -1338,7 +1351,7 @@ private BoundNode RewriteLambdaConversion(BoundLambda node)
ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer;
ClosureEnvironment containerAsFrame;
SynthesizedClosureEnvironment containerAsFrame;
BoundNode lambdaScope;
DebugId topLevelMethodId;
DebugId lambdaId;
......
......@@ -10,10 +10,9 @@
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// A class that represents the set of variables in a scope that have been
/// captured by nested functions within that scope.
/// The synthesized type added to a compilation to hold captured variables for closures.
/// </summary>
internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMethodBodyImplementationSymbol
internal sealed class SynthesizedClosureEnvironment : SynthesizedContainer, ISynthesizedMethodBodyImplementationSymbol
{
private readonly MethodSymbol _topLevelMethod;
internal readonly SyntaxNode ScopeSyntaxOpt;
......@@ -25,13 +24,11 @@ internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMet
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,
internal SynthesizedClosureEnvironment(
MethodSymbol topLevelMethod,
MethodSymbol containingMethod,
bool isStruct,
......@@ -40,11 +37,10 @@ internal sealed class ClosureEnvironment : SynthesizedContainer, ISynthesizedMet
DebugId closureId)
: base(MakeName(scopeSyntaxOpt, methodId, closureId), containingMethod)
{
CapturedVariables = capturedVariables;
TypeKind = isStruct ? TypeKind.Struct : TypeKind.Class;
_topLevelMethod = topLevelMethod;
OriginalContainingMethodOpt = containingMethod;
Constructor = isStruct ? null : new LambdaFrameConstructor(this);
Constructor = isStruct ? null : new SynthesizedClosureEnvironmentConstructor(this);
this.ClosureOrdinal = closureId.Ordinal;
// static lambdas technically have the class scope so the scope syntax is null
......
......@@ -4,9 +4,9 @@
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)
{
}
......
......@@ -19,7 +19,7 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy
internal SynthesizedLambdaMethod(
NamedTypeSymbol containingType,
ImmutableArray<ClosureEnvironment> structEnvironments,
ImmutableArray<SynthesizedClosureEnvironment> structEnvironments,
ClosureKind closureKind,
MethodSymbol topLevelMethod,
DebugId topLevelMethodId,
......@@ -43,9 +43,9 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy
TypeMap typeMap;
ImmutableArray<TypeParameterSymbol> typeParameters;
ImmutableArray<TypeParameterSymbol> constructedFromTypeParameters;
ClosureEnvironment lambdaFrame;
SynthesizedClosureEnvironment lambdaFrame;
lambdaFrame = this.ContainingType as ClosureEnvironment;
lambdaFrame = this.ContainingType as SynthesizedClosureEnvironment;
switch (closureKind)
{
case ClosureKind.Singleton: // all type parameters on method (except the top level method's)
......
......@@ -11,6 +11,53 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
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]
public void StaticClosure01()
{
......@@ -3655,8 +3702,8 @@ .maxstack 2
IL_000d: ldarg.0
IL_000e: call ""bool Program.c1.T()""
IL_0013: brfalse.s IL_002e
IL_0015: ldarg.0
IL_0016: ldftn ""bool Program.c1.<Test>b__1_0(int)""
IL_0015: ldloc.0
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_0021: ldc.i4.s 42
IL_0023: callvirt ""bool System.Func<int, bool>.Invoke(int)""
......
......@@ -30,6 +30,98 @@ public static IMethodSymbol FindLocalFunction(this CompilationVerifier verifier,
[CompilerTrait(CompilerFeature.LocalFunctions)]
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]
[WorkItem(18814, "https://github.com/dotnet/roslyn/issues/18814")]
[WorkItem(18918, "https://github.com/dotnet/roslyn/issues/18918")]
......@@ -78,7 +170,7 @@ void L5()
1");
verifier.VerifyIL("C.M()", @"
{
// Code size 46 (0x2e)
// Code size 47 (0x2f)
.maxstack 2
.locals init (C.<>c__DisplayClass2_0 V_0) //CS$<>8__locals0
IL_0000: ldloca.s V_0
......@@ -90,24 +182,24 @@ .maxstack 2
IL_0010: ldarg.0
IL_0011: ldfld ""int C._x""
IL_0016: call ""void System.Console.WriteLine(int)""
IL_001b: ldloca.s V_0
IL_001d: call ""void C.<M>g__L12_0(ref C.<>c__DisplayClass2_0)""
IL_0022: ldarg.0
IL_0023: ldfld ""int C._x""
IL_0028: call ""void System.Console.WriteLine(int)""
IL_002d: ret
IL_001b: ldarg.0
IL_001c: ldloca.s V_0
IL_001e: call ""void C.<M>g__L12_0(ref C.<>c__DisplayClass2_0)""
IL_0023: ldarg.0
IL_0024: ldfld ""int C._x""
IL_0029: call ""void System.Console.WriteLine(int)""
IL_002e: ret
}");
// L1
verifier.VerifyIL("C.<M>g__L12_0(ref C.<>c__DisplayClass2_0)", @"
{
// Code size 13 (0xd)
// Code size 8 (0x8)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldfld ""C C.<>c__DisplayClass2_0.<>4__this""
IL_0006: ldarg.0
IL_0007: call ""void C.<M>g__L22_1(ref C.<>c__DisplayClass2_0)""
IL_000c: ret
IL_0001: ldarg.1
IL_0002: call ""void C.<M>g__L22_1(ref C.<>c__DisplayClass2_0)""
IL_0007: ret
}");
// L2
verifier.VerifyIL("C.<M>g__L22_1(ref C.<>c__DisplayClass2_0)", @"
......@@ -122,35 +214,34 @@ .maxstack 2
// Skip some... L5
verifier.VerifyIL("C.<M>g__L52_4(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @"
{
// Code size 9 (0x9)
.maxstack 2
// Code size 10 (0xa)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: call ""int C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)""
IL_0007: pop
IL_0008: ret
IL_0002: ldarg.2
IL_0003: call ""int C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)""
IL_0008: pop
IL_0009: ret
}");
// L6
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
.locals init (int V_0)
IL_0000: ldarg.1
IL_0000: ldarg.2
IL_0001: ldfld ""int C.<>c__DisplayClass2_1.var2""
IL_0006: ldarg.1
IL_0007: ldfld ""C C.<>c__DisplayClass2_1.<>4__this""
IL_000c: ldarg.1
IL_000d: ldfld ""C C.<>c__DisplayClass2_1.<>4__this""
IL_0012: ldfld ""int C._x""
IL_0017: stloc.0
IL_0018: ldloc.0
IL_0019: ldc.i4.1
IL_001a: add
IL_001b: stfld ""int C._x""
IL_0020: ldloc.0
IL_0021: add
IL_0022: ret
IL_0006: ldarg.0
IL_0007: ldarg.0
IL_0008: ldfld ""int C._x""
IL_000d: stloc.0
IL_000e: ldloc.0
IL_000f: ldc.i4.1
IL_0010: add
IL_0011: stfld ""int C._x""
IL_0016: ldloc.0
IL_0017: add
IL_0018: ret
}");
}
......
......@@ -1408,8 +1408,8 @@ public int F()
var reader0 = md0.MetadataReader;
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.GetFieldDefNames(), "a", "<>9", "<>9__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(), "<>4__this", "a", "<>9", "<>9__0_0");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference(
......@@ -1420,13 +1420,13 @@ public int F()
var reader1 = diff1.GetMetadata().Reader;
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.GetFieldDefNames(), "a", "<>9__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(), "<>4__this", "a", "<>9__0#1_0#1");
diff1.VerifySynthesizedMembers(
"C: {<F>b__0#1_1#1, <>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: {a, <F>b__2#1}");
"C: {<>c__DisplayClass0#1_0#1, <>c}",
"C.<>c__DisplayClass0#1_0#1: {<>4__this, a, <F>b__1#1, <F>b__2#1}",
"C.<>c: {<>9__0#1_0#1, <F>b__0#1_0#1}");
var diff2 = compilation2.EmitDifference(
diff1.NextGeneration,
......@@ -1436,8 +1436,8 @@ public int F()
var reader2 = diff2.GetMetadata().Reader;
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.GetFieldDefNames(), "a", "<>9__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(), "<>4__this", "a", "<>9__1#2_0#2");
}
[Fact]
......@@ -1525,8 +1525,8 @@ public int F<T>()
var reader0 = md0.MetadataReader;
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.GetFieldDefNames(), "a", "<>9", "<>9__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(), "<>4__this", "a", "<>9", "<>9__0_0");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference(
......@@ -1537,13 +1537,13 @@ public int F<T>()
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.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.GetFieldDefNames(), "a", "<>9", "<>9__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(), "<>4__this", "a", "<>9", "<>9__0#1_0#1");
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__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(
diff1.NextGeneration,
......@@ -1553,8 +1553,8 @@ public int F<T>()
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.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.GetFieldDefNames(), "a", "<>9", "<>9__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(), "<>4__this", "a", "<>9", "<>9__1#2_0#2");
}
[Fact]
......@@ -1669,9 +1669,9 @@ public int F()
new SemanticEdit(SemanticEditKind.Update, main0, main1, preserveLocalVariables: true)));
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__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(
diff1.NextGeneration,
......@@ -1680,10 +1680,10 @@ public int F()
new SemanticEdit(SemanticEditKind.Update, main1, main2, preserveLocalVariables: true)));
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__DisplayClass1#1_0#1: {a, <F>b__2#1}",
"C.<>c__DisplayClass1#2_0#2: {a, <F>b__2#2}");
"C.<>c__DisplayClass1#1_0#1: {<>4__this, a, <F>b__1#1, <F>b__2#1}");
var diff3 = compilation3.EmitDifference(
diff2.NextGeneration,
......@@ -1691,10 +1691,10 @@ public int F()
new SemanticEdit(SemanticEditKind.Update, main2, main3, preserveLocalVariables: true)));
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__DisplayClass1#1_0#1: {a, <F>b__2#1}",
"C.<>c__DisplayClass1#2_0#2: {a, <F>b__2#2}");
"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}");
}
[Fact]
......
......@@ -537,50 +537,50 @@ public C(int a, int b) : base(() => a)
<closure offset=""0"" />
<lambda offset=""-2"" closure=""0"" />
<lambda offset=""41"" closure=""0"" />
<lambda offset=""63"" closure=""this"" />
<lambda offset=""63"" closure=""0"" />
<lambda offset=""87"" closure=""1"" />
</encLambdaMap>
</customDebugInfo>
<sequencePoints>
<entry offset=""0x0"" hidden=""true"" />
<entry offset=""0x14"" startLine=""13"" startColumn=""30"" endLine=""13"" endColumn=""43"" />
<entry offset=""0x27"" hidden=""true"" />
<entry offset=""0x2d"" startLine=""14"" startColumn=""5"" endLine=""14"" endColumn=""6"" />
<entry offset=""0x2e"" startLine=""15"" startColumn=""9"" endLine=""15"" endColumn=""19"" />
<entry offset=""0x35"" startLine=""16"" startColumn=""9"" endLine=""16"" endColumn=""21"" />
<entry offset=""0x47"" startLine=""17"" startColumn=""9"" endLine=""17"" endColumn=""23"" />
<entry offset=""0x59"" startLine=""18"" startColumn=""9"" endLine=""18"" endColumn=""21"" />
<entry offset=""0x6b"" startLine=""19"" startColumn=""5"" endLine=""19"" endColumn=""6"" />
<entry offset=""0x2e"" hidden=""true"" />
<entry offset=""0x34"" startLine=""14"" startColumn=""5"" endLine=""14"" endColumn=""6"" />
<entry offset=""0x35"" startLine=""15"" startColumn=""9"" endLine=""15"" endColumn=""19"" />
<entry offset=""0x3c"" startLine=""16"" startColumn=""9"" endLine=""16"" endColumn=""21"" />
<entry offset=""0x4e"" startLine=""17"" startColumn=""9"" endLine=""17"" endColumn=""23"" />
<entry offset=""0x60"" startLine=""18"" startColumn=""9"" endLine=""18"" endColumn=""21"" />
<entry offset=""0x72"" startLine=""19"" startColumn=""5"" endLine=""19"" endColumn=""6"" />
</sequencePoints>
<scope startOffset=""0x0"" endOffset=""0x6c"">
<local name=""CS$&lt;&gt;8__locals0"" il_index=""0"" il_start=""0x0"" il_end=""0x6c"" attributes=""0"" />
<scope startOffset=""0x27"" endOffset=""0x6c"">
<local name=""CS$&lt;&gt;8__locals1"" il_index=""1"" il_start=""0x27"" il_end=""0x6c"" attributes=""0"" />
<scope startOffset=""0x0"" endOffset=""0x73"">
<local name=""CS$&lt;&gt;8__locals0"" il_index=""0"" il_start=""0x0"" il_end=""0x73"" attributes=""0"" />
<scope startOffset=""0x2e"" endOffset=""0x73"">
<local name=""CS$&lt;&gt;8__locals1"" il_index=""1"" il_start=""0x2e"" il_end=""0x73"" attributes=""0"" />
</scope>
</scope>
</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>
<forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" />
</customDebugInfo>
<sequencePoints>
<entry offset=""0x0"" startLine=""17"" startColumn=""19"" endLine=""17"" endColumn=""22"" />
<entry offset=""0x0"" startLine=""13"" startColumn=""41"" endLine=""13"" endColumn=""42"" />
</sequencePoints>
</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>
<forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" />
</customDebugInfo>
<sequencePoints>
<entry offset=""0x0"" startLine=""13"" startColumn=""41"" endLine=""13"" endColumn=""42"" />
<entry offset=""0x0"" startLine=""16"" startColumn=""19"" endLine=""16"" endColumn=""20"" />
</sequencePoints>
</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>
<forward declaringType=""B"" methodName="".ctor"" parameterNames=""f"" />
</customDebugInfo>
<sequencePoints>
<entry offset=""0x0"" startLine=""16"" startColumn=""19"" endLine=""16"" endColumn=""20"" />
<entry offset=""0x0"" startLine=""17"" startColumn=""19"" endLine=""17"" endColumn=""22"" />
</sequencePoints>
</method>
<method containingType=""C+&lt;&gt;c__DisplayClass3_1"" name=""&lt;.ctor&gt;b__3"">
......
......@@ -37,6 +37,36 @@ public bool Add(T value)
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)
{
if (!_set.Remove(value))
......@@ -57,5 +87,7 @@ public IEnumerator<T> GetEnumerator()
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public ImmutableArray<T> AsImmutable() => _elements.ToImmutableArrayOrEmpty();
public T this[int i] => _elements[i];
}
}
......@@ -123,7 +123,7 @@ int G()
// Code size 7 (0x7)
.maxstack 1
.locals init (int V_0)
IL_0000: ldarg.0
IL_0000: ldarg.1
IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this""
IL_0006: ret
}");
......@@ -132,7 +132,7 @@ .locals init (int V_0)
// Code size 7 (0x7)
.maxstack 1
.locals init (int V_0)
IL_0000: ldarg.0
IL_0000: ldarg.1
IL_0001: ldfld ""int C.<>c__DisplayClass1_0.y""
IL_0006: ret
}");
......@@ -146,7 +146,7 @@ .locals init (int V_0)
// Code size 13 (0xd)
.maxstack 2
.locals init (int V_0)
IL_0000: ldarg.0
IL_0000: ldarg.1
IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this""
IL_0006: ldc.i4.1
IL_0007: callvirt ""void C.F(int)""
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册