' Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Namespace Microsoft.CodeAnalysis.VisualBasic
Partial Friend NotInheritable Class IteratorRewriter
Inherits StateMachineRewriter(Of FieldSymbol)
Private ReadOnly elementType As TypeSymbol
Private ReadOnly isEnumerable As Boolean
Private currentField As FieldSymbol
Private initialThreadIdField As FieldSymbol
Public Sub New(body As BoundStatement,
method As MethodSymbol,
isEnumerable As Boolean,
stateMachineType As IteratorStateMachine,
slotAllocatorOpt As VariableSlotAllocator,
compilationState As TypeCompilationState,
diagnostics As DiagnosticBag)
MyBase.New(body, method, stateMachineType, slotAllocatorOpt, compilationState, diagnostics)
Me.isEnumerable = isEnumerable
Dim methodReturnType = method.ReturnType
If methodReturnType.GetArity = 0 Then
Me.elementType = method.ContainingAssembly.GetSpecialType(SpecialType.System_Object)
Else
' the element type may contain method type parameters, which are now alpha-renamed into type parameters of the generated class
Me.elementType = DirectCast(methodReturnType, NamedTypeSymbol).TypeArgumentsNoUseSiteDiagnostics().Single().InternalSubstituteTypeParameters(Me.TypeMap)
End If
End Sub
'''
''' Rewrite an iterator method into a state machine class.
'''
Friend Overloads Shared Function Rewrite(body As BoundBlock,
method As MethodSymbol,
methodOrdinal As Integer,
slotAllocatorOpt As VariableSlotAllocator,
compilationState As TypeCompilationState,
diagnostics As DiagnosticBag,
ByRef stateMachineType As IteratorStateMachine) As BoundBlock
If body.HasErrors Or Not method.IsIterator Then
Return body
End If
Dim methodReturnType As TypeSymbol = method.ReturnType
Dim retSpecialType = method.ReturnType.OriginalDefinition.SpecialType
Dim isEnumerable As Boolean = retSpecialType = SpecialType.System_Collections_Generic_IEnumerable_T OrElse
retSpecialType = SpecialType.System_Collections_IEnumerable
Dim elementType As TypeSymbol
If method.ReturnType.IsDefinition Then
elementType = method.ContainingAssembly.GetSpecialType(SpecialType.System_Object)
Else
elementType = DirectCast(methodReturnType, NamedTypeSymbol).TypeArgumentsNoUseSiteDiagnostics(0)
End If
stateMachineType = New IteratorStateMachine(slotAllocatorOpt, compilationState, method, methodOrdinal, elementType, isEnumerable)
If compilationState.ModuleBuilderOpt IsNot Nothing Then
compilationState.ModuleBuilderOpt.CompilationState.SetStateMachineType(method, stateMachineType)
End If
Dim rewriter As New IteratorRewriter(body, method, isEnumerable, stateMachineType, slotAllocatorOpt, compilationState, diagnostics)
' check if we have all the types we need
If rewriter.EnsureAllSymbolsAndSignature() Then
Return body
End If
Return rewriter.Rewrite()
End Function
Friend Overrides Function EnsureAllSymbolsAndSignature() As Boolean
Dim hasErrors As Boolean = MyBase.EnsureAllSymbolsAndSignature
If Me.Method.IsSub OrElse Me.elementType.IsErrorType Then
hasErrors = True
End If
' NOTE: in current implementation these attributes must exist
' TODO: change to "don't use if not found"
EnsureWellKnownMember(Of MethodSymbol)(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor, hasErrors)
EnsureWellKnownMember(Of MethodSymbol)(WellKnownMember.System_Diagnostics_DebuggerNonUserCodeAttribute__ctor, hasErrors)
' NOTE: We don't ensure DebuggerStepThroughAttribute, it is just not emitted if not found
' EnsureWellKnownMember(Of MethodSymbol)(WellKnownMember.System_Diagnostics_DebuggerStepThroughAttribute__ctor, hasErrors)
' TODO: do we need these here? They are used on the actual iterator method.
' EnsureWellKnownMember(Of MethodSymbol)(WellKnownMember.System_Runtime_CompilerServices_IteratorStateMachine__ctor, hasErrors)
EnsureSpecialType(SpecialType.System_Object, hasErrors)
EnsureSpecialType(SpecialType.System_Boolean, hasErrors)
EnsureSpecialType(SpecialType.System_Int32, hasErrors)
If Me.Method.ReturnType.IsDefinition Then
If Me.isEnumerable Then
EnsureSpecialType(SpecialType.System_Collections_IEnumerator, hasErrors)
End If
Else
If Me.isEnumerable Then
EnsureSpecialType(SpecialType.System_Collections_Generic_IEnumerator_T, hasErrors)
EnsureSpecialType(SpecialType.System_Collections_IEnumerable, hasErrors)
End If
EnsureSpecialType(SpecialType.System_Collections_IEnumerator, hasErrors)
End If
EnsureSpecialType(SpecialType.System_IDisposable, hasErrors)
Return hasErrors
End Function
Protected Overrides Sub GenerateControlFields()
' Add a field: int _state
Me.StateField = Me.F.StateMachineField(Me.F.SpecialType(SpecialType.System_Int32), Me.Method, GeneratedNames.MakeStateMachineStateFieldName(), Accessibility.Public)
' Add a field: T current
currentField = F.StateMachineField(elementType, Me.Method, GeneratedNames.MakeIteratorCurrentFieldName(), Accessibility.Public)
' if it is an Enumerable, add a field: initialThreadId As Integer
initialThreadIdField = If(isEnumerable,
F.StateMachineField(F.SpecialType(SpecialType.System_Int32), Me.Method, GeneratedNames.MakeIteratorInitialThreadIdName(), Accessibility.Public),
Nothing)
End Sub
Protected Overrides Sub GenerateMethodImplementations()
Dim managedThreadId As BoundExpression = Nothing ' Thread.CurrentThread.ManagedThreadId
' Add bool IEnumerator.MoveNext() and void IDisposable.Dispose()
Dim disposeMethod = Me.OpenMethodImplementation(SpecialMember.System_IDisposable__Dispose,
"Dispose",
DebugAttributes.DebuggerNonUserCodeAttribute,
Accessibility.Private,
generateDebugInfo:=False,
hasMethodBodyDependency:=True)
Dim debuggerHidden = IsDebuggerHidden(Me.Method)
Dim moveNextAttrs As DebugAttributes = DebugAttributes.CompilerGeneratedAttribute
If debuggerHidden Then moveNextAttrs = moveNextAttrs Or DebugAttributes.DebuggerHiddenAttribute
Dim moveNextMethod = Me.OpenMethodImplementation(SpecialMember.System_Collections_IEnumerator__MoveNext,
"MoveNext",
moveNextAttrs,
Accessibility.Private,
generateDebugInfo:=True,
hasMethodBodyDependency:=True)
GenerateMoveNextAndDispose(moveNextMethod, disposeMethod)
F.CurrentMethod = moveNextMethod
If isEnumerable Then
' generate the code for GetEnumerator()
' IEnumerable result;
' if (this.initialThreadId == Thread.CurrentThread.ManagedThreadId && this.state == -2)
' {
' this.state = 0;
' result = this;
' }
' else
' {
' result = new Ints0_Impl(0);
' }
' result.parameter = this.parameterProxy; ' copy all of the parameter proxies
' Add IEnumerator IEnumerable.GetEnumerator()
Dim getEnumeratorGeneric = Me.OpenMethodImplementation(F.SpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).Construct(elementType),
SpecialMember.System_Collections_Generic_IEnumerable_T__GetEnumerator,
"GetEnumerator",
DebugAttributes.DebuggerNonUserCodeAttribute,
Accessibility.Private,
generateDebugInfo:=False,
hasMethodBodyDependency:=False)
Dim bodyBuilder = ArrayBuilder(Of BoundStatement).GetInstance()
Dim resultVariable = F.SynthesizedLocal(StateMachineType) ' iteratorClass result;
Dim currentManagedThreadIdMethod As MethodSymbol = Nothing
Dim currentManagedThreadIdProperty As PropertySymbol = F.WellKnownMember(Of PropertySymbol)(WellKnownMember.System_Environment__CurrentManagedThreadId, isOptional:=True)
If (currentManagedThreadIdProperty IsNot Nothing) Then
currentManagedThreadIdMethod = currentManagedThreadIdProperty.GetMethod()
End If
If (currentManagedThreadIdMethod IsNot Nothing) Then
managedThreadId = F.Call(Nothing, currentManagedThreadIdMethod)
Else
managedThreadId = F.Property(F.Property(WellKnownMember.System_Threading_Thread__CurrentThread), WellKnownMember.System_Threading_Thread__ManagedThreadId)
End If
' if (this.state == -2 && this.initialThreadId == Thread.CurrentThread.ManagedThreadId)
' this.state = 0;
' result = this;
' goto thisInitialized
' else
' result = new IteratorClass(0)
' ' initialize [Me] if needed
' thisInitialized:
' ' initialize other fields
Dim thisInitialized = F.GenerateLabel("thisInitialized")
bodyBuilder.Add(
F.If(
condition:=
F.LogicalAndAlso(
F.IntEqual(F.Field(F.Me, StateField, False), F.Literal(StateMachineStates.FinishedStateMachine)),
F.IntEqual(F.Field(F.Me, initialThreadIdField, False), managedThreadId)),
thenClause:=
F.Block(
F.Assignment(F.Field(F.Me, StateField, True), F.Literal(StateMachineStates.FirstUnusedState)),
F.Assignment(F.Local(resultVariable, True), F.Me),
If(Method.IsShared OrElse Method.MeParameter.Type.IsReferenceType,
F.Goto(thisInitialized),
DirectCast(F.Block(), BoundStatement))
),
elseClause:=
F.Assignment(F.Local(resultVariable, True), F.[New](StateMachineType.Constructor, F.Literal(0)))
))
' Initialize all the parameter copies
Dim copySrc = InitialParameters
Dim copyDest = nonReusableLocalProxies
If Not Method.IsShared Then
' starting with "this"
Dim proxy As FieldSymbol = Nothing
If (copyDest.TryGetValue(Method.MeParameter, proxy)) Then
bodyBuilder.Add(
F.Assignment(
F.Field(F.Local(resultVariable, True), proxy.AsMember(StateMachineType), True),
F.Field(F.Me, copySrc(Method.MeParameter).AsMember(F.CurrentType), False)))
End If
End If
bodyBuilder.Add(F.Label(thisInitialized))
For Each parameter In Method.Parameters
Dim proxy As FieldSymbol = Nothing
If (copyDest.TryGetValue(parameter, proxy)) Then
bodyBuilder.Add(
F.Assignment(
F.Field(F.Local(resultVariable, True), proxy.AsMember(StateMachineType), True),
F.Field(F.Me, copySrc(parameter).AsMember(F.CurrentType), False)))
End If
Next
bodyBuilder.Add(F.Return(F.Local(resultVariable, False)))
F.CloseMethod(F.Block(ImmutableArray.Create(resultVariable), bodyBuilder.ToImmutableAndFree()))
' Generate IEnumerable.GetEnumerator
' NOTE: this is a private implementing method. Its name is irrelevant
' but must be different from another GetEnumerator. Dev11 uses GetEnumerator0 here.
' IEnumerable.GetEnumerator seems better -
' it is clear why we have the property, and "Current" suffix will be shared in metadata with another Current.
' It is also consistent with the naming of IEnumerable.Current (see below).
Me.OpenMethodImplementation(SpecialMember.System_Collections_IEnumerable__GetEnumerator,
"IEnumerable.GetEnumerator",
DebugAttributes.DebuggerNonUserCodeAttribute,
Accessibility.Private,
generateDebugInfo:=False,
hasMethodBodyDependency:=False)
F.CloseMethod(F.Return(F.Call(F.Me, getEnumeratorGeneric)))
End If
' Add T IEnumerator.Current
Me.OpenPropertyImplementation(F.SpecialType(SpecialType.System_Collections_Generic_IEnumerator_T).Construct(elementType),
SpecialMember.System_Collections_Generic_IEnumerator_T__Current,
"Current",
DebugAttributes.DebuggerNonUserCodeAttribute,
Accessibility.Private,
False)
F.CloseMethod(F.Return(F.Field(F.Me, currentField, False)))
' Add void IEnumerator.Reset()
Me.OpenMethodImplementation(SpecialMember.System_Collections_IEnumerator__Reset,
"Reset",
DebugAttributes.DebuggerNonUserCodeAttribute,
Accessibility.Private,
generateDebugInfo:=False,
hasMethodBodyDependency:=False)
F.CloseMethod(F.Throw(F.[New](F.WellKnownType(WellKnownType.System_NotSupportedException))))
' Add object IEnumerator.Current
' NOTE: this is a private implementing property. Its name is irrelevant
' but must be different from another Current.
' Dev11 uses fully qualified and substituted name here (System.Collections.Generic.IEnumerator(Of Object).Current),
' It may be an overkill and may lead to metadata bloat.
' IEnumerable.Current seems better -
' it is clear why we have the property, and "Current" suffix will be shared in metadata with another Current.
Me.OpenPropertyImplementation(SpecialMember.System_Collections_IEnumerator__Current,
"IEnumerator.Current",
DebugAttributes.DebuggerNonUserCodeAttribute,
Accessibility.Private,
False)
F.CloseMethod(F.Return(F.Field(F.Me, currentField, False)))
' Add a body for the constructor
If True Then
F.CurrentMethod = StateMachineType.Constructor
Dim bodyBuilder = ArrayBuilder(Of BoundStatement).GetInstance()
bodyBuilder.Add(F.BaseInitialization())
bodyBuilder.Add(F.Assignment(F.Field(F.Me, StateField, True), F.Parameter(F.CurrentMethod.Parameters(0)).MakeRValue)) ' this.state = state
If isEnumerable Then
' this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
bodyBuilder.Add(F.Assignment(F.Field(F.Me, initialThreadIdField, True), managedThreadId))
End If
bodyBuilder.Add(F.Return())
F.CloseMethod(F.Block(bodyBuilder.ToImmutableAndFree()))
bodyBuilder = Nothing
End If
End Sub
Protected Overrides Function GenerateStateMachineCreation(stateMachineVariable As LocalSymbol, frameType As NamedTypeSymbol) As BoundStatement
Return F.Return(F.Local(stateMachineVariable, False))
End Function
Protected Overrides Sub InitializeStateMachine(bodyBuilder As ArrayBuilder(Of BoundStatement), frameType As NamedTypeSymbol, stateMachineLocal As LocalSymbol)
' Dim stateMachineLocal As new IteratorImplementationClass(N)
' where N is either 0 (if we're producing an enumerator) or -2 (if we're producing an enumerable)
Dim initialState = If(isEnumerable, StateMachineStates.FinishedStateMachine, StateMachineStates.FirstUnusedState)
bodyBuilder.Add(
F.Assignment(
F.Local(stateMachineLocal, True),
F.[New](StateMachineType.Constructor.AsMember(frameType), F.Literal(initialState))))
End Sub
Protected Overrides ReadOnly Property PreserveInitialParameterValues As Boolean
Get
Return Me.isEnumerable
End Get
End Property
Friend Overrides ReadOnly Property TypeMap As TypeSubstitution
Get
Return StateMachineType.TypeSubstitution
End Get
End Property
Private Sub GenerateMoveNextAndDispose(moveNextMethod As SynthesizedStateMachineMethod, disposeMethod As SynthesizedStateMachineMethod)
Dim rewriter = New IteratorMethodToClassRewriter(method:=Me.Method,
F:=Me.F,
state:=Me.StateField,
current:=Me.currentField,
HoistedVariables:=Me.hoistedVariables,
localProxies:=Me.nonReusableLocalProxies,
SynthesizedLocalOrdinals:=Me.SynthesizedLocalOrdinals,
slotAllocatorOpt:=Me.SlotAllocatorOpt,
nextFreeHoistedLocalSlot:=Me.nextFreeHoistedLocalSlot,
diagnostics:=Diagnostics)
rewriter.GenerateMoveNextAndDispose(Body, moveNextMethod, disposeMethod)
End Sub
Protected Overrides Function CreateByValLocalCapture(field As FieldSymbol, local As LocalSymbol) As FieldSymbol
Return field
End Function
Protected Overrides Function CreateParameterCapture(field As FieldSymbol, parameter As ParameterSymbol) As FieldSymbol
Return field
End Function
Protected Overrides Sub InitializeParameterWithProxy(parameter As ParameterSymbol, proxy As FieldSymbol, stateMachineVariable As LocalSymbol, initializers As ArrayBuilder(Of BoundExpression))
Debug.Assert(proxy IsNot Nothing)
Dim frameType As NamedTypeSymbol = If(Me.Method.IsGenericMethod,
Me.StateMachineType.Construct(Me.Method.TypeArguments),
Me.StateMachineType)
Dim expression As BoundExpression = If(parameter.IsMe,
DirectCast(Me.F.Me, BoundExpression),
Me.F.Parameter(parameter).MakeRValue())
initializers.Add(
Me.F.AssignmentExpression(
Me.F.Field(
Me.F.Local(stateMachineVariable, True),
proxy.AsMember(frameType),
True),
expression))
End Sub
End Class
End Namespace