File: CodeGen\EmitConversion.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.Immutable
Imports System.Reflection.Metadata
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports PrimitiveTypeCode = Microsoft.Cci.PrimitiveTypeCode
 
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen
 
    Partial Friend Class CodeGenerator
 
        Private Shared Function IsSimpleType(type As PrimitiveTypeCode) As Boolean
            Dim result = False
 
            Select Case type
                Case PrimitiveTypeCode.Boolean,
                     PrimitiveTypeCode.Float32,
                     PrimitiveTypeCode.Float64,
                     PrimitiveTypeCode.Int16,
                     PrimitiveTypeCode.Int32,
                     PrimitiveTypeCode.Int64,
                     PrimitiveTypeCode.Int8,
                     PrimitiveTypeCode.UInt16,
                     PrimitiveTypeCode.UInt32,
                     PrimitiveTypeCode.UInt64,
                     PrimitiveTypeCode.UInt8
 
                    result = True
            End Select
 
            Return result
        End Function
 
        Private Shared Function IsIntegral(type As PrimitiveTypeCode) As Boolean
            Dim result = False
 
            Select Case type
                Case PrimitiveTypeCode.Int8,
                     PrimitiveTypeCode.UInt8,
                     PrimitiveTypeCode.Int16,
                     PrimitiveTypeCode.UInt16,
                     PrimitiveTypeCode.Int32,
                     PrimitiveTypeCode.UInt32,
                     PrimitiveTypeCode.Int64,
                     PrimitiveTypeCode.UInt64
 
                    result = True
            End Select
 
            Return result
        End Function
 
        Private Sub EmitConvertIntrinsic(conversion As BoundConversion, underlyingFrom As PrimitiveTypeCode, underlyingTo As PrimitiveTypeCode)
 
            Debug.Assert(underlyingFrom = conversion.Operand.Type.GetEnumUnderlyingTypeOrSelf().PrimitiveTypeCode)
            Debug.Assert(underlyingTo = conversion.Type.GetEnumUnderlyingTypeOrSelf().PrimitiveTypeCode)
            Debug.Assert((IsSimpleType(underlyingFrom) AndAlso IsSimpleType(underlyingTo)) OrElse (underlyingFrom = PrimitiveTypeCode.Char AndAlso underlyingTo = PrimitiveTypeCode.Int32))
 
            ' Generate the expression to convert.
            EmitExpression(conversion.Operand, True)
 
            ' For "identity conversions" from float to float or double to double,
            ' we require the generation of conv.r4 or conv.r8, if the conversion
            ' was explicitly written. The runtime can use these instructions to 
            ' truncate precision, and compiler generates them. 
            If underlyingFrom = underlyingTo AndAlso
               Not conversion.ExplicitCastInCode AndAlso
               underlyingFrom <> PrimitiveTypeCode.Float32 AndAlso
               underlyingFrom <> PrimitiveTypeCode.Float64 Then
                Return
            End If
 
            ' Handle conversions to Boolean
            If underlyingTo = PrimitiveTypeCode.Boolean Then
                ' Emit Zero
                _builder.EmitConstantValue(ConstantValue.Default(underlyingFrom.GetConstantValueTypeDiscriminator()))
 
                ' using cgt.un is optimal, but doesn't work in the case of floating point values
                If underlyingFrom.IsFloatingPoint() Then
                    _builder.EmitOpCode(ILOpCode.Ceq)
                    _builder.EmitOpCode(ILOpCode.Ldc_i4_0)
                    _builder.EmitOpCode(ILOpCode.Ceq)
                Else
                    _builder.EmitOpCode(ILOpCode.Cgt_un)
                End If
 
                Return
            End If
 
            ' Handle conversions from boolean
            If underlyingFrom = PrimitiveTypeCode.Boolean Then
                ' First, normalize to -1
                _builder.EmitOpCode(ILOpCode.Ldc_i4_0)
                _builder.EmitOpCode(ILOpCode.Cgt_un)
                _builder.EmitOpCode(ILOpCode.Neg)
 
                If underlyingTo <> PrimitiveTypeCode.Int32 Then
                    ' Convert to the target type, but don't do overflow checking.  This results in unsigned types
                    ' getting their max value.
                    _builder.EmitNumericConversion(PrimitiveTypeCode.Int32, underlyingTo, checked:=False)
                End If
 
                Return
            End If
 
            ' Handle conversion between simple numeric types
 
            If underlyingFrom = PrimitiveTypeCode.Float32 AndAlso IsIntegral(underlyingTo) Then
                ' If converting from an intermediate value, we need to guarantee that
                ' the intermediate value keeps the precision of its type.  The JIT will try to
                ' promote the precision of intermediate values if it can, and this can lead to
                ' incorrect results (VS#241243).
                Select Case conversion.Operand.Kind
                    Case BoundKind.BinaryOperator
 
                        Select Case (DirectCast(conversion.Operand, BoundBinaryOperator).OperatorKind And BinaryOperatorKind.OpMask)
                            Case BinaryOperatorKind.Add,
                                 BinaryOperatorKind.Subtract,
                                 BinaryOperatorKind.Multiply,
                                 BinaryOperatorKind.Divide,
                                 BinaryOperatorKind.Modulo,
                                 BinaryOperatorKind.Power
 
                                _builder.EmitOpCode(ILOpCode.Conv_r4)
                        End Select
 
                    Case BoundKind.UnaryOperator
                        Select Case (DirectCast(conversion.Operand, BoundUnaryOperator).OperatorKind And UnaryOperatorKind.IntrinsicOpMask)
                            Case UnaryOperatorKind.Minus,
                                 UnaryOperatorKind.Plus
 
                                _builder.EmitOpCode(ILOpCode.Conv_r4)
                        End Select
                End Select
 
                ' no intermediate value in other cases, so no need for the forced convert
            End If
 
            EmitConvertSimpleNumeric(conversion, underlyingFrom, underlyingTo, conversion.Checked)
        End Sub
 
        Private Sub EmitConvertSimpleNumeric(conversion As BoundConversion, typeFrom As PrimitiveTypeCode, typeTo As PrimitiveTypeCode, checked As Boolean)
            Debug.Assert(IsIntegral(typeFrom) OrElse typeFrom.IsFloatingPoint() OrElse typeFrom = PrimitiveTypeCode.Char)
            Debug.Assert(IsIntegral(typeTo) OrElse typeTo.IsFloatingPoint())
            _builder.EmitNumericConversion(typeFrom, typeTo, checked)
        End Sub
 
        Private Sub EmitConversionExpression(conversion As BoundConversion, used As Boolean)
            If Not used AndAlso Not ConversionHasSideEffects(conversion) Then
                EmitExpression(conversion.Operand, False)
                Return
            End If
 
            Dim typeTo = conversion.Type
 
            If conversion.Operand.IsNothingLiteral Then
                Debug.Assert(typeTo.IsValueType OrElse typeTo.IsTypeParameter)
 
                If used Then
                    'TODO: used
                    EmitLoadDefaultValueOfTypeFromNothingLiteral(typeTo, used:=True, syntaxNode:=conversion.Syntax)
                End If
            Else
                Dim underlyingTo = typeTo.GetEnumUnderlyingTypeOrSelf().PrimitiveTypeCode
                Dim typeFrom = conversion.Operand.Type
                Dim underlyingFrom = typeFrom.GetEnumUnderlyingTypeOrSelf().PrimitiveTypeCode
 
                If (IsSimpleType(underlyingFrom) AndAlso IsSimpleType(underlyingTo)) OrElse
                   (underlyingFrom = PrimitiveTypeCode.Char AndAlso underlyingTo = PrimitiveTypeCode.Int32) Then ' Allow AscW optimization.
                    EmitConvertIntrinsic(conversion, underlyingFrom, underlyingTo)
 
                ElseIf typeFrom.IsNullableType Then
                    Debug.Assert(typeTo.IsReferenceType)
 
                    EmitExpression(conversion.Operand, used:=True)
 
                    If (conversion.ConversionKind And ConversionKind.Narrowing) <> 0 Then
                        EmitBox(typeFrom, conversion.Operand.Syntax)
                        _builder.EmitOpCode(ILOpCode.Castclass)
                        EmitSymbolToken(typeTo, conversion.Syntax)
                    ElseIf used Then
                        ' boxing itself is CLR-widening, so no need to emit unused boxing
                        EmitBox(typeFrom, conversion.Operand.Syntax)
                    End If
 
                ElseIf typeTo.IsNullableType Then
                    Debug.Assert(typeFrom.IsReferenceType)
 
                    ' unboxing is CLR-narrowing and may deterministically throw
                    EmitExpression(conversion.Operand, True)
                    EmitUnboxAny(typeTo, conversion.Syntax)
 
                Else
                    EmitExpression(conversion.Operand, True)
 
                    If Not Conversions.IsIdentityConversion(conversion.ConversionKind) Then
 
                        Debug.Assert(Not typeFrom.IsTypeParameter() AndAlso Not typeTo.IsTypeParameter() AndAlso
                                              typeFrom.IsReferenceType AndAlso typeTo.IsValueType)
 
                        Debug.Assert(typeFrom.SpecialType = SpecialType.System_Object OrElse
                                     typeFrom.SpecialType = SpecialType.System_ValueType OrElse
                                     typeFrom.SpecialType = SpecialType.System_Enum OrElse
                                     typeFrom.IsInterfaceType)
 
                        ' Conversions from references types to structures should be equivalent to
                        ' conversions from Nothing if the reference is Nothing.  Perform the check
                        ' and do the conversion.
 
                        Dim unboxLabel = New GeneratedLabelSymbol("unbox")
                        Dim resultLabel = New GeneratedLabelSymbol("result")
 
                        ' If the Reference is not nothing, branch directly to the Unbox
                        _builder.EmitOpCode(ILOpCode.Dup)
                        _builder.EmitBranch(ILOpCode.Brtrue_s, unboxLabel)
 
                        ' The reference is nothing, so we need to load the "Nothing" value for the
                        ' struct we're converting to.
                        ' 
                        ' But first Pop off the Null reference cause we don't need it anymore.
                        _builder.EmitOpCode(ILOpCode.Pop)
 
                        Dim constructor = GetParameterlessValueTypeConstructor(DirectCast(typeTo, NamedTypeSymbol))
 
                        'TODO: used
                        If constructor Is Nothing OrElse constructor.IsDefaultValueTypeConstructor() Then
                            EmitInitObj(typeTo, used:=True, syntaxNode:=conversion.Syntax)
                        Else
                            ' before we use constructor symbol we need to report use site error if any
                            Dim diagnosticInfo As DiagnosticInfo = constructor.GetUseSiteInfo().DiagnosticInfo
                            If diagnosticInfo IsNot Nothing Then
                                _diagnostics.Add(New VBDiagnostic(diagnosticInfo, conversion.Syntax.Location))
                            End If
 
                            EmitNewObj(constructor, ImmutableArray(Of BoundExpression).Empty, used:=True, syntaxNode:=conversion.Syntax)
                        End If
 
                        _builder.EmitBranch(ILOpCode.Br_s, resultLabel)
 
                        _builder.MarkLabel(unboxLabel)
 
                        ' Unbox the reference to get the struct from inside the object.
                        _builder.EmitOpCode(ILOpCode.Unbox_any)
                        EmitSymbolToken(typeTo, conversion.Syntax)
 
                        _builder.MarkLabel(resultLabel)
                    End If
                End If
 
                EmitPopIfUnused(used)
            End If
 
        End Sub
 
        ''' <summary> 
        ''' Returns parameterless value type constructor.
        ''' </summary>
        Private Function GetParameterlessValueTypeConstructor(typeTo As NamedTypeSymbol) As MethodSymbol
            Debug.Assert(typeTo.IsValueType AndAlso Not typeTo.IsTypeParameter)
 
            '  find valuetype parameterless constructor and check the accessibility
            For Each constr In typeTo.InstanceConstructors
                ' NOTE: we intentionally skip constructors with all 
                '       optional parameters; this matches Dev10 behavior
                If constr.ParameterCount = 0 Then
                    '  check 'constr' 
                    If AccessCheck.IsSymbolAccessible(constr, _method.ContainingType, typeTo, useSiteInfo:=CompoundUseSiteInfo(Of AssemblySymbol).Discarded) Then
                        Return constr
                    End If
 
                    '  exit for each in any case
                    Return Nothing
                End If
            Next
 
            ' This point should not be reachable, because if there is no constructor in the 
            ' loaded value type, we should have generated a synthesized constructor.
            Throw ExceptionUtilities.Unreachable
        End Function
 
        Private Function IsUnboxingDirectCast(conversion As BoundDirectCast) As Boolean
            Dim typeTo As TypeSymbol = conversion.Type
            Dim typeFrom As TypeSymbol = conversion.Operand.Type
 
            Return Not conversion.Operand.IsNothingLiteral AndAlso
                   Not Conversions.IsIdentityConversion(conversion.ConversionKind) AndAlso
                   Not typeFrom.GetEnumUnderlyingTypeOrSelf().IsSameTypeIgnoringAll(typeTo.GetEnumUnderlyingTypeOrSelf()) AndAlso
                   Not typeFrom.IsTypeParameter() AndAlso
                   Not typeFrom.IsValueType AndAlso
                   Not typeTo.IsReferenceType
        End Function
 
        Private Sub EmitDirectCastExpression(conversion As BoundDirectCast, used As Boolean)
            If Not used AndAlso Not ConversionHasSideEffects(conversion) Then
                EmitExpression(conversion.Operand, False)
                Return
            End If
 
            'TODO: Dev10 CodeGenerator::GenerateDirectCast does some optimization for
            '      a case when conversion to Boolean is applied to comparison operator.
            '      Do we need to do something special about this case too?
 
            If conversion.Operand.IsNothingLiteral Then
 
                If conversion.Type.IsTypeParameter() Then
                    EmitLoadDefaultValueOfTypeParameter(conversion.Type, used, conversion.Syntax)
                    Return
                Else
                    EmitExpression(conversion.Operand, True)
 
                    If conversion.Type.IsValueType Then
                        ' unbox the return value to get the struct from inside the object
                        EmitUnboxAny(conversion.Type, conversion.Syntax)
                    Else
                        Debug.Assert(conversion.Type.IsReferenceType)
                    End If
                End If
            Else
 
                EmitExpression(conversion.Operand, True)
 
                If Not Conversions.IsIdentityConversion(conversion.ConversionKind) Then
                    Dim typeTo = conversion.Type
                    Dim typeFrom = conversion.Operand.Type
 
                    If typeFrom.GetEnumUnderlyingTypeOrSelf().IsSameTypeIgnoringAll(typeTo.GetEnumUnderlyingTypeOrSelf()) Then
                        ' Do nothing, it is the same as identity.
                    ElseIf typeFrom.IsTypeParameter() Then
                        ' For any conversion from a generic parameter to any other type,
                        ' box the operand and then allow the explicit cast from Object to
                        ' the target type. This is a clr requirement.
 
                        EmitBox(typeFrom, conversion.Operand.Syntax)
 
                        If typeTo.SpecialType <> SpecialType.System_Object Then
                            If typeTo.IsTypeParameter() Then
                                _builder.EmitOpCode(ILOpCode.Unbox_any)
                                EmitSymbolToken(typeTo, conversion.Syntax)
 
                                'TODO: is this needed for widening conversions?
                            ElseIf typeTo.IsReferenceType Then
                                _builder.EmitOpCode(ILOpCode.Castclass)
                                EmitSymbolToken(typeTo, conversion.Syntax)
 
                            Else
                                Debug.Assert(typeTo.IsValueType)
                                ' unbox the return value to get the struct from inside the object
                                EmitUnboxAny(typeTo, conversion.Syntax)
                            End If
                        End If
 
                    ElseIf typeTo.IsTypeParameter() Then
                        Debug.Assert(Not typeFrom.IsTypeParameter())
 
                        If typeFrom.IsValueType Then
                            ' For any conversion from a value type to a generic parameter,
                            ' box the operand and then allow the explicit cast from Object to
                            ' the target generic parameter type.
                            EmitBox(typeFrom, conversion.Operand.Syntax)
                        End If
 
                        _builder.EmitOpCode(ILOpCode.Unbox_any)
                        EmitSymbolToken(typeTo, conversion.Syntax)
 
                    ElseIf typeFrom.IsValueType Then
 
                        EmitBox(typeFrom, conversion.Operand.Syntax)
 
                        If typeTo.IsInterfaceType() Then
                            ' For any conversion from a value type to an implemented interface,
                            ' box the operand and then allow the explicit conversion from Object to
                            ' the interface below.
 
                            _builder.EmitOpCode(ILOpCode.Castclass)
                            EmitSymbolToken(typeTo, conversion.Syntax)
                        Else
                            Debug.Assert(typeTo.SpecialType = SpecialType.System_Object OrElse
                                         typeTo.SpecialType = SpecialType.System_ValueType OrElse
                                         typeTo.SpecialType = SpecialType.System_Enum)
                        End If
 
                    ElseIf typeTo.IsReferenceType Then
                        Debug.Assert(typeFrom.IsReferenceType)
 
                        Dim needExplicitCastClass As Boolean = True
 
                        If Conversions.IsWideningConversion(conversion.ConversionKind) Then
                            needExplicitCastClass = False
 
                            ' No need to emit explicit cast, except in the following cases.
                            ' TODO: Do we still need this checks or CLR is smarter now?
                            If typeFrom.IsArrayType() Then
                                Dim fromElementType = DirectCast(typeFrom, ArrayTypeSymbol).ElementType
 
                                ' Bug VSWhidbey 415020
                                If typeTo.IsArrayType() AndAlso
                                   (fromElementType.IsTypeParameter() OrElse
                                            DirectCast(typeTo, ArrayTypeSymbol).ElementType.IsTypeParameter()) Then
 
                                    needExplicitCastClass = True
 
                                ElseIf fromElementType.IsTypeParameter() AndAlso
                                    typeTo.IsInterfaceType() Then
 
                                    ' Bug VSWhidbey 517458. Special case for IList(Of T), ICollection(Of T) and IEnumerable(Of T).
                                    Dim [interface] = DirectCast(typeTo, NamedTypeSymbol)
 
                                    If [interface].Arity = 1 AndAlso
                                       Not [interface].TypeArgumentsNoUseSiteDiagnostics(0).IsSameTypeIgnoringAll(fromElementType) Then
                                        needExplicitCastClass = True
                                    End If
                                End If
                            End If
                        End If
 
                        If needExplicitCastClass Then
                            _builder.EmitOpCode(ILOpCode.Castclass)
                            EmitSymbolToken(typeTo, conversion.Syntax)
                        End If
 
                    Else
                        Debug.Assert(typeTo.IsValueType)
                        Debug.Assert(typeFrom.IsReferenceType)
                        Debug.Assert(typeFrom.SpecialType = SpecialType.System_Object OrElse
                                     typeFrom.SpecialType = SpecialType.System_ValueType OrElse
                                     typeFrom.SpecialType = SpecialType.System_Enum OrElse
                                     typeFrom.IsInterfaceType)
 
                        Debug.Assert(IsUnboxingDirectCast(conversion))
 
                        ' unbox the return value to get the struct from inside the object
                        EmitUnboxAny(typeTo, conversion.Syntax)
                    End If
                End If
            End If
 
            EmitPopIfUnused(used)
        End Sub
 
        Private Function ConversionHasSideEffects(conversion As BoundConversion) As Boolean
            ' only some intrinsic conversions are side-effect free
            ' the only side-effect of an intrinsic conversion is a throw when we fail to convert.
            ' 
            ' unchecked numeric conv does not throw
            ' implicit ref cast does not throw
            ' ...
 
            'TODO: compute this (note: returning true is safe - false would just enable optimizations)
            Return True
        End Function
 
        Private Function ConversionHasSideEffects(conversion As BoundDirectCast) As Boolean
            'TODO: compute this (note: returning true is safe - false would just enable optimizations)
            Return True
        End Function
 
        Private Function ConversionHasSideEffects(conversion As BoundTryCast) As Boolean
            'TODO: compute this (note: returning true is safe - false would just enable optimizations)
            Return False
        End Function
 
        Private Sub EmitTryCastExpression(conversion As BoundTryCast, used As Boolean)
            If Not used AndAlso Not ConversionHasSideEffects(conversion) Then
                EmitExpression(conversion.Operand, False)
                Return
            End If
 
            If conversion.Operand.IsNothingLiteral Then
 
                If conversion.Type.IsTypeParameter() Then
                    'TODO: used
                    EmitLoadDefaultValueOfTypeParameter(conversion.Type, used:=True, syntaxNode:=conversion.Syntax)
                Else
                    Debug.Assert(conversion.Type.IsReferenceType)
                    EmitExpression(conversion.Operand, True)
                End If
            Else
 
                EmitExpression(conversion.Operand, True)
 
                If Not Conversions.IsIdentityConversion(conversion.ConversionKind) Then
                    Dim typeTo = conversion.Type
                    Dim typeFrom = conversion.Operand.Type
 
                    Debug.Assert(typeFrom IsNot Nothing)
                    Debug.Assert(typeTo IsNot Nothing)
 
                    If typeFrom.IsReferenceType() OrElse
                        typeFrom.IsTypeParameter() OrElse
                        typeTo.IsTypeParameter() Then
 
                        If Not IsVerifierReference(typeFrom) Then
                            EmitBox(typeFrom, conversion.Operand.Syntax)
                        End If
 
                        _builder.EmitOpCode(ILOpCode.Isinst)
                        EmitSymbolToken(typeTo, conversion.Syntax)
 
                        If Not IsVerifierReference(typeTo) Then
                            _builder.EmitOpCode(ILOpCode.Unbox_any)
                            EmitSymbolToken(typeTo, conversion.Syntax)
                        End If
 
                    Else
                        Debug.Assert(typeFrom.IsValueType)
                        Debug.Assert(typeTo.IsReferenceType)
                        Debug.Assert(typeTo.SpecialType = SpecialType.System_Object OrElse
                                     typeTo.SpecialType = SpecialType.System_ValueType OrElse
                                     typeTo.SpecialType = SpecialType.System_Enum OrElse
                                     typeTo.IsInterfaceType())
 
                        EmitBox(typeFrom, conversion.Operand.Syntax)
                    End If
                End If
            End If
 
            EmitPopIfUnused(used)
        End Sub
 
    End Class
 
End Namespace