|
' 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.
Imports System.Collections.Immutable
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Namespace Microsoft.CodeAnalysis.VisualBasic
''' <summary>
''' </summary>
''' <typeparam name="TProxy">
''' Type used by State Machine rewriter to represent symbol proxy. Lambda rewriter as
''' well as iterator rewriter use simplified form of proxy as they only capture
''' locals as r-values in fields, async rewriter uses a different structure as a proxy
''' because it has to capture l-values on stack as well
''' </typeparam>
Partial Friend MustInherit Class StateMachineRewriter(Of TProxy)
Protected ReadOnly Body As BoundStatement
Protected ReadOnly Method As MethodSymbol
Protected ReadOnly Diagnostics As BindingDiagnosticBag
Protected ReadOnly F As SyntheticBoundNodeFactory
Protected ReadOnly StateMachineType As SynthesizedContainer
Protected ReadOnly SlotAllocatorOpt As VariableSlotAllocator
Protected ReadOnly SynthesizedLocalOrdinals As SynthesizedLocalOrdinalsDispenser
Protected ReadOnly StateDebugInfoBuilder As ArrayBuilder(Of StateMachineStateDebugInfo)
Protected StateField As FieldSymbol
Protected nonReusableLocalProxies As Dictionary(Of Symbol, TProxy)
Protected nextFreeHoistedLocalSlot As Integer
Protected hoistedVariables As IReadOnlySet(Of Symbol)
Protected InitialParameters As Dictionary(Of Symbol, TProxy)
Protected Sub New(body As BoundStatement,
method As MethodSymbol,
stateMachineType As StateMachineTypeSymbol,
stateMachineStateDebugInfoBuilder As ArrayBuilder(Of StateMachineStateDebugInfo),
slotAllocatorOpt As VariableSlotAllocator,
compilationState As TypeCompilationState,
diagnostics As BindingDiagnosticBag)
Debug.Assert(body IsNot Nothing)
Debug.Assert(method IsNot Nothing)
Debug.Assert(compilationState IsNot Nothing)
Debug.Assert(diagnostics IsNot Nothing)
Debug.Assert(diagnostics.AccumulatesDiagnostics)
Debug.Assert(stateMachineType IsNot Nothing)
Me.Body = body
Me.Method = method
Me.StateMachineType = stateMachineType
Me.SlotAllocatorOpt = slotAllocatorOpt
Me.Diagnostics = diagnostics
Me.SynthesizedLocalOrdinals = New SynthesizedLocalOrdinalsDispenser()
Me.StateDebugInfoBuilder = stateMachineStateDebugInfoBuilder
Me.nonReusableLocalProxies = New Dictionary(Of Symbol, TProxy)()
Me.F = New SyntheticBoundNodeFactory(method, method, method.ContainingType, body.Syntax, compilationState, diagnostics)
End Sub
''' <summary>
''' True if the initial values of locals in the rewritten method need to be preserved. (e.g. enumerable iterator methods)
''' </summary>
Protected MustOverride ReadOnly Property PreserveInitialParameterValues As Boolean
''' <summary>
''' Type substitution if applicable or Nothing
''' </summary>
Friend MustOverride ReadOnly Property TypeMap As TypeSubstitution
''' <summary>
''' Add fields to the state machine class that control the state machine.
''' </summary>
Protected MustOverride Sub GenerateControlFields()
''' <summary>
''' Initialize the state machine class.
''' </summary>
Protected MustOverride Sub InitializeStateMachine(bodyBuilder As ArrayBuilder(Of BoundStatement), frameType As NamedTypeSymbol, stateMachineLocal As LocalSymbol)
''' <summary>
''' Generate implementation-specific state machine initialization for the kickoff method body.
''' </summary>
Protected MustOverride Function GenerateStateMachineCreation(stateMachineVariable As LocalSymbol, frameType As NamedTypeSymbol) As BoundStatement
''' <summary>
''' Generate implementation-specific state machine member method implementations.
''' </summary>
Protected MustOverride Sub GenerateMethodImplementations()
Protected Function Rewrite() As BoundBlock
Debug.Assert(Not Me.PreserveInitialParameterValues OrElse Method.IsIterator)
Me.F.OpenNestedType(Me.StateMachineType)
Me.F.CompilationState.StateMachineImplementationClass(Me.Method) = Me.StateMachineType
Me.GenerateControlFields()
' and fields for the initial values of all the parameters of the method
If Me.PreserveInitialParameterValues Then
Me.InitialParameters = New Dictionary(Of Symbol, TProxy)()
End If
' add fields for the captured variables of the method
Dim variablesToHoist = IteratorAndAsyncCaptureWalker.Analyze(New FlowAnalysisInfo(F.CompilationState.Compilation, Me.Method, Me.Body), Me.Diagnostics.DiagnosticBag)
CreateNonReusableLocalProxies(variablesToHoist, Me.nextFreeHoistedLocalSlot)
Me.hoistedVariables = New OrderedSet(Of Symbol)(variablesToHoist.CapturedLocals)
GenerateMethodImplementations()
' Return a replacement body for the kickoff method
Return GenerateKickoffMethodBody()
End Function
Private Function GenerateKickoffMethodBody() As BoundBlock
Me.F.CurrentMethod = Me.Method
Dim bodyBuilder = ArrayBuilder(Of BoundStatement).GetInstance()
bodyBuilder.Add(SyntheticBoundNodeFactory.HiddenSequencePoint())
Dim frameType As NamedTypeSymbol = If(Me.Method.IsGenericMethod, Me.StateMachineType.Construct(Method.TypeArguments), Me.StateMachineType)
Dim stateMachineVariable As LocalSymbol = F.SynthesizedLocal(frameType)
InitializeStateMachine(bodyBuilder, frameType, stateMachineVariable)
' Plus code to initialize all of the parameter proxies result
Dim proxies = If(PreserveInitialParameterValues, Me.InitialParameters, Me.nonReusableLocalProxies)
Dim initializers = ArrayBuilder(Of BoundExpression).GetInstance()
' starting with the "Me" proxy
If Not Me.Method.IsShared AndAlso Me.Method.MeParameter IsNot Nothing Then
Dim proxy As TProxy = Nothing
If proxies.TryGetValue(Me.Method.MeParameter, proxy) Then
InitializeParameterWithProxy(Me.Method.MeParameter, proxy, stateMachineVariable, initializers)
End If
End If
' then all the parameters
For Each parameter In Me.Method.Parameters
Dim proxy As TProxy = Nothing
If proxies.TryGetValue(parameter, proxy) Then
InitializeParameterWithProxy(parameter, proxy, stateMachineVariable, initializers)
End If
Next
If initializers.Count > 0 Then
bodyBuilder.Add(F.ExpressionStatement(F.Sequence(initializers.ToArray())))
End If
initializers.Free()
bodyBuilder.Add(GenerateStateMachineCreation(stateMachineVariable, frameType))
Return Me.F.Block(
ImmutableArray.Create(Of LocalSymbol)(stateMachineVariable),
bodyBuilder.ToImmutableAndFree())
End Function
Private Sub CreateNonReusableLocalProxies(captured As IteratorAndAsyncCaptureWalker.Result,
ByRef nextFreeHoistedLocalSlot As Integer)
Dim typeMap As TypeSubstitution = StateMachineType.TypeSubstitution
Dim isDebugBuild As Boolean = F.Compilation.Options.OptimizationLevel = OptimizationLevel.Debug
Dim mapToPreviousFields = isDebugBuild AndAlso SlotAllocatorOpt IsNot Nothing
Me.nextFreeHoistedLocalSlot = If(mapToPreviousFields, SlotAllocatorOpt.PreviousHoistedLocalSlotCount, 0)
For Each variable In captured.CapturedLocals
Debug.Assert(variable.Kind = SymbolKind.Local OrElse variable.Kind = SymbolKind.Parameter)
Select Case variable.Kind
Case SymbolKind.Local
Dim local = DirectCast(variable, LocalSymbol)
' No need to hoist constants
If local.IsConst Then
Continue For
End If
If local.SynthesizedKind = SynthesizedLocalKind.ConditionalBranchDiscriminator Then
Continue For
End If
CaptureLocalSymbol(typeMap, DirectCast(variable, LocalSymbol), captured.ByRefLocalsInitializers)
Case SymbolKind.Parameter
CaptureParameterSymbol(typeMap, DirectCast(variable, ParameterSymbol))
End Select
Next
End Sub
Protected Function CaptureParameterSymbol(typeMap As TypeSubstitution,
parameter As ParameterSymbol) As TProxy
Dim proxy As TProxy = Nothing
If Me.nonReusableLocalProxies.TryGetValue(parameter, proxy) Then
' This proxy may have already be added while processing
' previous ByRef local
Return proxy
End If
If parameter.IsMe Then
Dim typeName As String = parameter.ContainingSymbol.ContainingType.Name
Dim isMeOfClosureType As Boolean = typeName.StartsWith(GeneratedNameConstants.DisplayClassPrefix, StringComparison.Ordinal)
' NOTE: even though 'Me' is 'ByRef' in structures, Dev11 does capture it by value
' NOTE: without generation of any errors/warnings. Roslyn has to match this behavior
proxy = CreateParameterCapture(
Me.F.StateMachineField(
Method.ContainingType,
Me.Method,
If(isMeOfClosureType,
GeneratedNames.MakeStateMachineCapturedClosureMeName(typeName),
GeneratedNames.MakeStateMachineCapturedMeName()),
Accessibility.Friend),
parameter)
Me.nonReusableLocalProxies.Add(parameter, proxy)
If Me.PreserveInitialParameterValues Then
Dim initialMe As TProxy = If(Me.Method.ContainingType.IsStructureType(),
CreateParameterCapture(
Me.F.StateMachineField(
Me.Method.ContainingType,
Me.Method,
GeneratedNames.MakeIteratorParameterProxyName(GeneratedNames.MakeStateMachineCapturedMeName()),
Accessibility.Friend),
parameter),
Me.nonReusableLocalProxies(parameter))
Me.InitialParameters.Add(parameter, initialMe)
End If
Else
Dim paramType As TypeSymbol = parameter.Type.InternalSubstituteTypeParameters(typeMap).Type
Debug.Assert(Not parameter.IsByRef)
proxy = CreateParameterCapture(
F.StateMachineField(
paramType,
Me.Method,
GeneratedNames.MakeStateMachineParameterName(parameter.Name),
Accessibility.Friend),
parameter)
Me.nonReusableLocalProxies.Add(parameter, proxy)
If Me.PreserveInitialParameterValues Then
Me.InitialParameters.Add(parameter,
CreateParameterCapture(
Me.F.StateMachineField(
paramType,
Me.Method,
GeneratedNames.MakeIteratorParameterProxyName(parameter.Name),
Accessibility.Friend),
parameter))
End If
End If
Return proxy
End Function
Protected Function CaptureLocalSymbol(typeMap As TypeSubstitution,
local As LocalSymbol,
initializers As Dictionary(Of LocalSymbol, BoundExpression)) As TProxy
Dim proxy As TProxy = Nothing
If nonReusableLocalProxies.TryGetValue(local, proxy) Then
' This proxy may have already be added while processing
' previous ByRef local
Return proxy
End If
If local.IsByRef Then
' We'll create proxies for these variable later:
' TODO: so, we have to check if it is already in or not. See the early impl.
Debug.Assert(initializers.ContainsKey(local))
proxy = CreateByRefLocalCapture(typeMap, local, initializers)
nonReusableLocalProxies.Add(local, proxy)
Return proxy
End If
' Variable needs to be hoisted.
Dim fieldType = local.Type.InternalSubstituteTypeParameters(typeMap).Type
Dim id As LocalDebugId = LocalDebugId.None
Dim slotIndex As Integer = -1
If Not local.SynthesizedKind.IsSlotReusable(F.Compilation.Options.OptimizationLevel) Then
' Calculate local debug id
'
' EnC: When emitting the baseline (gen 0) the id is stored in a custom debug information attached to the kickoff method.
' When emitting a delta the id is only used to map to the existing field in the previous generation.
Dim declaratorSyntax As SyntaxNode = local.GetDeclaratorSyntax()
Dim syntaxOffset As Integer = Me.Method.CalculateLocalSyntaxOffset(declaratorSyntax.SpanStart, declaratorSyntax.SyntaxTree)
Dim ordinal As Integer = SynthesizedLocalOrdinals.AssignLocalOrdinal(local.SynthesizedKind, syntaxOffset)
id = New LocalDebugId(syntaxOffset, ordinal)
Dim previousSlotIndex = -1
If SlotAllocatorOpt IsNot Nothing AndAlso SlotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(declaratorSyntax,
F.CompilationState.ModuleBuilderOpt.Translate(fieldType, declaratorSyntax, Diagnostics.DiagnosticBag),
local.SynthesizedKind,
id,
Diagnostics.DiagnosticBag,
previousSlotIndex) Then
slotIndex = previousSlotIndex
End If
End If
If slotIndex = -1 Then
slotIndex = Me.nextFreeHoistedLocalSlot
Me.nextFreeHoistedLocalSlot = Me.nextFreeHoistedLocalSlot + 1
End If
proxy = CreateByValLocalCapture(MakeHoistedFieldForLocal(local, fieldType, slotIndex, id), local)
nonReusableLocalProxies.Add(local, proxy)
Return proxy
End Function
Protected MustOverride Sub InitializeParameterWithProxy(parameter As ParameterSymbol, proxy As TProxy, stateMachineVariable As LocalSymbol, initializers As ArrayBuilder(Of BoundExpression))
Protected MustOverride Function CreateByValLocalCapture(field As FieldSymbol, local As LocalSymbol) As TProxy
Protected MustOverride Function CreateParameterCapture(field As FieldSymbol, parameter As ParameterSymbol) As TProxy
Protected Overridable Function CreateByRefLocalCapture(typeMap As TypeSubstitution,
local As LocalSymbol,
initializers As Dictionary(Of LocalSymbol, BoundExpression)) As TProxy
' This is only supposed to be reachable in Async rewriter
Throw ExceptionUtilities.Unreachable
End Function
Protected Function MakeHoistedFieldForLocal(local As LocalSymbol, localType As TypeSymbol, slotIndex As Integer, id As LocalDebugId) As FieldSymbol
Dim proxyName As String
Select Case local.SynthesizedKind
Case SynthesizedLocalKind.LambdaDisplayClass
proxyName = GeneratedNameConstants.StateMachineHoistedUserVariableOrDisplayClassPrefix & GeneratedNameConstants.ClosureVariablePrefix & "$" & slotIndex
Case SynthesizedLocalKind.UserDefined
proxyName = GeneratedNameConstants.StateMachineHoistedUserVariableOrDisplayClassPrefix & local.Name & "$" & slotIndex
Case SynthesizedLocalKind.With
proxyName = GeneratedNameConstants.HoistedWithLocalPrefix & slotIndex
Case Else
proxyName = GeneratedNameConstants.HoistedSynthesizedLocalPrefix & slotIndex
End Select
Return F.StateMachineField(localType, Me.Method, proxyName, New LocalSlotDebugInfo(local.SynthesizedKind, id), slotIndex, Accessibility.Friend)
End Function
''' <summary>
''' If any required special/well-known type/member is not found or has use-site errors
''' we should not continue with transformation because it may have unwanted consequences;
''' e.g. we do return Nothing if well-known member symbol is not found. This method should
''' check all required symbols and return False if any of them are missing or have use-site errors.
''' We will also return True if signature is definitely bad - contains parameters that are ByRef or have error types
''' </summary>
Friend Overridable Function EnsureAllSymbolsAndSignature() As Boolean
If Me.Method.ReturnType.IsErrorType Then
Return True
End If
For Each parameter In Me.Method.Parameters
If parameter.IsByRef OrElse parameter.Type.IsErrorType Then
Return True
End If
Next
Return False
End Function
Friend Function EnsureSpecialType(type As SpecialType, bag As BindingDiagnosticBag) As Symbol
Return Binder.GetSpecialType(F.Compilation, type, Me.Body.Syntax, bag)
End Function
Friend Function EnsureWellKnownType(type As WellKnownType, bag As BindingDiagnosticBag) As Symbol
Return Binder.GetWellKnownType(F.Compilation, type, Me.Body.Syntax, bag)
End Function
Friend Function EnsureSpecialMember(member As SpecialMember, bag As BindingDiagnosticBag) As Symbol
Return Binder.GetSpecialTypeMember(F.Compilation.Assembly, member, Me.Body.Syntax, bag)
End Function
Friend Function EnsureWellKnownMember(member As WellKnownMember, bag As BindingDiagnosticBag) As Symbol
Return Binder.GetWellKnownTypeMember(F.Compilation, member, Me.Body.Syntax, bag)
End Function
''' <summary>
''' Check that the property and its getter exist and collect any use-site errors.
''' </summary>
Friend Sub EnsureSpecialPropertyGetter(member As SpecialMember, bag As BindingDiagnosticBag)
Dim symbol = DirectCast(EnsureSpecialMember(member, bag), PropertySymbol)
If symbol IsNot Nothing Then
Dim getter = symbol.GetMethod
If getter Is Nothing Then
Binder.ReportDiagnostic(bag, Body.Syntax, ERRID.ERR_NoGetProperty1, CustomSymbolDisplayFormatter.QualifiedName(symbol))
Return
End If
Dim useSiteInfo = getter.GetUseSiteInfo()
Binder.ReportUseSite(bag, Body.Syntax, useSiteInfo)
End If
End Sub
Friend Function OpenMethodImplementation(interfaceMethod As WellKnownMember, name As String, accessibility As Accessibility, Optional hasMethodBodyDependency As Boolean = False, Optional associatedProperty As PropertySymbol = Nothing) As SynthesizedMethod
Dim methodToImplement As MethodSymbol = Me.F.WellKnownMember(Of MethodSymbol)(interfaceMethod)
Return OpenMethodImplementation(methodToImplement, name, accessibility, hasMethodBodyDependency, associatedProperty)
End Function
Friend Function OpenMethodImplementation(interfaceMethod As SpecialMember, name As String, accessibility As Accessibility, Optional hasMethodBodyDependency As Boolean = False, Optional associatedProperty As PropertySymbol = Nothing) As SynthesizedMethod
Dim methodToImplement As MethodSymbol = DirectCast(Me.F.SpecialMember(interfaceMethod), MethodSymbol)
Return OpenMethodImplementation(methodToImplement, name, accessibility, hasMethodBodyDependency, associatedProperty)
End Function
Friend Function OpenMethodImplementation(interfaceType As NamedTypeSymbol, interfaceMethod As SpecialMember, name As String, accessibility As Accessibility, Optional hasMethodBodyDependency As Boolean = False, Optional associatedProperty As PropertySymbol = Nothing) As SynthesizedMethod
Dim methodToImplement As MethodSymbol = DirectCast(Me.F.SpecialMember(interfaceMethod), MethodSymbol).AsMember(interfaceType)
Return OpenMethodImplementation(methodToImplement, name, accessibility, hasMethodBodyDependency, associatedProperty)
End Function
Private Function OpenMethodImplementation(methodToImplement As MethodSymbol,
methodName As String,
accessibility As Accessibility,
Optional hasMethodBodyDependency As Boolean = False,
Optional associatedProperty As PropertySymbol = Nothing) As SynthesizedMethod
' Errors must be reported before and if any this point should not be reachable
Debug.Assert(methodToImplement IsNot Nothing AndAlso methodToImplement.GetUseSiteInfo().DiagnosticInfo Is Nothing)
Dim result As New SynthesizedStateMachineDebuggerNonUserCodeMethod(DirectCast(Me.F.CurrentType, StateMachineTypeSymbol),
methodName,
methodToImplement,
Me.F.Syntax,
accessibility,
hasMethodBodyDependency,
associatedProperty)
Me.F.AddMethod(Me.F.CurrentType, result)
Me.F.CurrentMethod = result
Return result
End Function
Friend Function OpenPropertyImplementation(interfaceProperty As SpecialMember, name As String, accessibility As Accessibility) As MethodSymbol
Dim methodToImplement As MethodSymbol = DirectCast(Me.F.SpecialMember(interfaceProperty), PropertySymbol).GetMethod
Return OpenPropertyImplementation(methodToImplement, name, accessibility)
End Function
Friend Function OpenPropertyImplementation(interfaceType As NamedTypeSymbol, interfaceMethod As SpecialMember, name As String, accessibility As Accessibility) As MethodSymbol
Dim methodToImplement As MethodSymbol = DirectCast(Me.F.SpecialMember(interfaceMethod), PropertySymbol).GetMethod.AsMember(interfaceType)
Return OpenPropertyImplementation(methodToImplement, name, accessibility)
End Function
Private Function OpenPropertyImplementation(getterToImplement As MethodSymbol, name As String, accessibility As Accessibility) As MethodSymbol
Dim prop As New SynthesizedStateMachineProperty(DirectCast(Me.F.CurrentType, StateMachineTypeSymbol),
name,
getterToImplement,
Me.F.Syntax,
accessibility)
Me.F.AddProperty(Me.F.CurrentType, prop)
Dim getter = prop.GetMethod
Me.F.AddMethod(Me.F.CurrentType, getter)
Me.F.CurrentMethod = getter
Return getter
End Function
Friend Sub CloseMethod(body As BoundStatement)
Me.F.CloseMethod(RewriteBodyIfNeeded(body, Me.F.TopLevelMethod, Me.F.CurrentMethod))
End Sub
Friend Overridable Function RewriteBodyIfNeeded(body As BoundStatement, topMethod As MethodSymbol, currentMethod As MethodSymbol) As BoundStatement
Return body
End Function
Friend Function OpenMoveNextMethodImplementation(interfaceMethod As WellKnownMember, accessibility As Accessibility) As SynthesizedMethod
Dim methodToImplement As MethodSymbol = Me.F.WellKnownMember(Of MethodSymbol)(interfaceMethod)
Return OpenMoveNextMethodImplementation(methodToImplement, accessibility)
End Function
Friend Function OpenMoveNextMethodImplementation(interfaceMethod As SpecialMember, accessibility As Accessibility) As SynthesizedMethod
Dim methodToImplement As MethodSymbol = DirectCast(Me.F.SpecialMember(interfaceMethod), MethodSymbol)
Return OpenMoveNextMethodImplementation(methodToImplement, accessibility)
End Function
Private Function OpenMoveNextMethodImplementation(methodToImplement As MethodSymbol, accessibility As Accessibility) As SynthesizedMethod
' Errors must be reported before and if any this point should not be reachable
Debug.Assert(methodToImplement IsNot Nothing AndAlso methodToImplement.GetUseSiteInfo().DiagnosticInfo Is Nothing)
Dim result As New SynthesizedStateMachineMoveNextMethod(DirectCast(Me.F.CurrentType, StateMachineTypeSymbol),
methodToImplement,
Me.F.Syntax,
accessibility)
Me.F.AddMethod(Me.F.CurrentType, result)
Me.F.CurrentMethod = result
Return result
End Function
End Class
End Namespace
|