|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
#if DEBUG
//#define CHECK_LOCALS // define CHECK_LOCALS to help debug some rewriting problems that would otherwise cause code-gen failures
#endif
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// The rewriter for removing lambda expressions from method bodies and introducing closure classes
/// as containers for captured variables along the lines of the example in section 6.5.3 of the
/// C# language specification. A closure is the lowered form of a nested function, consisting of a
/// synthesized method and a set of environments containing the captured variables.
///
/// The entry point is the public method <see cref="Rewrite"/>. It operates as follows:
///
/// First, an analysis of the whole method body is performed that determines which variables are
/// captured, what their scopes are, and what the nesting relationship is between scopes that
/// 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="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"/>.
///
/// Next, we walk and rewrite the input bound tree, keeping track of the following:
/// (1) The current set of active frame pointers, in <see cref="_framePointers"/>
/// (2) The current method being processed (this changes within a lambda's body), in <see cref="_currentMethod"/>
/// (3) The "this" symbol for the current method in <see cref="_currentFrameThis"/>, and
/// (4) The symbol that is used to access the innermost frame pointer (it could be a local variable or "this" parameter)
///
/// Lastly, we visit the top-level method and each of the lowered methods
/// to rewrite references (e.g., calls and delegate conversions) to local
/// functions. We visit references to local functions separately from
/// lambdas because we may see the reference before we lower the target
/// local function. Lambdas, on the other hand, are always convertible as
/// they are being lowered.
///
/// There are a few key transformations done in the rewriting.
/// (1) Lambda expressions are turned into delegate creation expressions, and the body of the lambda is
/// moved into a new, compiler-generated method of a selected frame class.
/// (2) On entry to a scope with captured variables, we create a frame object and store it in a local variable.
/// (3) References to captured variables are transformed into references to fields of a frame class.
///
/// In addition, the rewriting deposits into <see cref="TypeCompilationState.SynthesizedMethods"/>
/// a (<see cref="MethodSymbol"/>, <see cref="BoundStatement"/>) pair for each generated method.
///
/// <see cref="Rewrite"/> produces its output in two forms. First, it returns a new bound statement
/// for the caller to use for the body of the original method. Second, it returns a collection of
/// (<see cref="MethodSymbol"/>, <see cref="BoundStatement"/>) pairs for additional methods that the lambda rewriter produced.
/// These additional methods contain the bodies of the lambdas moved into ordinary methods of their
/// respective frame classes, and the caller is responsible for processing them just as it does with
/// the returned bound node. For example, the caller will typically perform iterator method and
/// asynchronous method transformations, and emit IL instructions into an assembly.
/// </summary>
internal sealed partial class ClosureConversion : MethodToClassRewriter
{
private readonly Analysis _analysis;
private readonly MethodSymbol _topLevelMethod;
private readonly MethodSymbol _substitutedSourceMethod;
private readonly int _topLevelMethodOrdinal;
// lambda frame for static lambdas.
// initialized lazily and could be null if there are no static lambdas
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>();
// for each block with lifted (captured) variables, the corresponding frame type
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.
private readonly Dictionary<NamedTypeSymbol, Symbol> _framePointers = new Dictionary<NamedTypeSymbol, Symbol>();
// The set of original locals that should be assigned to proxies
// if lifted. This is useful for the expression evaluator where
// the original locals are left as is.
private readonly HashSet<LocalSymbol> _assignLocals;
// The current method or lambda being processed.
private MethodSymbol _currentMethod;
// The "this" symbol for the current method.
private ParameterSymbol _currentFrameThis;
private readonly ArrayBuilder<EncLambdaInfo> _lambdaDebugInfoBuilder;
private readonly ArrayBuilder<LambdaRuntimeRudeEditInfo> _lambdaRuntimeRudeEditsBuilder;
// ID dispenser for field names of frame references
private int _synthesizedFieldNameIdDispenser;
// The symbol (field or local) holding the innermost frame
private Symbol _innermostFramePointer;
// The mapping of type parameters for the current lambda body
private TypeMap _currentLambdaBodyTypeMap;
// The current set of type parameters (mapped from the enclosing method's type parameters)
private ImmutableArray<TypeParameterSymbol> _currentTypeParameters;
// Initialization for the proxy of the upper frame if it needs to be deferred.
// Such situation happens when lifting this in a ctor.
private BoundExpression _thisProxyInitDeferred;
// Set to true once we've seen the base (or self) constructor invocation in a constructor
private bool _seenBaseCall;
// Set to true while translating code inside of an expression lambda.
private bool _inExpressionLambda;
// When a lambda captures only 'this' of the enclosing method, we cache it in a local
// variable. This is the set of such local variables that must be added to the enclosing
// method's top-level block.
private ArrayBuilder<LocalSymbol> _addedLocals;
// Similarly, this is the set of statements that must be added to the enclosing method's
// top-level block initializing those variables to null.
private ArrayBuilder<BoundStatement> _addedStatements;
/// <summary>
/// Temporary bag for methods synthesized by the rewriting. Added to
/// <see cref="TypeCompilationState.SynthesizedMethods"/> at the end of rewriting.
/// </summary>
private ArrayBuilder<TypeCompilationState.MethodWithBody> _synthesizedMethods;
/// <summary>
/// TODO(https://github.com/dotnet/roslyn/projects/26): Delete this.
/// This should only be used by <see cref="NeedsProxy(Symbol)"/> which
/// hasn't had logic to move the proxy analysis into <see cref="Analysis"/>,
/// where the <see cref="Analysis.ScopeTree"/> could be walked to build
/// the proxy list.
/// </summary>
private readonly ImmutableHashSet<Symbol> _allCapturedVariables;
#nullable enable
private ClosureConversion(
Analysis analysis,
NamedTypeSymbol thisType,
ParameterSymbol? thisParameter,
MethodSymbol method,
int methodOrdinal,
MethodSymbol substitutedSourceMethod,
ArrayBuilder<EncLambdaInfo> lambdaDebugInfoBuilder,
ArrayBuilder<LambdaRuntimeRudeEditInfo> lambdaRuntimeRudeEditsBuilder,
VariableSlotAllocator? slotAllocator,
TypeCompilationState compilationState,
BindingDiagnosticBag diagnostics,
HashSet<LocalSymbol> assignLocals)
: base(slotAllocator, compilationState, diagnostics)
{
RoslynDebug.Assert(analysis != null);
RoslynDebug.Assert((object)thisType != null);
RoslynDebug.Assert(method != null);
RoslynDebug.Assert(compilationState != null);
RoslynDebug.Assert(diagnostics != null);
_topLevelMethod = method;
_substitutedSourceMethod = substitutedSourceMethod;
_topLevelMethodOrdinal = methodOrdinal;
_lambdaDebugInfoBuilder = lambdaDebugInfoBuilder;
_lambdaRuntimeRudeEditsBuilder = lambdaRuntimeRudeEditsBuilder;
_currentMethod = method;
_analysis = analysis;
_assignLocals = assignLocals;
_currentTypeParameters = method.TypeParameters;
_currentLambdaBodyTypeMap = TypeMap.Empty;
_innermostFramePointer = _currentFrameThis = thisParameter;
_framePointers[thisType] = thisParameter;
_seenBaseCall = method.MethodKind != MethodKind.Constructor; // only used for ctors
_synthesizedFieldNameIdDispenser = 1;
var allCapturedVars = ImmutableHashSet.CreateBuilder<Symbol>();
Analysis.VisitNestedFunctions(analysis.ScopeTree, (scope, function) =>
{
allCapturedVars.UnionWith(function.CapturedVariables);
});
_allCapturedVariables = allCapturedVars.ToImmutable();
}
protected override bool NeedsProxy(Symbol localOrParameter)
{
Debug.Assert(localOrParameter is LocalSymbol || localOrParameter is ParameterSymbol ||
(localOrParameter as MethodSymbol)?.MethodKind == MethodKind.LocalFunction);
return _allCapturedVariables.Contains(localOrParameter);
}
/// <summary>
/// Rewrite the given node to eliminate lambda expressions. Also returned are the method symbols and their
/// bound bodies for the extracted lambda bodies. These would typically be emitted by the caller such as
/// MethodBodyCompiler. See this class' documentation
/// for a more thorough explanation of the algorithm and its use by clients.
/// </summary>
/// <param name="loweredBody">The bound node to be rewritten</param>
/// <param name="thisType">The type of the top-most frame</param>
/// <param name="thisParameter">The "this" parameter in the top-most frame, or null if static method</param>
/// <param name="method">The containing method of the node to be rewritten</param>
/// <param name="methodOrdinal">Index of the method symbol in its containing type member list.</param>
/// <param name="substitutedSourceMethod">If this is non-null, then <paramref name="method"/> will be treated as this for uses of parent symbols. For use in EE.</param>
/// <param name="lambdaDebugInfoBuilder">Information on lambdas defined in <paramref name="method"/> needed for debugging.</param>
/// <param name="lambdaRuntimeRudeEditsBuilder">EnC rude edit information on lambdas defined in <paramref name="method"/>.</param>
/// <param name="closureDebugInfoBuilder">Information on closures defined in <paramref name="method"/> needed for debugging.</param>
/// <param name="slotAllocator">Slot allocator.</param>
/// <param name="compilationState">The caller's buffer into which we produce additional methods to be emitted by the caller</param>
/// <param name="diagnostics">Diagnostic bag for diagnostics</param>
/// <param name="assignLocals">The set of original locals that should be assigned to proxies if lifted</param>
public static BoundStatement Rewrite(
BoundStatement loweredBody,
NamedTypeSymbol thisType,
ParameterSymbol? thisParameter,
MethodSymbol method,
int methodOrdinal,
MethodSymbol substitutedSourceMethod,
ArrayBuilder<EncLambdaInfo> lambdaDebugInfoBuilder,
ArrayBuilder<LambdaRuntimeRudeEditInfo> lambdaRuntimeRudeEditsBuilder,
ArrayBuilder<EncClosureInfo> closureDebugInfoBuilder,
VariableSlotAllocator? slotAllocator,
TypeCompilationState compilationState,
BindingDiagnosticBag diagnostics,
HashSet<LocalSymbol> assignLocals)
{
Debug.Assert(thisType is not null);
Debug.Assert(thisParameter is null || TypeSymbol.Equals(thisParameter.Type, thisType, TypeCompareKind.ConsiderEverything2));
Debug.Assert(compilationState.ModuleBuilderOpt != null);
Debug.Assert(diagnostics.DiagnosticBag != null);
var analysis = Analysis.Analyze(
loweredBody,
method,
methodOrdinal,
slotAllocator,
compilationState,
diagnostics.DiagnosticBag);
CheckLocalsDefined(loweredBody);
var rewriter = new ClosureConversion(
analysis,
thisType,
thisParameter,
method,
methodOrdinal,
substitutedSourceMethod,
lambdaDebugInfoBuilder,
lambdaRuntimeRudeEditsBuilder,
slotAllocator,
compilationState,
diagnostics,
assignLocals);
rewriter.SynthesizeClosureEnvironments(closureDebugInfoBuilder);
rewriter.SynthesizeClosureMethods();
var body = rewriter.AddStatementsIfNeeded(
(BoundStatement)rewriter.Visit(loweredBody));
// Add the completed methods to the compilation state
if (rewriter._synthesizedMethods != null)
{
if (compilationState.SynthesizedMethods == null)
{
compilationState.SynthesizedMethods = rewriter._synthesizedMethods;
}
else
{
compilationState.SynthesizedMethods.AddRange(rewriter._synthesizedMethods);
rewriter._synthesizedMethods.Free();
}
}
CheckLocalsDefined(body);
analysis.Free();
return body;
}
#nullable disable
private BoundStatement AddStatementsIfNeeded(BoundStatement body)
{
if (_addedLocals != null)
{
_addedStatements.Add(body);
body = new BoundBlock(body.Syntax, _addedLocals.ToImmutableAndFree(), _addedStatements.ToImmutableAndFree()) { WasCompilerGenerated = true };
_addedLocals = null;
_addedStatements = null;
}
else
{
Debug.Assert(_addedStatements == null);
}
return body;
}
protected override TypeMap TypeMap
{
get { return _currentLambdaBodyTypeMap; }
}
protected override MethodSymbol CurrentMethod
{
get { return _currentMethod; }
}
protected override NamedTypeSymbol ContainingType
{
get { return _topLevelMethod.ContainingType; }
}
/// <summary>
/// Check that the top-level node is well-defined, in the sense that all
/// locals that are used are defined in some enclosing scope.
/// </summary>
static partial void CheckLocalsDefined(BoundNode node);
/// <summary>
/// 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(ArrayBuilder<EncClosureInfo> closureDebugInfo)
{
Analysis.VisitScopeTree(_analysis.ScopeTree, scope =>
{
if (scope.DeclaredEnvironment is { } env)
{
Debug.Assert(!_frames.ContainsKey(scope.BoundNode));
var frame = MakeFrame(scope, env);
env.SynthesizedEnvironment = frame;
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(ContainingType, frame.GetCciAdapter());
if (frame.Constructor != null)
{
AddSynthesizedMethod(
frame.Constructor,
FlowAnalysisPass.AppendImplicitReturn(
MethodCompiler.BindSynthesizedMethodBody(frame.Constructor, CompilationState, Diagnostics),
frame.Constructor));
}
_frames.Add(scope.BoundNode, env);
}
});
SynthesizedClosureEnvironment MakeFrame(Analysis.Scope scope, Analysis.ClosureEnvironment env)
{
var scopeBoundNode = scope.BoundNode;
var syntax = scopeBoundNode.Syntax;
Debug.Assert(syntax != null);
DebugId methodId = _analysis.GetTopLevelMethodId();
DebugId closureId = _analysis.GetClosureId(env, syntax, closureDebugInfo, out var rudeEdit);
var containingMethod = scope.ContainingFunctionOpt?.OriginalMethodSymbol ?? _topLevelMethod;
if ((object)_substitutedSourceMethod != null && containingMethod == _topLevelMethod)
{
containingMethod = _substitutedSourceMethod;
}
var synthesizedEnv = new SynthesizedClosureEnvironment(
_topLevelMethod,
containingMethod,
env.IsStruct,
syntax,
methodId,
closureId,
rudeEdit);
foreach (var captured in env.CapturedVariables)
{
Debug.Assert(!proxies.ContainsKey(captured));
var hoistedField = LambdaCapturedVariable.Create(synthesizedEnv, captured, ref _synthesizedFieldNameIdDispenser);
proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false));
synthesizedEnv.AddHoistedField(hoistedField);
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(synthesizedEnv, hoistedField.GetCciAdapter());
}
return synthesizedEnv;
}
}
/// <summary>
/// Synthesize closure methods for all nested functions.
/// </summary>
private void SynthesizeClosureMethods()
{
Analysis.VisitNestedFunctions(_analysis.ScopeTree, (scope, nestedFunction) =>
{
var originalMethod = nestedFunction.OriginalMethodSymbol;
var syntax = originalMethod.DeclaringSyntaxReferences[0].GetSyntax();
int closureOrdinal;
ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer;
SynthesizedClosureEnvironment containerAsFrame;
DebugId topLevelMethodId;
DebugId lambdaId;
if (nestedFunction.ContainingEnvironmentOpt != null)
{
containerAsFrame = nestedFunction.ContainingEnvironmentOpt.SynthesizedEnvironment;
translatedLambdaContainer = containerAsFrame;
closureKind = ClosureKind.General;
closureOrdinal = containerAsFrame.ClosureId.Ordinal;
}
else if (nestedFunction.CapturesThis)
{
containerAsFrame = null;
translatedLambdaContainer = _topLevelMethod.ContainingType;
closureKind = ClosureKind.ThisOnly;
closureOrdinal = LambdaDebugInfo.ThisOnlyClosureOrdinal;
}
else if ((nestedFunction.CapturedEnvironments.Count == 0 &&
originalMethod.MethodKind == MethodKind.LambdaMethod &&
_analysis.MethodsConvertedToDelegates.Contains(originalMethod)) ||
// If we are in a variant interface, runtime might not consider the
// method synthesized directly within the interface as variant safe.
// For simplicity we do not perform precise analysis whether this would
// definitely be the case. If we are in a variant interface, we always force
// creation of a display class.
VarianceSafety.GetEnclosingVariantInterface(_topLevelMethod) is object)
{
translatedLambdaContainer = containerAsFrame = GetStaticFrame(Diagnostics, syntax);
closureKind = ClosureKind.Singleton;
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
else
{
// Lower directly onto the containing type
containerAsFrame = null;
translatedLambdaContainer = _topLevelMethod.ContainingType;
closureKind = ClosureKind.Static;
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
Debug.Assert((object)translatedLambdaContainer != _topLevelMethod.ContainingType ||
VarianceSafety.GetEnclosingVariantInterface(_topLevelMethod) is null);
var structEnvironments = getStructEnvironments(nestedFunction);
// Move the body of the lambda to a freshly generated synthetic method on its frame.
topLevelMethodId = _analysis.GetTopLevelMethodId();
lambdaId = GetLambdaId(syntax, closureKind, closureOrdinal, structEnvironments.SelectAsArray(e => e.ClosureId), containerAsFrame?.RudeEdit);
var synthesizedMethod = new SynthesizedClosureMethod(
translatedLambdaContainer,
structEnvironments,
closureKind,
_topLevelMethod,
topLevelMethodId,
originalMethod,
nestedFunction.BlockSyntax,
lambdaId,
CompilationState);
nestedFunction.SynthesizedLoweredMethod = synthesizedMethod;
});
static ImmutableArray<SynthesizedClosureEnvironment> getStructEnvironments(Analysis.NestedFunction function)
{
var environments = ArrayBuilder<SynthesizedClosureEnvironment>.GetInstance();
foreach (var env in function.CapturedEnvironments)
{
if (env.IsStruct)
{
environments.Add(env.SynthesizedEnvironment);
}
}
return environments.ToImmutableAndFree();
}
}
/// <summary>
/// Get the static container for closures or create one if one doesn't already exist.
/// </summary>
/// <param name="syntax">
/// associate the frame with the first lambda that caused it to exist.
/// we need to associate this with some syntax.
/// unfortunately either containing method or containing class could be synthetic
/// therefore could have no syntax.
/// </param>
private SynthesizedClosureEnvironment GetStaticFrame(BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
if ((object)_lazyStaticLambdaFrame == null)
{
var isNonGeneric = !_topLevelMethod.IsGenericMethod;
if (isNonGeneric)
{
_lazyStaticLambdaFrame = CompilationState.StaticLambdaFrame;
}
if ((object)_lazyStaticLambdaFrame == null)
{
DebugId methodId;
if (isNonGeneric)
{
methodId = new DebugId(DebugId.UndefinedOrdinal, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal);
}
else
{
methodId = _analysis.GetTopLevelMethodId();
}
// 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 SynthesizedClosureEnvironment(
_topLevelMethod,
containingMethod,
isStruct: false,
scopeSyntaxOpt: null,
methodId: methodId,
closureId: default,
rudeEdit: null);
// non-generic static lambdas can share the frame
if (isNonGeneric)
{
CompilationState.StaticLambdaFrame = _lazyStaticLambdaFrame;
}
var frame = _lazyStaticLambdaFrame;
// add frame type and cache field
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(this.ContainingType, frame.GetCciAdapter());
// add its ctor (note Constructor can be null if TypeKind.Struct is passed in to LambdaFrame.ctor, but Class is passed in above)
AddSynthesizedMethod(
frame.Constructor,
FlowAnalysisPass.AppendImplicitReturn(
MethodCompiler.BindSynthesizedMethodBody(frame.Constructor, CompilationState, diagnostics),
frame.Constructor));
// add cctor
// Frame.inst = new Frame()
var F = new SyntheticBoundNodeFactory(frame.StaticConstructor, syntax, CompilationState, diagnostics);
var body = F.Block(
F.Assignment(
F.Field(null, frame.SingletonCache),
F.New(frame.Constructor)),
new BoundReturnStatement(syntax, RefKind.None, null, @checked: false));
AddSynthesizedMethod(frame.StaticConstructor, body);
}
}
return _lazyStaticLambdaFrame;
}
/// <summary>
/// Produce a bound expression representing a pointer to a frame of a particular frame type.
/// </summary>
/// <param name="syntax">The syntax to attach to the bound nodes produced</param>
/// <param name="frameType">The type of frame to be returned</param>
/// <returns>A bound node that computes the pointer to the required frame</returns>
private BoundExpression FrameOfType(SyntaxNode syntax, NamedTypeSymbol frameType)
{
BoundExpression result = FramePointer(syntax, frameType.OriginalDefinition);
Debug.Assert(TypeSymbol.Equals(result.Type, frameType, TypeCompareKind.ConsiderEverything2));
return result;
}
/// <summary>
/// Produce a bound expression representing a pointer to a frame of a particular frame class.
/// Note that for generic frames, the frameClass parameter is the generic definition, but
/// the resulting expression will be constructed with the current type parameters.
/// </summary>
/// <param name="syntax">The syntax to attach to the bound nodes produced</param>
/// <param name="frameClass">The class type of frame to be returned</param>
/// <returns>A bound node that computes the pointer to the required frame</returns>
protected override BoundExpression FramePointer(SyntaxNode syntax, NamedTypeSymbol frameClass)
{
Debug.Assert(frameClass.IsDefinition);
// If in an instance method of the right type, we can just return the "this" pointer.
if ((object)_currentFrameThis != null && TypeSymbol.Equals(_currentFrameThis.Type, frameClass, TypeCompareKind.ConsiderEverything2))
{
return new BoundThisReference(syntax, frameClass);
}
// If the current method has by-ref struct closure parameters, and one of them is correct, use it.
var lambda = _currentMethod as SynthesizedClosureMethod;
if (lambda != null)
{
var start = lambda.ParameterCount - lambda.ExtraSynthesizedParameterCount;
for (var i = start; i < lambda.ParameterCount; i++)
{
var potentialParameter = lambda.Parameters[i];
if (TypeSymbol.Equals(potentialParameter.Type.OriginalDefinition, frameClass, TypeCompareKind.ConsiderEverything2))
{
return new BoundParameter(syntax, potentialParameter);
}
}
}
// Otherwise we need to return the value from a frame pointer local variable...
Symbol framePointer = _framePointers[frameClass];
CapturedSymbolReplacement proxyField;
if (proxies.TryGetValue(framePointer, out proxyField))
{
// However, frame pointer local variables themselves can be "captured". In that case
// the inner frames contain pointers to the enclosing frames. That is, nested
// frame pointers are organized in a linked list.
return proxyField.Replacement(
syntax,
static (frameType, arg) => arg.self.FramePointer(arg.syntax, frameType),
(syntax, self: this));
}
var localFrame = (LocalSymbol)framePointer;
return new BoundLocal(syntax, localFrame, null, localFrame.Type);
}
private static void InsertAndFreePrologue<T>(ArrayBuilder<BoundStatement> result, ArrayBuilder<T> prologue) where T : BoundNode
{
foreach (var node in prologue)
{
if (node is BoundStatement stmt)
{
result.Add(stmt);
}
else
{
result.Add(new BoundExpressionStatement(node.Syntax, (BoundExpression)(BoundNode)node));
}
}
prologue.Free();
}
/// <summary>
/// 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="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, Analysis.ClosureEnvironment env, Func<ArrayBuilder<BoundExpression>, ArrayBuilder<LocalSymbol>, BoundNode> F)
{
var frame = env.SynthesizedEnvironment;
var frameTypeParameters = ImmutableArray.Create(_currentTypeParameters.SelectAsArray(t => TypeWithAnnotations.Create(t)), 0, frame.Arity);
NamedTypeSymbol frameType = frame.ConstructIfGeneric(frameTypeParameters);
Debug.Assert(frame.ScopeSyntaxOpt != null);
LocalSymbol framePointer = new SynthesizedLocal(_topLevelMethod, TypeWithAnnotations.Create(frameType), SynthesizedLocalKind.LambdaDisplayClass, frame.ScopeSyntaxOpt);
SyntaxNode syntax = node.Syntax;
// assign new frame to the frame variable
var prologue = ArrayBuilder<BoundExpression>.GetInstance();
if ((object)frame.Constructor != null)
{
MethodSymbol constructor = frame.Constructor.AsMember(frameType);
Debug.Assert(TypeSymbol.Equals(frameType, constructor.ContainingType, TypeCompareKind.ConsiderEverything2));
prologue.Add(new BoundAssignmentOperator(syntax,
new BoundLocal(syntax, framePointer, null, frameType),
new BoundObjectCreationExpression(syntax: syntax, constructor: constructor),
frameType));
}
CapturedSymbolReplacement oldInnermostFrameProxy = null;
if ((object)_innermostFramePointer != null)
{
proxies.TryGetValue(_innermostFramePointer, out oldInnermostFrameProxy);
if (env.CapturesParent)
{
var capturedFrame = LambdaCapturedVariable.Create(frame, _innermostFramePointer, ref _synthesizedFieldNameIdDispenser);
FieldSymbol frameParent = capturedFrame.AsMember(frameType);
BoundExpression left = new BoundFieldAccess(syntax, new BoundLocal(syntax, framePointer, null, frameType), frameParent, null);
BoundExpression right = FrameOfType(syntax, frameParent.Type as NamedTypeSymbol);
BoundExpression assignment = new BoundAssignmentOperator(syntax, left, right, left.Type);
prologue.Add(assignment);
if (CompilationState.Emitting)
{
Debug.Assert(capturedFrame.Type.IsReferenceType); // Make sure we're not accidentally capturing a struct by value
frame.AddHoistedField(capturedFrame);
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, capturedFrame.GetCciAdapter());
}
proxies[_innermostFramePointer] = new CapturedToFrameSymbolReplacement(capturedFrame, isReusable: false);
}
}
// 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 env.CapturedVariables)
{
InitVariableProxy(syntax, variable, framePointer, prologue);
}
Symbol oldInnermostFramePointer = _innermostFramePointer;
if (!framePointer.Type.IsValueType)
{
_innermostFramePointer = framePointer;
}
var addedLocals = ArrayBuilder<LocalSymbol>.GetInstance();
addedLocals.Add(framePointer);
_framePointers.Add(frame, framePointer);
var result = F(prologue, addedLocals);
_innermostFramePointer = oldInnermostFramePointer;
if ((object)_innermostFramePointer != null)
{
if (oldInnermostFrameProxy != null)
{
proxies[_innermostFramePointer] = oldInnermostFrameProxy;
}
else
{
proxies.Remove(_innermostFramePointer);
}
}
return result;
}
private void InitVariableProxy(SyntaxNode syntax, Symbol symbol, LocalSymbol framePointer, ArrayBuilder<BoundExpression> prologue)
{
CapturedSymbolReplacement proxy;
if (proxies.TryGetValue(symbol, out proxy))
{
BoundExpression value;
switch (symbol.Kind)
{
case SymbolKind.Parameter:
var parameter = (ParameterSymbol)symbol;
ParameterSymbol parameterToUse;
if (!_parameterMap.TryGetValue(parameter, out parameterToUse))
{
parameterToUse = parameter;
}
value = new BoundParameter(syntax, parameterToUse);
break;
case SymbolKind.Local:
var local = (LocalSymbol)symbol;
if (_assignLocals == null || !_assignLocals.Contains(local))
{
return;
}
LocalSymbol localToUse;
if (!localMap.TryGetValue(local, out localToUse))
{
localToUse = local;
}
value = new BoundLocal(syntax, localToUse, null, localToUse.Type);
break;
default:
throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
}
var left = proxy.Replacement(
syntax,
static (frameType1, arg) => new BoundLocal(arg.syntax, arg.framePointer, null, arg.framePointer.Type),
(syntax, framePointer));
var assignToProxy = new BoundAssignmentOperator(syntax, left, value, value.Type);
if (_currentMethod.MethodKind == MethodKind.Constructor &&
symbol == _currentMethod.ThisParameter &&
!_seenBaseCall &&
// Primary constructor doesn't have any user code after base constructor initializer.
// Therefore, if we detected a proxy for 'this', it must be used to refer in a lambda
// to a constructor parameter captured into the containing type state.
// That lambda could be executed before the base constructor initializer, or by
// the base constructor initializer. That is why we cannot defer the proxy
// initialization until after the base constructor initializer is executed.
// Even though that is going to be an unverifiable IL.
_currentMethod is not SynthesizedPrimaryConstructor)
{
// Containing method is a constructor
// Initialization statement for the "this" proxy must be inserted
// after the constructor initializer statement block
Debug.Assert(_thisProxyInitDeferred == null);
_thisProxyInitDeferred = assignToProxy;
}
else
{
Debug.Assert(_currentMethod is not SynthesizedPrimaryConstructor primaryConstructor ||
symbol != _currentMethod.ThisParameter ||
primaryConstructor.GetCapturedParameters().Any());
prologue.Add(assignToProxy);
}
}
}
#region Visit Methods
protected override BoundNode VisitUnhoistedParameter(BoundParameter node)
{
ParameterSymbol replacementParameter;
if (_parameterMap.TryGetValue(node.ParameterSymbol, out replacementParameter))
{
return new BoundParameter(node.Syntax, replacementParameter, node.HasErrors);
}
return base.VisitUnhoistedParameter(node);
}
public override BoundNode VisitThisReference(BoundThisReference node)
{
// "topLevelMethod.ThisParameter == null" can occur in a delegate creation expression because the method group
// in the argument can have a "this" receiver even when "this"
// is not captured because a static method is selected. But we do preserve
// the method group and its receiver in the bound tree.
// No need to capture "this" in such case.
// TODO: Why don't we drop "this" while lowering if method is static?
// Actually, considering that method group expression does not evaluate to a particular value
// why do we have it in the lowered tree at all?
return (_currentMethod == _topLevelMethod || _topLevelMethod.ThisParameter == null ?
node :
FramePointer(node.Syntax, (NamedTypeSymbol)node.Type));
}
public override BoundNode VisitBaseReference(BoundBaseReference node)
{
return (!_currentMethod.IsStatic && TypeSymbol.Equals(_currentMethod.ContainingType, _topLevelMethod.ContainingType, TypeCompareKind.ConsiderEverything2))
? node
: FramePointer(node.Syntax, _topLevelMethod.ContainingType); // technically, not the correct static type
}
public override BoundNode VisitMethodDefIndex(BoundMethodDefIndex node)
{
TypeSymbol type = VisitType(node.Type);
var loweredSymbol = (node.Method.MethodKind is MethodKind.LambdaMethod or MethodKind.LocalFunction) ?
Analysis.GetNestedFunctionInTree(_analysis.ScopeTree, node.Method.OriginalDefinition).SynthesizedLoweredMethod : node.Method;
return node.Update(loweredSymbol, type);
}
/// <summary>
/// Rewrites a reference to an unlowered local function to the newly
/// lowered local function.
/// </summary>
private void RemapLocalFunction(
SyntaxNode syntax,
MethodSymbol localFunc,
out BoundExpression receiver,
out MethodSymbol method,
ref ImmutableArray<BoundExpression> arguments,
ref ImmutableArray<RefKind> argRefKinds)
{
Debug.Assert(localFunc.MethodKind == MethodKind.LocalFunction);
var function = Analysis.GetNestedFunctionInTree(_analysis.ScopeTree, localFunc.OriginalDefinition);
var loweredSymbol = function.SynthesizedLoweredMethod;
// If the local function captured variables then they will be stored
// in frames and the frames need to be passed as extra parameters.
var frameCount = loweredSymbol.ExtraSynthesizedParameterCount;
if (frameCount != 0)
{
Debug.Assert(!arguments.IsDefault);
// Build a new list of arguments to pass to the local function
// call that includes any necessary capture frames
var argumentsBuilder = ArrayBuilder<BoundExpression>.GetInstance(loweredSymbol.ParameterCount);
argumentsBuilder.AddRange(arguments);
var start = loweredSymbol.ParameterCount - frameCount;
for (int i = start; i < loweredSymbol.ParameterCount; i++)
{
// will always be a LambdaFrame, it's always a capture frame
var frameType = (NamedTypeSymbol)loweredSymbol.Parameters[i].Type.OriginalDefinition;
Debug.Assert(frameType is SynthesizedClosureEnvironment);
if (frameType.Arity > 0)
{
var typeParameters = ((SynthesizedClosureEnvironment)frameType).ConstructedFromTypeParameters;
Debug.Assert(typeParameters.Length == frameType.Arity);
var subst = this.TypeMap.SubstituteTypeParameters(typeParameters);
frameType = frameType.Construct(subst);
}
var frame = FrameOfType(syntax, frameType);
argumentsBuilder.Add(frame);
}
// frame arguments are passed by ref
// add corresponding refkinds
var refkindsBuilder = ArrayBuilder<RefKind>.GetInstance(argumentsBuilder.Count);
if (!argRefKinds.IsDefault)
{
refkindsBuilder.AddRange(argRefKinds);
}
else
{
refkindsBuilder.AddMany(RefKind.None, arguments.Length);
}
refkindsBuilder.AddMany(RefKind.Ref, frameCount);
arguments = argumentsBuilder.ToImmutableAndFree();
argRefKinds = refkindsBuilder.ToImmutableAndFree();
}
method = loweredSymbol;
NamedTypeSymbol constructedFrame;
RemapLambdaOrLocalFunction(syntax,
localFunc,
SubstituteTypeArguments(localFunc.TypeArgumentsWithAnnotations),
loweredSymbol.ClosureKind,
ref method,
out receiver,
out constructedFrame);
}
/// <summary>
/// Substitutes references from old type arguments to new type arguments
/// in the lowered methods.
/// </summary>
/// <example>
/// Consider the following method:
/// void M() {
/// void L<T>(T t) => Console.Write(t);
/// L("A");
/// }
///
/// In this example, L<T> is a local function that will be
/// lowered into its own method and the type parameter T will be
/// alpha renamed to something else (let's call it T'). In this case,
/// all references to the original type parameter T in L must be
/// rewritten to the renamed parameter, T'.
/// </example>
private ImmutableArray<TypeWithAnnotations> SubstituteTypeArguments(ImmutableArray<TypeWithAnnotations> typeArguments)
{
Debug.Assert(!typeArguments.IsDefault);
if (typeArguments.IsEmpty)
{
return typeArguments;
}
// We must perform this process repeatedly as local
// functions may nest inside one another and capture type
// parameters from the enclosing local functions. Each
// iteration of nesting will cause alpha-renaming of the captured
// parameters, meaning that we must replace until there are no
// more alpha-rename mappings.
//
// The method symbol references are different from all other
// substituted types in this context because the method symbol in
// local function references is not rewritten until all local
// functions have already been lowered. Everything else is rewritten
// by the visitors as the definition is lowered. This means that
// only one substitution happens per lowering, but we need to do
// N substitutions all at once, where N is the number of lowerings.
var builder = ArrayBuilder<TypeWithAnnotations>.GetInstance(typeArguments.Length);
foreach (var typeArg in typeArguments)
{
TypeWithAnnotations oldTypeArg;
TypeWithAnnotations newTypeArg = typeArg;
do
{
oldTypeArg = newTypeArg;
newTypeArg = this.TypeMap.SubstituteType(oldTypeArg);
}
while (!TypeSymbol.Equals(oldTypeArg.Type, newTypeArg.Type, TypeCompareKind.ConsiderEverything));
// When type substitution does not change the type, it is expected to return the very same object.
// Therefore the loop is terminated when that type (as an object) does not change.
Debug.Assert((object)oldTypeArg.Type == newTypeArg.Type);
// The types are the same, so the last pass performed no substitutions.
// Therefore the annotations ought to be the same too.
Debug.Assert(oldTypeArg.NullableAnnotation == newTypeArg.NullableAnnotation);
builder.Add(newTypeArg);
}
return builder.ToImmutableAndFree();
}
private void RemapLambdaOrLocalFunction(
SyntaxNode syntax,
MethodSymbol originalMethod,
ImmutableArray<TypeWithAnnotations> typeArgumentsOpt,
ClosureKind closureKind,
ref MethodSymbol synthesizedMethod,
out BoundExpression receiver,
out NamedTypeSymbol constructedFrame)
{
var translatedLambdaContainer = synthesizedMethod.ContainingType;
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));
var totalTypeArgumentCount = (containerAsFrame?.Arity ?? 0) + synthesizedMethod.Arity;
var realTypeArguments = ImmutableArray.Create(_currentTypeParameters.SelectAsArray(t => TypeWithAnnotations.Create(t)), 0, totalTypeArgumentCount - originalMethod.Arity);
if (!typeArgumentsOpt.IsDefault)
{
realTypeArguments = realTypeArguments.Concat(typeArgumentsOpt);
}
if ((object)containerAsFrame != null && containerAsFrame.Arity != 0)
{
var containerTypeArguments = ImmutableArray.Create(realTypeArguments, 0, containerAsFrame.Arity);
realTypeArguments = ImmutableArray.Create(realTypeArguments, containerAsFrame.Arity, realTypeArguments.Length - containerAsFrame.Arity);
constructedFrame = containerAsFrame.Construct(containerTypeArguments);
}
else
{
constructedFrame = translatedLambdaContainer;
}
synthesizedMethod = synthesizedMethod.AsMember(constructedFrame);
if (synthesizedMethod.IsGenericMethod)
{
synthesizedMethod = synthesizedMethod.Construct(realTypeArguments);
}
else
{
Debug.Assert(realTypeArguments.Length == 0);
}
// for instance lambdas, receiver is the frame
// for static lambdas, get the singleton receiver
if (closureKind == ClosureKind.Singleton)
{
var field = containerAsFrame.SingletonCache.AsMember(constructedFrame);
receiver = new BoundFieldAccess(syntax, null, field, constantValueOpt: null);
}
else if (closureKind == ClosureKind.Static)
{
receiver = new BoundTypeExpression(syntax, null, synthesizedMethod.ContainingType);
}
else // ThisOnly and General
{
receiver = FrameOfType(syntax, constructedFrame);
}
}
public override BoundNode VisitCall(BoundCall node)
{
if (node.Method.MethodKind == MethodKind.LocalFunction)
{
var args = VisitList(node.Arguments);
var argRefKinds = node.ArgumentRefKindsOpt;
var type = VisitType(node.Type);
Debug.Assert(node.ArgsToParamsOpt.IsDefault, "should be done with argument reordering by now");
RemapLocalFunction(
node.Syntax,
node.Method,
out var receiver,
out var method,
ref args,
ref argRefKinds);
return node.Update(
receiver,
node.InitialBindingReceiverIsSubjectToCloning,
method,
args,
node.ArgumentNamesOpt,
argRefKinds,
node.IsDelegateCall,
node.Expanded,
node.InvokedAsExtensionMethod,
node.ArgsToParamsOpt,
node.DefaultArguments,
node.ResultKind,
type);
}
var visited = base.VisitCall(node);
if (visited.Kind != BoundKind.Call)
{
return visited;
}
var rewritten = (BoundCall)visited;
// Check if we need to init the 'this' proxy in a ctor call
if (!_seenBaseCall)
{
if (_currentMethod == _topLevelMethod && node.IsConstructorInitializer())
{
_seenBaseCall = true;
if (_thisProxyInitDeferred != null)
{
// Insert the this proxy assignment after the ctor call.
// Create bound sequence: { ctor call, thisProxyInitDeferred }
return new BoundSequence(
syntax: node.Syntax,
locals: ImmutableArray<LocalSymbol>.Empty,
sideEffects: ImmutableArray.Create<BoundExpression>(rewritten),
value: _thisProxyInitDeferred,
type: rewritten.Type);
}
}
}
return rewritten;
}
private BoundSequence RewriteSequence(BoundSequence node, ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals)
{
RewriteLocals(node.Locals, newLocals);
foreach (var effect in node.SideEffects)
{
var replacement = (BoundExpression)this.Visit(effect);
if (replacement != null) prologue.Add(replacement);
}
var newValue = (BoundExpression)this.Visit(node.Value);
var newType = this.VisitType(node.Type);
return node.Update(newLocals.ToImmutableAndFree(), prologue.ToImmutableAndFree(), newValue, newType);
}
public override BoundNode VisitBlock(BoundBlock node)
{
// Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out var frame))
{
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
RewriteBlock(node, prologue, newLocals));
}
else
{
return RewriteBlock(node, ArrayBuilder<BoundExpression>.GetInstance(), ArrayBuilder<LocalSymbol>.GetInstance());
}
}
private BoundBlock RewriteBlock(BoundBlock node, ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals)
{
RewriteLocals(node.Locals, newLocals);
var newStatements = ArrayBuilder<BoundStatement>.GetInstance();
if (prologue.Count > 0)
{
newStatements.Add(BoundSequencePoint.CreateHidden());
}
InsertAndFreePrologue(newStatements, prologue);
foreach (var statement in node.Statements)
{
var replacement = (BoundStatement)this.Visit(statement);
if (replacement != null)
{
newStatements.Add(replacement);
}
}
var newInstrumentation = node.Instrumentation;
if (newInstrumentation != null)
{
var newPrologue = (BoundStatement)Visit(newInstrumentation.Prologue);
var newEpilogue = (BoundStatement)Visit(newInstrumentation.Epilogue);
newInstrumentation = newInstrumentation.Update(newInstrumentation.Locals, newPrologue, newEpilogue);
}
// TODO: we may not need to update if there was nothing to rewrite.
return node.Update(newLocals.ToImmutableAndFree(), node.LocalFunctions, node.HasUnsafeModifier, newInstrumentation, newStatements.ToImmutableAndFree());
}
public override BoundNode VisitScope(BoundScope node)
{
Debug.Assert(!node.Locals.IsEmpty);
var newLocals = ArrayBuilder<LocalSymbol>.GetInstance();
RewriteLocals(node.Locals, newLocals);
var statements = VisitList(node.Statements);
if (newLocals.Count == 0)
{
newLocals.Free();
return new BoundStatementList(node.Syntax, statements);
}
return node.Update(newLocals.ToImmutableAndFree(), statements);
}
public override BoundNode VisitCatchBlock(BoundCatchBlock node)
{
// Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out var frame))
{
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{
return RewriteCatch(node, prologue, newLocals);
});
}
else
{
return RewriteCatch(node, ArrayBuilder<BoundExpression>.GetInstance(), ArrayBuilder<LocalSymbol>.GetInstance());
}
}
private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals)
{
RewriteLocals(node.Locals, newLocals);
var rewrittenCatchLocals = newLocals.ToImmutableAndFree();
// If exception variable got lifted, IntroduceFrame will give us frame init prologue.
// It needs to run before the exception variable is accessed.
// To ensure that, we will make exception variable a sequence that performs prologue as its side-effects.
BoundExpression rewrittenExceptionSource = null;
var rewrittenFilterPrologue = (BoundStatementList)this.Visit(node.ExceptionFilterPrologueOpt);
var rewrittenFilter = (BoundExpression)this.Visit(node.ExceptionFilterOpt);
if (node.ExceptionSourceOpt != null)
{
rewrittenExceptionSource = (BoundExpression)Visit(node.ExceptionSourceOpt);
if (prologue.Count > 0)
{
rewrittenExceptionSource = new BoundSequence(
rewrittenExceptionSource.Syntax,
ImmutableArray.Create<LocalSymbol>(),
prologue.ToImmutable(),
rewrittenExceptionSource,
rewrittenExceptionSource.Type);
}
}
else if (prologue.Count > 0)
{
Debug.Assert(rewrittenFilter != null);
var prologueBuilder = ArrayBuilder<BoundStatement>.GetInstance(prologue.Count);
foreach (var p in prologue)
{
prologueBuilder.Add(new BoundExpressionStatement(p.Syntax, p) { WasCompilerGenerated = true });
}
if (rewrittenFilterPrologue != null)
{
prologueBuilder.AddRange(rewrittenFilterPrologue.Statements);
}
rewrittenFilterPrologue = new BoundStatementList(rewrittenFilter.Syntax, prologueBuilder.ToImmutableAndFree());
}
// done with this.
prologue.Free();
// rewrite filter and body
// NOTE: this will proxy all accesses to exception local if that got lifted.
var exceptionTypeOpt = this.VisitType(node.ExceptionTypeOpt);
var rewrittenBlock = (BoundBlock)this.Visit(node.Body);
return node.Update(
rewrittenCatchLocals,
rewrittenExceptionSource,
exceptionTypeOpt,
rewrittenFilterPrologue,
rewrittenFilter,
rewrittenBlock,
node.IsSynthesizedAsyncCatchAll);
}
public override BoundNode VisitSequence(BoundSequence node)
{
// Test if this frame has captured variables and requires the introduction of a closure class.
if (_frames.TryGetValue(node, out var frame))
{
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{
return RewriteSequence(node, prologue, newLocals);
});
}
else
{
return RewriteSequence(node, ArrayBuilder<BoundExpression>.GetInstance(), ArrayBuilder<LocalSymbol>.GetInstance());
}
}
public override BoundNode VisitStatementList(BoundStatementList node)
{
// 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 var frame))
{
return IntroduceFrame(node, frame, (ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals) =>
{
var newStatements = ArrayBuilder<BoundStatement>.GetInstance();
InsertAndFreePrologue(newStatements, prologue);
foreach (var s in node.Statements)
{
newStatements.Add((BoundStatement)this.Visit(s));
}
return new BoundBlock(node.Syntax, newLocals.ToImmutableAndFree(), newStatements.ToImmutableAndFree(), node.HasErrors);
});
}
else
{
return base.VisitStatementList(node);
}
}
public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationExpression node)
{
// A delegate creation expression of the form "new Action( ()=>{} )" is treated exactly like
// (Action)(()=>{})
if (node.Argument.Kind == BoundKind.Lambda)
{
return RewriteLambdaConversion((BoundLambda)node.Argument);
}
if (node.MethodOpt?.MethodKind == MethodKind.LocalFunction)
{
var arguments = default(ImmutableArray<BoundExpression>);
var argRefKinds = default(ImmutableArray<RefKind>);
RemapLocalFunction(
node.Syntax,
node.MethodOpt,
out var receiver,
out var method,
ref arguments,
ref argRefKinds);
return new BoundDelegateCreationExpression(
node.Syntax,
receiver,
method,
node.IsExtensionMethod,
node.WasTargetTyped,
VisitType(node.Type));
}
return base.VisitDelegateCreationExpression(node);
}
public override BoundNode VisitFunctionPointerLoad(BoundFunctionPointerLoad node)
{
if (node.TargetMethod.MethodKind == MethodKind.LocalFunction)
{
Debug.Assert(node.TargetMethod is { RequiresInstanceReceiver: false, IsStatic: true });
ImmutableArray<BoundExpression> arguments = default;
ImmutableArray<RefKind> argRefKinds = default;
RemapLocalFunction(
node.Syntax,
node.TargetMethod,
out BoundExpression receiver,
out MethodSymbol remappedMethod,
ref arguments,
ref argRefKinds);
Debug.Assert(arguments.IsDefault &&
argRefKinds.IsDefault &&
receiver.Kind == BoundKind.TypeExpression &&
remappedMethod is { RequiresInstanceReceiver: false, IsStatic: true });
return node.Update(remappedMethod, constrainedToTypeOpt: node.ConstrainedToTypeOpt, node.Type);
}
return base.VisitFunctionPointerLoad(node);
}
public override BoundNode VisitConversion(BoundConversion conversion)
{
// a conversion with a method should have been rewritten, e.g. to an invocation
Debug.Assert(_inExpressionLambda || conversion.Conversion.MethodSymbol is null);
Debug.Assert(conversion.ConversionKind != ConversionKind.MethodGroup);
if (conversion.ConversionKind == ConversionKind.AnonymousFunction)
{
var result = (BoundExpression)RewriteLambdaConversion((BoundLambda)conversion.Operand);
if (_inExpressionLambda && conversion.ExplicitCastInCode)
{
result = new BoundConversion(
syntax: conversion.Syntax,
operand: result,
conversion: conversion.Conversion,
isBaseConversion: false,
@checked: false,
explicitCastInCode: true,
conversionGroupOpt: conversion.ConversionGroupOpt,
constantValueOpt: conversion.ConstantValueOpt,
type: conversion.Type);
}
return result;
}
return base.VisitConversion(conversion);
}
public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
{
ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer;
SynthesizedClosureEnvironment containerAsFrame;
BoundNode lambdaScope;
DebugId topLevelMethodId;
DebugId lambdaId;
RewriteLambdaOrLocalFunction(
node,
out closureKind,
out translatedLambdaContainer,
out containerAsFrame,
out lambdaScope,
out topLevelMethodId,
out lambdaId);
return new BoundNoOpStatement(node.Syntax, NoOpStatementFlavor.Default);
}
#nullable enable
private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int closureOrdinal, ImmutableArray<DebugId> structClosureIds, RuntimeRudeEdit? closureRudeEdit)
{
Debug.Assert(syntax != null);
Debug.Assert(CompilationState.ModuleBuilderOpt != null);
Debug.Assert(closureOrdinal >= LambdaDebugInfo.MinClosureOrdinal);
SyntaxNode? lambdaOrLambdaBodySyntax;
bool isLambdaBody;
if (syntax is AnonymousFunctionExpressionSyntax anonymousFunction)
{
lambdaOrLambdaBodySyntax = anonymousFunction.Body;
isLambdaBody = true;
}
else if (syntax is LocalFunctionStatementSyntax localFunction)
{
lambdaOrLambdaBodySyntax = (SyntaxNode?)localFunction.Body ?? localFunction.ExpressionBody?.Expression;
if (lambdaOrLambdaBodySyntax is null)
{
lambdaOrLambdaBodySyntax = localFunction;
isLambdaBody = false;
}
else
{
isLambdaBody = true;
}
}
else if (LambdaUtilities.IsQueryPairLambda(syntax))
{
// "pair" query lambdas
lambdaOrLambdaBodySyntax = syntax;
isLambdaBody = false;
Debug.Assert(closureKind == ClosureKind.Singleton);
}
else
{
// query lambdas
lambdaOrLambdaBodySyntax = syntax;
isLambdaBody = true;
}
Debug.Assert(!isLambdaBody || LambdaUtilities.IsLambdaBody(lambdaOrLambdaBodySyntax));
// determine lambda ordinal and calculate syntax offset
DebugId lambdaId;
DebugId previousLambdaId = default;
RuntimeRudeEdit? lambdaRudeEdit = null;
if (closureRudeEdit == null &&
slotAllocator?.TryGetPreviousLambda(lambdaOrLambdaBodySyntax, isLambdaBody, closureOrdinal, structClosureIds, out previousLambdaId, out lambdaRudeEdit) == true &&
lambdaRudeEdit == null)
{
lambdaId = previousLambdaId;
}
else
{
lambdaId = new DebugId(_lambdaDebugInfoBuilder.Count, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal);
var rudeEdit = closureRudeEdit ?? lambdaRudeEdit;
if (rudeEdit != null)
{
_lambdaRuntimeRudeEditsBuilder.Add(new LambdaRuntimeRudeEditInfo(previousLambdaId, rudeEdit.Value));
}
}
int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(lambdaOrLambdaBodySyntax), lambdaOrLambdaBodySyntax.SyntaxTree);
_lambdaDebugInfoBuilder.Add(new EncLambdaInfo(new LambdaDebugInfo(syntaxOffset, lambdaId, closureOrdinal), structClosureIds));
return lambdaId;
}
#nullable disable
private SynthesizedClosureMethod RewriteLambdaOrLocalFunction(
IBoundLambdaOrFunction node,
out ClosureKind closureKind,
out NamedTypeSymbol translatedLambdaContainer,
out SynthesizedClosureEnvironment containerAsFrame,
out BoundNode lambdaScope,
out DebugId topLevelMethodId,
out DebugId lambdaId)
{
Analysis.NestedFunction function = Analysis.GetNestedFunctionInTree(_analysis.ScopeTree, node.Symbol);
var synthesizedMethod = function.SynthesizedLoweredMethod;
Debug.Assert(synthesizedMethod != null);
closureKind = synthesizedMethod.ClosureKind;
translatedLambdaContainer = synthesizedMethod.ContainingType;
containerAsFrame = translatedLambdaContainer as SynthesizedClosureEnvironment;
topLevelMethodId = _analysis.GetTopLevelMethodId();
lambdaId = synthesizedMethod.LambdaId;
if (function.ContainingEnvironmentOpt != null)
{
// Find the scope of the containing environment
BoundNode tmpScope = null;
Analysis.VisitScopeTree(_analysis.ScopeTree, scope =>
{
if (scope.DeclaredEnvironment == function.ContainingEnvironmentOpt)
{
tmpScope = scope.BoundNode;
}
});
Debug.Assert(tmpScope != null);
lambdaScope = tmpScope;
}
else
{
lambdaScope = null;
}
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(translatedLambdaContainer, synthesizedMethod.GetCciAdapter());
foreach (var parameter in node.Symbol.Parameters)
{
_parameterMap.Add(parameter, synthesizedMethod.Parameters[parameter.Ordinal]);
}
// rewrite the lambda body as the generated method's body
var oldMethod = _currentMethod;
var oldFrameThis = _currentFrameThis;
var oldTypeParameters = _currentTypeParameters;
var oldInnermostFramePointer = _innermostFramePointer;
var oldTypeMap = _currentLambdaBodyTypeMap;
var oldAddedStatements = _addedStatements;
var oldAddedLocals = _addedLocals;
_addedStatements = null;
_addedLocals = null;
// switch to the generated method
_currentMethod = synthesizedMethod;
if (closureKind == ClosureKind.Static || closureKind == ClosureKind.Singleton)
{
// no link from a static lambda to its container
_innermostFramePointer = _currentFrameThis = null;
}
else
{
_currentFrameThis = synthesizedMethod.ThisParameter;
_framePointers.TryGetValue(translatedLambdaContainer, out _innermostFramePointer);
}
_currentTypeParameters = containerAsFrame?.TypeParameters.Concat(synthesizedMethod.TypeParameters) ?? synthesizedMethod.TypeParameters;
_currentLambdaBodyTypeMap = synthesizedMethod.TypeMap;
if (node.Body is BoundBlock block)
{
var body = AddStatementsIfNeeded((BoundStatement)VisitBlock(block));
CheckLocalsDefined(body);
AddSynthesizedMethod(synthesizedMethod, body);
}
// return to the old method
_currentMethod = oldMethod;
_currentFrameThis = oldFrameThis;
_currentTypeParameters = oldTypeParameters;
_innermostFramePointer = oldInnermostFramePointer;
_currentLambdaBodyTypeMap = oldTypeMap;
_addedLocals = oldAddedLocals;
_addedStatements = oldAddedStatements;
return synthesizedMethod;
}
private void AddSynthesizedMethod(MethodSymbol method, BoundStatement body)
{
if (_synthesizedMethods == null)
{
_synthesizedMethods = ArrayBuilder<TypeCompilationState.MethodWithBody>.GetInstance();
}
_synthesizedMethods.Add(
new TypeCompilationState.MethodWithBody(
method,
body,
CompilationState.CurrentImportChain));
}
private BoundNode RewriteLambdaConversion(BoundLambda node)
{
var wasInExpressionLambda = _inExpressionLambda;
_inExpressionLambda = _inExpressionLambda || node.Type.IsExpressionTree();
if (_inExpressionLambda)
{
var newType = VisitType(node.Type);
var newBody = (BoundBlock)Visit(node.Body);
node = node.Update(node.UnboundLambda, node.Symbol, newBody, node.Diagnostics, node.Binder, newType);
var result0 = wasInExpressionLambda ? node : ExpressionLambdaRewriter.RewriteLambda(node, CompilationState, TypeMap, RecursionDepth, Diagnostics);
_inExpressionLambda = wasInExpressionLambda;
return result0;
}
ClosureKind closureKind;
NamedTypeSymbol translatedLambdaContainer;
SynthesizedClosureEnvironment containerAsFrame;
BoundNode lambdaScope;
DebugId topLevelMethodId;
DebugId lambdaId;
SynthesizedClosureMethod synthesizedMethod = RewriteLambdaOrLocalFunction(
node,
out closureKind,
out translatedLambdaContainer,
out containerAsFrame,
out lambdaScope,
out topLevelMethodId,
out lambdaId);
MethodSymbol referencedMethod = synthesizedMethod;
BoundExpression receiver;
NamedTypeSymbol constructedFrame;
RemapLambdaOrLocalFunction(node.Syntax, node.Symbol, default(ImmutableArray<TypeWithAnnotations>), closureKind, ref referencedMethod, out receiver, out constructedFrame);
// Rewrite the lambda expression (and the enclosing anonymous method conversion) as a delegate creation expression
TypeSymbol type = this.VisitType(node.Type);
// static lambdas are emitted as instance methods on a singleton receiver
// delegates invoke dispatch is optimized for instance delegates so
// it is preferable to emit lambdas as instance methods even when lambdas
// do not capture anything
BoundExpression result = new BoundDelegateCreationExpression(
node.Syntax,
receiver,
referencedMethod,
isExtensionMethod: false,
wasTargetTyped: false,
type: type);
// if the block containing the lambda is not the innermost block,
// or the lambda is static, then the lambda object should be cached in its frame.
// NOTE: we are not caching static lambdas in static ctors - cannot reuse such cache.
var shouldCacheForStaticMethod = closureKind == ClosureKind.Singleton &&
_currentMethod.MethodKind != MethodKind.StaticConstructor &&
!referencedMethod.IsGenericMethod;
// NOTE: We require "lambdaScope != null".
// We do not want to introduce a field into an actual user's class (not a synthetic frame).
var shouldCacheInLoop = lambdaScope != null &&
lambdaScope != Analysis.GetScopeParent(_analysis.ScopeTree, node.Body).BoundNode &&
InLoopOrLambda(node.Syntax, lambdaScope.Syntax);
if (shouldCacheForStaticMethod || shouldCacheInLoop)
{
// replace the expression "new Delegate(frame.M)" with "frame.cache ?? (frame.cache = new Delegate(frame.M));
var F = new SyntheticBoundNodeFactory(_currentMethod, node.Syntax, CompilationState, Diagnostics);
try
{
BoundExpression cache;
if (shouldCacheForStaticMethod || shouldCacheInLoop && (object)containerAsFrame != null)
{
// Since the cache variable will be in a container with possibly alpha-rewritten generic parameters, we need to
// substitute the original type according to the type map for that container. That substituted type may be
// different from the local variable `type`, which has the node's type substituted for the current container.
var cacheVariableType = containerAsFrame.TypeMap.SubstituteType(node.Type).Type;
var hasTypeParametersFromAnyMethod = cacheVariableType.ContainsMethodTypeParameter();
// If we want to cache a variable by moving its value into a field,
// the variable cannot use any type parameter from the method it is currently declared within.
if (!hasTypeParametersFromAnyMethod)
{
var cacheVariableName = GeneratedNames.MakeLambdaCacheFieldName(
// If we are generating the field into a display class created exclusively for the lambda the lambdaOrdinal itself is unique already,
// no need to include the top-level method ordinal in the field name.
(closureKind == ClosureKind.General) ? -1 : topLevelMethodId.Ordinal,
topLevelMethodId.Generation,
lambdaId.Ordinal,
lambdaId.Generation);
var cacheField = new SynthesizedLambdaCacheFieldSymbol(translatedLambdaContainer, cacheVariableType, cacheVariableName, _topLevelMethod, isReadOnly: false, isStatic: closureKind == ClosureKind.Singleton);
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(translatedLambdaContainer, cacheField.GetCciAdapter());
cache = F.Field(receiver, cacheField.AsMember(constructedFrame)); //NOTE: the field was added to the unconstructed frame type.
result = F.Coalesce(cache, F.AssignmentExpression(cache, result));
}
}
else
{
// the lambda captures at most the "this" of the enclosing method. We cache its delegate in a local variable.
var cacheLocal = F.SynthesizedLocal(type, kind: SynthesizedLocalKind.CachedAnonymousMethodDelegate);
if (_addedLocals == null) _addedLocals = ArrayBuilder<LocalSymbol>.GetInstance();
_addedLocals.Add(cacheLocal);
if (_addedStatements == null) _addedStatements = ArrayBuilder<BoundStatement>.GetInstance();
cache = F.Local(cacheLocal);
_addedStatements.Add(F.Assignment(cache, F.Null(type)));
result = F.Coalesce(cache, F.AssignmentExpression(cache, result));
}
}
catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex)
{
Diagnostics.Add(ex.Diagnostic);
return new BoundBadExpression(F.Syntax, LookupResultKind.Empty, ImmutableArray<Symbol>.Empty, ImmutableArray.Create<BoundExpression>(node), node.Type);
}
}
return result;
}
// This helper checks syntactically whether there is a loop or lambda expression
// between given lambda syntax and the syntax that corresponds to its closure.
// we use this heuristic as a hint that the lambda delegate may be created
// multiple times with same closure.
// In such cases it makes sense to cache the delegate.
//
// Examples:
// int x = 123;
// for (int i = 1; i< 10; i++)
// {
// if (i< 2)
// {
// arr[i].Execute(arg => arg + x); // delegate should be cached
// }
// }
// for (int i = 1; i< 10; i++)
// {
// var val = i;
// if (i< 2)
// {
// int y = i + i;
// System.Console.WriteLine(y);
// arr[i].Execute(arg => arg + val); // delegate should NOT be cached (closure created inside the loop)
// }
// }
//
private static bool InLoopOrLambda(SyntaxNode lambdaSyntax, SyntaxNode scopeSyntax)
{
var curSyntax = lambdaSyntax.Parent;
while (curSyntax != null && curSyntax != scopeSyntax)
{
switch (curSyntax.Kind())
{
case SyntaxKind.ForStatement:
case SyntaxKind.ForEachStatement:
case SyntaxKind.ForEachVariableStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.ParenthesizedLambdaExpression:
return true;
}
curSyntax = curSyntax.Parent;
}
return false;
}
public override BoundNode VisitLambda(BoundLambda node)
{
// these nodes have been handled in the context of the enclosing anonymous method conversion.
throw ExceptionUtilities.Unreachable();
}
#endregion
#if CHECK_LOCALS
/// <summary>
/// Ensure that local variables are always in scope where used in bound trees
/// </summary>
/// <param name="node"></param>
static partial void CheckLocalsDefined(BoundNode node)
{
LocalsDefinedScanner.INSTANCE.Visit(node);
}
class LocalsDefinedScanner : BoundTreeWalker
{
internal static LocalsDefinedScanner INSTANCE = new LocalsDefinedScanner();
HashSet<Symbol> localsDefined = new HashSet<Symbol>();
public override BoundNode VisitLocal(BoundLocal node)
{
Debug.Assert(node.LocalSymbol.IsConst || localsDefined.Contains(node.LocalSymbol));
return base.VisitLocal(node);
}
public override BoundNode VisitSequence(BoundSequence node)
{
try
{
if (!node.Locals.IsNullOrEmpty)
foreach (var l in node.Locals)
localsDefined.Add(l);
return base.VisitSequence(node);
}
finally
{
if (!node.Locals.IsNullOrEmpty)
foreach (var l in node.Locals)
localsDefined.Remove(l);
}
}
public override BoundNode VisitCatchBlock(BoundCatchBlock node)
{
try
{
if ((object)node.LocalOpt != null) localsDefined.Add(node.LocalOpt);
return base.VisitCatchBlock(node);
}
finally
{
if ((object)node.LocalOpt != null) localsDefined.Remove(node.LocalOpt);
}
}
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
try
{
if (!node.LocalsOpt.IsNullOrEmpty)
foreach (var l in node.LocalsOpt)
localsDefined.Add(l);
return base.VisitSwitchStatement(node);
}
finally
{
if (!node.LocalsOpt.IsNullOrEmpty)
foreach (var l in node.LocalsOpt)
localsDefined.Remove(l);
}
}
public override BoundNode VisitBlock(BoundBlock node)
{
try
{
if (!node.LocalsOpt.IsNullOrEmpty)
foreach (var l in node.LocalsOpt)
localsDefined.Add(l);
return base.VisitBlock(node);
}
finally
{
if (!node.LocalsOpt.IsNullOrEmpty)
foreach (var l in node.LocalsOpt)
localsDefined.Remove(l);
}
}
}
#endif
}
}
|