﻿// 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.

using System;
using System.Diagnostics;
using System.Reflection.Metadata;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CodeGen
{
    internal partial class ILBuilder
    {
        public void EmitNumericConversion(Microsoft.Cci.PrimitiveTypeCode fromPredefTypeKind, Microsoft.Cci.PrimitiveTypeCode toPredefTypeKind, bool @checked)
        {
            bool fromUnsigned = fromPredefTypeKind.IsUnsigned();

            switch (toPredefTypeKind)
            {
                case Microsoft.Cci.PrimitiveTypeCode.Int8:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                            break; // NOP
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_i1_un : ILOpCode.Conv_ovf_i1);
                            else
                                this.EmitOpCode(ILOpCode.Conv_i1);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                            break; // NOP
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_u1_un : ILOpCode.Conv_ovf_u1);
                            else
                                this.EmitOpCode(ILOpCode.Conv_u1);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.Int16:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.Int16:
                            break; // NOP
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_i2_un : ILOpCode.Conv_ovf_i2);
                            else
                                this.EmitOpCode(ILOpCode.Conv_i2);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.Char:
                case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        case Microsoft.Cci.PrimitiveTypeCode.Char:
                            break; // NOP
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_u2_un : ILOpCode.Conv_ovf_u2);
                            else
                                this.EmitOpCode(ILOpCode.Conv_u2);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.Int32:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.Int16:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        case Microsoft.Cci.PrimitiveTypeCode.Int32:
                        case Microsoft.Cci.PrimitiveTypeCode.Char:
                            break; // NOP
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_i4_un);
                            break; // NOP in unchecked
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_i4_un : ILOpCode.Conv_ovf_i4);
                            else
                                this.EmitOpCode(ILOpCode.Conv_i4);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                        case Microsoft.Cci.PrimitiveTypeCode.Char:
                            break; // NOP
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        case Microsoft.Cci.PrimitiveTypeCode.Int16:
                        case Microsoft.Cci.PrimitiveTypeCode.Int32:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_u4);
                            break; // NOP in unchecked
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_u4_un : ILOpCode.Conv_ovf_u4);
                            else
                                this.EmitOpCode(ILOpCode.Conv_u4);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                        case Microsoft.Cci.PrimitiveTypeCode.UIntPtr when !@checked:
                            break; // NOP
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        case Microsoft.Cci.PrimitiveTypeCode.Int16:
                        case Microsoft.Cci.PrimitiveTypeCode.Int32:
                            this.EmitOpCode(ILOpCode.Conv_i); // potentially widening, so not NOP
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        case Microsoft.Cci.PrimitiveTypeCode.Char:
                            // Doesn't actually matter whether we sign extend, because
                            // bit 32 can't be set in any of these types.
                            this.EmitOpCode(ILOpCode.Conv_u); // potentially widening, so not NOP
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_i_un);
                            else
                                // Don't want to sign extend if this is a widening conversion.
                                this.EmitOpCode(ILOpCode.Conv_u); // potentially widening, so not NOP
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                        case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                            if (@checked)
                                goto default;
                            break; // NOP
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_i_un : ILOpCode.Conv_ovf_i);
                            else
                                this.EmitOpCode(ILOpCode.Conv_i);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                        case Microsoft.Cci.PrimitiveTypeCode.IntPtr when !@checked:
                        case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                        case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                            break; // NOP
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                        case Microsoft.Cci.PrimitiveTypeCode.Char:
                            this.EmitOpCode(ILOpCode.Conv_u); // potentially widening, so not NOP
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        case Microsoft.Cci.PrimitiveTypeCode.Int16:
                        case Microsoft.Cci.PrimitiveTypeCode.Int32:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_u);
                            else
                                this.EmitOpCode(ILOpCode.Conv_i); // potentially widening, so not NOP
                            break;
                        default:
                            if (@checked)
                                this.EmitOpCode(fromUnsigned ? ILOpCode.Conv_ovf_u_un : ILOpCode.Conv_ovf_u);
                            else
                                this.EmitOpCode(ILOpCode.Conv_u);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.Int64:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.Int64:
                            break; //NOP
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        case Microsoft.Cci.PrimitiveTypeCode.Int16:
                        case Microsoft.Cci.PrimitiveTypeCode.Int32:
                        case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                            this.EmitOpCode(ILOpCode.Conv_i8); // sign extend
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                        case Microsoft.Cci.PrimitiveTypeCode.Char:
                            this.EmitOpCode(ILOpCode.Conv_u8); // 0 extend
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                        case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                        case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_i8_un);
                            else
                                this.EmitOpCode(ILOpCode.Conv_u8); // 0 extend if unchecked
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_i8_un);
                            break; // NOP in unchecked
                        default:
                            Debug.Assert(fromPredefTypeKind.IsFloatingPoint());
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_i8);
                            else
                                this.EmitOpCode(ILOpCode.Conv_i8);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                            break; //NOP
                        case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                        case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                        case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                        case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                        case Microsoft.Cci.PrimitiveTypeCode.Char:
                            this.EmitOpCode(ILOpCode.Conv_u8); // 0 extend
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        case Microsoft.Cci.PrimitiveTypeCode.Int16:
                        case Microsoft.Cci.PrimitiveTypeCode.Int32:
                        case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_u8);
                            else
                                this.EmitOpCode(ILOpCode.Conv_i8); // sign extend if unchecked
                            break;
                        case Microsoft.Cci.PrimitiveTypeCode.Int64:
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_u8);
                            break; // NOP in unchecked
                        default:
                            Debug.Assert(fromPredefTypeKind.IsFloatingPoint());
                            if (@checked)
                                this.EmitOpCode(ILOpCode.Conv_ovf_u8);
                            else
                                this.EmitOpCode(ILOpCode.Conv_u8);
                            break;
                    }
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.Float32:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                        case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                            this.EmitOpCode(ILOpCode.Conv_r_un);
                            break;
                    }
                    this.EmitOpCode(ILOpCode.Conv_r4);
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.Float64:
                    switch (fromPredefTypeKind)
                    {
                        case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                        case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                        case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                            this.EmitOpCode(ILOpCode.Conv_r_un);
                            break;
                    }
                    this.EmitOpCode(ILOpCode.Conv_r8);
                    break;

                case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                    if (@checked)
                    {
                        switch (fromPredefTypeKind)
                        {
                            case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                            case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                            case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                                this.EmitOpCode(ILOpCode.Conv_u);
                                break;
                            case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                                this.EmitOpCode(ILOpCode.Conv_ovf_u_un);
                                break;
                            case Microsoft.Cci.PrimitiveTypeCode.Int8:
                            case Microsoft.Cci.PrimitiveTypeCode.Int16:
                            case Microsoft.Cci.PrimitiveTypeCode.Int32:
                            case Microsoft.Cci.PrimitiveTypeCode.Int64:
                                this.EmitOpCode(ILOpCode.Conv_ovf_u);
                                break;
                            case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                                this.EmitOpCode(ILOpCode.Conv_ovf_u);
                                break;
                            case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                                break; // NOP
                            default:
                                throw ExceptionUtilities.UnexpectedValue(fromPredefTypeKind);
                        }
                    }
                    else
                    {
                        switch (fromPredefTypeKind)
                        {
                            case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                            case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                            case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                            case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                            case Microsoft.Cci.PrimitiveTypeCode.Int64:
                                this.EmitOpCode(ILOpCode.Conv_u);
                                break;
                            case Microsoft.Cci.PrimitiveTypeCode.Int8:
                            case Microsoft.Cci.PrimitiveTypeCode.Int16:
                            case Microsoft.Cci.PrimitiveTypeCode.Int32:
                                // This matches dev10.  Presumably, we're using conv_i,
                                // rather than conv_u, to sign-extend the value.
                                this.EmitOpCode(ILOpCode.Conv_i);
                                break;
                            case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                            case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                                break; // NOP
                            default:
                                throw ExceptionUtilities.UnexpectedValue(fromPredefTypeKind);
                        }
                    }
                    break;

                default:
                    throw ExceptionUtilities.UnexpectedValue(toPredefTypeKind);
            }
        }
    }
}
