File: Analysis\IteratorAndAsyncAnalysis\IteratorAndAsyncCaptureWalker.vb
Web Access
Project: src\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' 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.Generic
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind
 
Namespace Microsoft.CodeAnalysis.VisualBasic
 
    ''' <summary>
    ''' A walker that computes the set of local variables of an iterator 
    ''' method that must be moved to fields of the generated class.
    ''' </summary>
    Friend NotInheritable Class IteratorAndAsyncCaptureWalker
        Inherits DataFlowPass
 
        ' In Release builds we hoist only variables (locals And parameters) that are captured. 
        ' This set will contain such variables after the bound tree is visited.
        Private ReadOnly _variablesToHoist As OrderedSet(Of Symbol)
        Private ReadOnly _byRefLocalsInitializers As Dictionary(Of LocalSymbol, BoundExpression)
 
        ' Contains variables that are captured but can't be hoisted since their type can't be allocated on heap.
        ' The value is a list of all usage of each such variable.
        Private _lazyDisallowedCaptures As MultiDictionary(Of Symbol, SyntaxNode)
 
        Public Structure Result
            Public ReadOnly CapturedLocals As OrderedSet(Of Symbol)
            Public ReadOnly ByRefLocalsInitializers As Dictionary(Of LocalSymbol, BoundExpression)
 
            Friend Sub New(cl As OrderedSet(Of Symbol), initializers As Dictionary(Of LocalSymbol, BoundExpression))
                Me.CapturedLocals = cl
                Me.ByRefLocalsInitializers = initializers
            End Sub
        End Structure
 
        Public Sub New(info As FlowAnalysisInfo)
            MyBase.New(info, Nothing, suppressConstExpressionsSupport:=False, trackStructsWithIntrinsicTypedFields:=True, trackUnassignments:=True)
 
            Me._variablesToHoist = New OrderedSet(Of Symbol)()
            Me._byRefLocalsInitializers = New Dictionary(Of LocalSymbol, BoundExpression)()
        End Sub
 
        ' Returns deterministically ordered list of variables that ought to be hoisted.
        Public Overloads Shared Function Analyze(info As FlowAnalysisInfo, diagnostics As DiagnosticBag) As Result
            Debug.Assert(info.Symbol IsNot Nothing)
            Debug.Assert(info.Symbol.Kind = SymbolKind.Method)
 
            Dim walker As New IteratorAndAsyncCaptureWalker(info)
 
            walker._convertInsufficientExecutionStackExceptionToCancelledByStackGuardException = True
 
            walker.Analyze()
            Debug.Assert(Not walker.InvalidRegionDetected)
 
            Dim variablesToHoist = walker._variablesToHoist
            Dim allVariables = walker.variableBySlot
            Dim byRefLocalsInitializers = walker._byRefLocalsInitializers
            Dim lazyDisallowedCaptures = walker._lazyDisallowedCaptures
 
            walker.Free()
 
            If lazyDisallowedCaptures IsNot Nothing Then
                For Each variable In lazyDisallowedCaptures.Keys
                    Dim type As TypeSymbol = If(variable.Kind = SymbolKind.Local, TryCast(variable, LocalSymbol).Type, TryCast(variable, ParameterSymbol).Type)
                    For Each node In lazyDisallowedCaptures(variable)
                        ' Variable of restricted type '{0}' cannot be declared in an Async or Iterator method.
                        diagnostics.Add(ERRID.ERR_CannotLiftRestrictedTypeResumable1, node.GetLocation(), type)
                    Next
                Next
            End If
 
            If info.Compilation.Options.OptimizationLevel <> OptimizationLevel.Release Then
                Debug.Assert(variablesToHoist.Count = 0)
 
                ' In debug build we hoist all locals and parameters, except ByRef locals in iterator methods.
                ' Lifetime of ByRef locals in iterator methods never crosses statement boundaries, thus 
                ' there is no reason to hoist them and the pipeline doesn't handle this.
                Dim skipByRefLocals As Boolean = DirectCast(info.Symbol, MethodSymbol).IsIterator
                For Each v In allVariables
                    If v.Symbol IsNot Nothing AndAlso HoistInDebugBuild(v.Symbol, skipByRefLocals) Then
                        variablesToHoist.Add(v.Symbol)
                    End If
                Next
            End If
 
            Return New Result(variablesToHoist, byRefLocalsInitializers)
        End Function
 
        Private Shared Function HoistInDebugBuild(symbol As Symbol, skipByRefLocals As Boolean) As Boolean
            ' In debug build hoist all parameters that can be hoisted:
            If symbol.Kind = SymbolKind.Parameter Then
                Dim parameter = TryCast(symbol, ParameterSymbol)
                Return Not parameter.Type.IsRestrictedType()
            End If
 
            If symbol.Kind = SymbolKind.Local Then
                Dim local = TryCast(symbol, LocalSymbol)
                If local.IsConst Then
                    Return False
                End If
 
                If skipByRefLocals AndAlso local.IsByRef Then
                    Return False
                End If
 
                ' hoist all user-defined locals that can be hoisted
                If local.SynthesizedKind = SynthesizedLocalKind.UserDefined Then
                    Return Not local.Type.IsRestrictedType()
                End If
 
                ' hoist all synthesized variables that have to survive state machine suspension
                Return local.SynthesizedKind <> SynthesizedLocalKind.ConditionalBranchDiscriminator
            End If
 
            Return False
        End Function
 
        Protected Overrides Function Scan() As Boolean
            Me._variablesToHoist.Clear()
            Me._byRefLocalsInitializers.Clear()
            Me._lazyDisallowedCaptures?.Clear()
 
            Return MyBase.Scan()
        End Function
 
        Private Sub CaptureVariable(variable As Symbol, syntax As SyntaxNode)
            Dim type As TypeSymbol = If(variable.Kind = SymbolKind.Local, TryCast(variable, LocalSymbol).Type, TryCast(variable, ParameterSymbol).Type)
            If type.IsRestrictedType() Then
                ' Error has already been reported:
                If TypeOf variable Is SynthesizedLocal Then
                    Return
                End If
 
                If _lazyDisallowedCaptures Is Nothing Then
                    _lazyDisallowedCaptures = New MultiDictionary(Of Symbol, SyntaxNode)()
                End If
 
                _lazyDisallowedCaptures.Add(variable, syntax)
 
            ElseIf compilation.Options.OptimizationLevel = OptimizationLevel.Release Then
                ' In debug build we hoist all locals and parameters after walk:
                Me._variablesToHoist.Add(variable)
            End If
 
        End Sub
 
        Protected Overrides Sub EnterParameter(parameter As ParameterSymbol)
            ' parameters are NOT initially assigned here - if that is a problem, then
            ' the parameters must be captured.
            GetOrCreateSlot(parameter)
 
            ' Instead of analyzing which parameters are actually being referenced
            ' we add all of them; this might need to be revised later
            CaptureVariable(parameter, Nothing)
        End Sub
 
        Protected Overrides Sub ReportUnassigned(symbol As Symbol, node As SyntaxNode, rwContext As ReadWriteContext, Optional slot As Integer = -1, Optional boundFieldAccess As BoundFieldAccess = Nothing)
            If symbol.Kind = SymbolKind.Field Then
                Dim sym As Symbol = GetNodeSymbol(boundFieldAccess)
 
                ' Unreachable for AmbiguousLocalsPseudoSymbol: ambiguous implicit 
                ' receiver should not ever be considered unassigned
                Debug.Assert(Not TypeOf sym Is AmbiguousLocalsPseudoSymbol)
 
                If sym IsNot Nothing Then
                    CaptureVariable(sym, node)
                End If
 
            ElseIf symbol.Kind = SymbolKind.Parameter OrElse symbol.Kind = SymbolKind.Local Then
                CaptureVariable(symbol, node)
            End If
        End Sub
 
        Protected Overrides ReadOnly Property IgnoreOutSemantics As Boolean
            Get
                Return False
            End Get
        End Property
 
        Protected Overrides Function IsEmptyStructType(type As TypeSymbol) As Boolean
            Return False
        End Function
 
        Protected Overrides ReadOnly Property EnableBreakingFlowAnalysisFeatures As Boolean
            Get
                Return True
            End Get
        End Property
 
        Private Sub MarkLocalsUnassigned()
            For i = SlotKind.FirstAvailable To nextVariableSlot - 1
                Dim symbol As Symbol = variableBySlot(i).Symbol
                Select Case symbol.Kind
                    Case SymbolKind.Local
                        If Not DirectCast(symbol, LocalSymbol).IsConst Then
                            SetSlotState(i, False)
                        End If
                    Case SymbolKind.Parameter
                        SetSlotState(i, False)
                End Select
            Next
        End Sub
 
        Public Overrides Function VisitAwaitOperator(node As BoundAwaitOperator) As BoundNode
            MyBase.VisitAwaitOperator(node)
            MarkLocalsUnassigned()
            Return Nothing
        End Function
 
        Protected Overrides Sub VisitCallAfterVisitArguments(node As BoundCall)
            MyBase.VisitCallAfterVisitArguments(node)
 
            Dim receiverOpt As BoundExpression = node.ReceiverOpt
 
            If receiverOpt IsNot Nothing AndAlso Not receiverOpt.IsLValue Then
                CheckAssignedFromArgumentWrite(receiverOpt, receiverOpt.Syntax)
            End If
        End Sub
 
        Public Overrides Function VisitSequence(node As BoundSequence) As BoundNode
            Dim result As BoundNode = Nothing
 
            For Each local In node.Locals
                SetSlotState(GetOrCreateSlot(local), True)
            Next
            result = MyBase.VisitSequence(node)
            For Each local In node.Locals
                CheckAssigned(local, node.Syntax)
            Next
 
            Return result
        End Function
 
        Protected Overrides ReadOnly Property ProcessCompilerGeneratedLocals As Boolean
            Get
                Return True
            End Get
        End Property
 
        Public Overrides Function VisitReferenceAssignment(node As BoundReferenceAssignment) As BoundNode
            Dim local As LocalSymbol = node.ByRefLocal.LocalSymbol
 
            Debug.Assert(Not Me._byRefLocalsInitializers.ContainsKey(local))
            Me._byRefLocalsInitializers.Add(local, node.LValue)
 
            Return MyBase.VisitReferenceAssignment(node)
        End Function
 
        Public Overrides Function VisitYieldStatement(node As BoundYieldStatement) As BoundNode
            MyBase.VisitYieldStatement(node)
            MarkLocalsUnassigned()
            Return Nothing
        End Function
 
        Protected Overrides Function TreatTheLocalAsAssignedWithinTheLambda(local As LocalSymbol, right As BoundExpression) As Boolean
            ' By the time this analysis is invoked, Lambda conversion 
            ' is already rewritten into an object creation
            If right.Kind = BoundKind.ObjectCreationExpression Then
                Dim objCreation = DirectCast(right, BoundObjectCreationExpression)
                If TypeOf objCreation.Type Is LambdaFrame AndAlso objCreation.Arguments.Length = 1 Then
                    Dim arg0 As BoundExpression = objCreation.Arguments(0)
                    If arg0.Kind = BoundKind.Local AndAlso DirectCast(arg0, BoundLocal).LocalSymbol Is local Then
                        Return True
                    End If
                End If
            End If
            Return False
        End Function
    End Class
 
End Namespace