|
' 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.Reflection.Metadata
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Emit
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen
Friend NotInheritable Class CodeGenerator
Private ReadOnly _method As MethodSymbol
Private ReadOnly _block As BoundStatement
Private ReadOnly _builder As ILBuilder
Private ReadOnly _module As PEModuleBuilder
Private ReadOnly _diagnostics As DiagnosticBag
Private ReadOnly _ilEmitStyle As ILEmitStyle
Private ReadOnly _emitPdbSequencePoints As Boolean
Private ReadOnly _stackLocals As HashSet(Of LocalSymbol) = Nothing
''' <summary> Keeps track on current nesting level of try statements </summary>
Private _tryNestingLevel As Integer = 0
''' <summary> Current enclosing Catch block if there is any. </summary>
Private _currentCatchBlock As BoundCatchBlock = Nothing
Private ReadOnly _synthesizedLocalOrdinals As SynthesizedLocalOrdinalsDispenser = New SynthesizedLocalOrdinalsDispenser()
Private _uniqueNameId As Integer
' label used when return is emitted in a form of store/goto
Private Shared ReadOnly s_returnLabel As New Object
Private _unhandledReturn As Boolean
Private _checkCallsForUnsafeJITOptimization As Boolean
Private _asyncCatchHandlerOffset As Integer = -1
Private _asyncYieldPoints As ArrayBuilder(Of Integer) = Nothing
Private _asyncResumePoints As ArrayBuilder(Of Integer) = Nothing
Public Sub New(method As MethodSymbol,
boundBody As BoundStatement,
builder As ILBuilder,
moduleBuilder As PEModuleBuilder,
diagnostics As DiagnosticBag,
optimizations As OptimizationLevel,
emittingPdb As Boolean)
Debug.Assert(method IsNot Nothing)
Debug.Assert(boundBody IsNot Nothing)
Debug.Assert(builder IsNot Nothing)
Debug.Assert(moduleBuilder IsNot Nothing)
Debug.Assert(diagnostics IsNot Nothing)
_method = method
_block = boundBody
_builder = builder
_module = moduleBuilder
_diagnostics = diagnostics
'Always optimize synthesized methods that don't contain user code.
If Not method.GenerateDebugInfo Then
_ilEmitStyle = ILEmitStyle.Release
Else
If optimizations = OptimizationLevel.Debug Then
_ilEmitStyle = ILEmitStyle.Debug
Else
_ilEmitStyle = If(IsDebugPlus(),
ILEmitStyle.DebugFriendlyRelease,
ILEmitStyle.Release)
End If
End If
' Emit sequence points unless
' - the PDBs are not being generated
' - debug information for the method is not generated since the method does not contain
' user code that can be stepped through, or changed during EnC.
'
' This setting only affects generating PDB sequence points, it shall Not affect generated IL in any way.
_emitPdbSequencePoints = emittingPdb AndAlso method.GenerateDebugInfo
Try
_block = Optimizer.Optimize(method, boundBody, debugFriendly:=_ilEmitStyle <> ILEmitStyle.Release, stackLocals:=_stackLocals)
Catch ex As BoundTreeVisitor.CancelledByStackGuardException
ex.AddAnError(diagnostics)
_block = boundBody
End Try
_checkCallsForUnsafeJITOptimization = (_method.ImplementationAttributes And MethodSymbol.DisableJITOptimizationFlags) <> MethodSymbol.DisableJITOptimizationFlags
Debug.Assert(Not _module.JITOptimizationIsDisabled(_method))
End Sub
Private Function IsDebugPlus() As Boolean
Return Me._module.Compilation.Options.DebugPlusMode
End Function
Public Sub Generate()
Debug.Assert(_asyncYieldPoints Is Nothing)
Debug.Assert(_asyncResumePoints Is Nothing)
Debug.Assert(_asyncCatchHandlerOffset < 0)
GenerateImpl()
End Sub
Public Sub Generate(<Out> ByRef asyncCatchHandlerOffset As Integer,
<Out> ByRef asyncYieldPoints As ImmutableArray(Of Integer),
<Out> ByRef asyncResumePoints As ImmutableArray(Of Integer))
GenerateImpl()
Debug.Assert(_asyncCatchHandlerOffset >= 0)
asyncCatchHandlerOffset = _builder.GetILOffsetFromMarker(_asyncCatchHandlerOffset)
Dim yieldPoints As ArrayBuilder(Of Integer) = _asyncYieldPoints
Dim resumePoints As ArrayBuilder(Of Integer) = _asyncResumePoints
Debug.Assert((yieldPoints Is Nothing) = (resumePoints Is Nothing))
If yieldPoints Is Nothing Then
asyncYieldPoints = ImmutableArray(Of Integer).Empty
asyncResumePoints = ImmutableArray(Of Integer).Empty
Else
Debug.Assert(yieldPoints.Count > 0, "Why it was allocated?")
Dim yieldPointsBuilder = ArrayBuilder(Of Integer).GetInstance
Dim resumePointsBuilder = ArrayBuilder(Of Integer).GetInstance
For i = 0 To yieldPoints.Count - 1
Dim yieldOffset = _builder.GetILOffsetFromMarker(yieldPoints(i))
Dim resumeOffset = _builder.GetILOffsetFromMarker(resumePoints(i))
Debug.Assert(resumeOffset >= 0) ' resume marker should always be reachable from dispatch
' yield point may not be reachable if the whole
' await is not reachable; we just ignore such awaits
If yieldOffset > 0 Then
yieldPointsBuilder.Add(yieldOffset)
resumePointsBuilder.Add(resumeOffset)
End If
Next
asyncYieldPoints = yieldPointsBuilder.ToImmutableAndFree()
asyncResumePoints = resumePointsBuilder.ToImmutableAndFree()
yieldPoints.Free()
resumePoints.Free()
End If
End Sub
Private Sub GenerateImpl()
SetInitialDebugDocument()
' Synthesized methods should have a sequence point
' at offset 0 to ensure correct stepping behavior.
If _emitPdbSequencePoints AndAlso _method.IsImplicitlyDeclared Then
_builder.DefineInitialHiddenSequencePoint()
End If
Try
EmitStatement(_block)
If _unhandledReturn Then
HandleReturn()
End If
If Not _diagnostics.HasAnyErrors Then
_builder.Realize()
End If
Catch e As EmitCancelledException
Debug.Assert(_diagnostics.HasAnyErrors())
End Try
_synthesizedLocalOrdinals.Free()
End Sub
Private Sub HandleReturn()
_builder.MarkLabel(s_returnLabel)
_builder.EmitRet(True)
_unhandledReturn = False
End Sub
Private Function IsStackLocal(local As LocalSymbol) As Boolean
Return _stackLocals IsNot Nothing AndAlso _stackLocals.Contains(local)
End Function
Private Sub EmitSymbolToken(symbol As FieldSymbol, syntaxNode As SyntaxNode)
_builder.EmitToken(_module.Translate(symbol, syntaxNode, _diagnostics), syntaxNode, _diagnostics)
End Sub
Private Sub EmitSymbolToken(symbol As MethodSymbol, syntaxNode As SyntaxNode, Optional encodeAsRawDefinitionToken As Boolean = False)
Dim methodRef = _module.Translate(symbol, syntaxNode, _diagnostics, needDeclaration:=encodeAsRawDefinitionToken)
_builder.EmitToken(methodRef, syntaxNode, _diagnostics, If(encodeAsRawDefinitionToken, Cci.MetadataWriter.RawTokenEncoding.RowId, Cci.MetadataWriter.RawTokenEncoding.None))
End Sub
Private Sub EmitSymbolToken(symbol As TypeSymbol, syntaxNode As SyntaxNode)
_builder.EmitToken(_module.Translate(symbol, syntaxNode, _diagnostics), syntaxNode, _diagnostics)
End Sub
Private Sub EmitSequencePointExpression(node As BoundSequencePointExpression, used As Boolean)
Dim syntax = node.Syntax
If _emitPdbSequencePoints Then
If syntax Is Nothing Then
EmitHiddenSequencePoint()
Else
EmitSequencePoint(syntax)
End If
End If
' used is true to ensure that something is emitted
EmitExpression(node.Expression, used:=True)
EmitPopIfUnused(used)
End Sub
Private Sub EmitSequencePointExpressionAddress(node As BoundSequencePointExpression, addressKind As AddressKind)
Dim syntax = node.Syntax
If _emitPdbSequencePoints Then
If syntax Is Nothing Then
EmitHiddenSequencePoint()
Else
EmitSequencePoint(syntax)
End If
End If
Dim temp = EmitAddress(node.Expression, addressKind)
Debug.Assert(temp Is Nothing, "we should not be taking ref of a sequence if value needs a temp")
End Sub
Private Sub EmitSequencePointStatement(node As BoundSequencePoint)
Dim syntax = node.Syntax
If _emitPdbSequencePoints Then
If syntax Is Nothing Then
EmitHiddenSequencePoint()
Else
EmitSequencePoint(syntax)
End If
End If
Dim statement = node.StatementOpt
Dim instructionsEmitted As Integer = 0
If statement IsNot Nothing Then
instructionsEmitted = EmitStatementAndCountInstructions(statement)
End If
If instructionsEmitted = 0 AndAlso syntax IsNot Nothing AndAlso _ilEmitStyle = ILEmitStyle.Debug Then
' if there was no code emitted, then emit nop
' otherwise this point could get associated with some random statement, possibly in a wrong scope
_builder.EmitOpCode(ILOpCode.Nop)
End If
End Sub
Private Sub EmitSequencePointStatement(node As BoundSequencePointWithSpan)
Dim span = node.Span
If span <> Nothing AndAlso _emitPdbSequencePoints Then
EmitSequencePoint(node.SyntaxTree, span)
End If
Dim statement = node.StatementOpt
Dim instructionsEmitted As Integer = 0
If statement IsNot Nothing Then
instructionsEmitted = EmitStatementAndCountInstructions(statement)
End If
If instructionsEmitted = 0 AndAlso span <> Nothing AndAlso _ilEmitStyle = ILEmitStyle.Debug Then
' if there was no code emitted, then emit nop
' otherwise this point could get associated with some random statement, possibly in a wrong scope
_builder.EmitOpCode(ILOpCode.Nop)
End If
End Sub
Private Sub SetInitialDebugDocument()
Dim methodBlockSyntax = Me._method.Syntax
If _emitPdbSequencePoints AndAlso methodBlockSyntax IsNot Nothing Then
' If methodBlockSyntax is available (i.e. we're in a SourceMethodSymbol), then
' provide the IL builder with our best guess at the appropriate debug document.
' If we don't and this is hidden sequence point precedes all non-hidden sequence
' points, then the IL Builder will drop the sequence point for lack of a document.
' This negatively impacts the scenario where we insert hidden sequence points at
' the beginnings of methods so that step-into (F11) will handle them correctly.
_builder.SetInitialDebugDocument(methodBlockSyntax.SyntaxTree)
End If
End Sub
Private Sub EmitHiddenSequencePoint()
Debug.Assert(_emitPdbSequencePoints)
_builder.DefineHiddenSequencePoint()
End Sub
Private Sub EmitSequencePoint(syntax As SyntaxNode)
EmitSequencePoint(syntax.SyntaxTree, syntax.Span)
End Sub
Private Function EmitSequencePoint(tree As SyntaxTree, span As TextSpan) As TextSpan
Debug.Assert(tree IsNot Nothing)
Debug.Assert(_emitPdbSequencePoints)
_builder.DefineSequencePoint(tree, span)
Return span
End Function
End Class
End Namespace
|