File: Metadata\ILBuilderVisualizer.cs
Web Access
Project: src\src\Compilers\Test\Core\Microsoft.CodeAnalysis.Test.Utilities.csproj (Microsoft.CodeAnalysis.Test.Utilities)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection.Emit;
using System.Reflection.Metadata;
using System.Text;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.Metadata.Tools;
using Roslyn.Utilities;
using Cci = Microsoft.Cci;
using Microsoft.CodeAnalysis.Symbols;
using System.Diagnostics;
using System.Reflection.Metadata.Ecma335;
 
namespace Roslyn.Test.Utilities
{
    internal sealed class ILBuilderVisualizer : ILVisualizer
    {
        private readonly ITokenDeferral _tokenDeferral;
 
        public ILBuilderVisualizer(ITokenDeferral tokenDeferral)
        {
            _tokenDeferral = tokenDeferral;
        }
 
        public override string VisualizeUserString(uint token)
        {
            // Check for an encoding of the spelling of the current module's module version ID.
            if (token == 0x80000000)
            {
                return "##MVID##";
            }
 
            return "\"" + _tokenDeferral.GetStringFromToken(token) + "\"";
        }
 
        public override string VisualizeSymbol(uint token, OperandType operandType)
        {
            if (operandType == OperandType.InlineTok)
            {
                switch ((Cci.MetadataWriter.RawTokenEncoding)(token >> 24))
                {
                    case Cci.MetadataWriter.RawTokenEncoding.GreatestMethodDefinitionRowId:
                        return "Max Method Token Index";
 
                    case Cci.MetadataWriter.RawTokenEncoding.DocumentRowId:
                        return "Source Document " + (token & 0x00ffffff).ToString();
                }
 
                token &= 0xffffff;
            }
 
            object reference = _tokenDeferral.GetReferenceFromToken(token);
            ISymbol symbol = ((reference as ISymbolInternal) ?? (reference as Cci.IReference)?.GetInternalSymbol())?.GetISymbol();
            return string.Format("\"{0}\"", symbol == null ? (object)reference : symbol.ToDisplayString(SymbolDisplayFormat.ILVisualizationFormat));
        }
 
        public override string VisualizeLocalType(object type)
        {
            return (((type as ISymbolInternal) ?? (type as Cci.IReference)?.GetInternalSymbol()) is ISymbolInternal symbol) ? symbol.GetISymbol().ToDisplayString(SymbolDisplayFormat.ILVisualizationFormat) : type.ToString();
        }
 
        /// <summary>
        /// Determine the list of spans ordered by handler
        /// block start, with outer handlers before inner.
        /// </summary>
        private static List<HandlerSpan> GetHandlerSpans(ImmutableArray<Cci.ExceptionHandlerRegion> regions)
        {
            if (regions.Length == 0)
            {
                return null;
            }
 
            var spans = new List<HandlerSpan>();
 
            // Add unique try blocks.
            foreach (Cci.ExceptionHandlerRegion region in regions)
            {
                var span = new HandlerSpan(HandlerKind.Try, null, region.TryStartOffset, region.TryEndOffset);
 
                int n = spans.Count;
                if (n == 0 || span.CompareTo(spans[n - 1]) != 0)
                {
                    spans.Add(span);
                }
            }
 
            // Add all handler blocks.
            foreach (Cci.ExceptionHandlerRegion region in regions)
            {
                HandlerSpan span;
 
                if (region.HandlerKind == System.Reflection.Metadata.ExceptionRegionKind.Filter)
                {
                    span = new HandlerSpan(HandlerKind.Filter, null, region.FilterDecisionStartOffset, region.HandlerEndOffset, region.HandlerStartOffset);
                }
                else
                {
                    HandlerKind kind;
 
                    switch (region.HandlerKind)
                    {
                        case System.Reflection.Metadata.ExceptionRegionKind.Catch:
                            kind = HandlerKind.Catch;
                            break;
                        case System.Reflection.Metadata.ExceptionRegionKind.Fault:
                            kind = HandlerKind.Fault;
                            break;
                        case System.Reflection.Metadata.ExceptionRegionKind.Filter:
                            kind = HandlerKind.Filter;
                            break;
                        default:
                            kind = HandlerKind.Finally;
                            break;
                    }
 
                    span = new HandlerSpan(kind, region.ExceptionType, region.HandlerStartOffset, region.HandlerEndOffset);
                }
                spans.Add(span);
            }
 
            spans.Sort();
            return spans;
        }
 
        /// <remarks>
        /// Invoked via Reflection from <see cref="ILBuilder"/><c>.GetDebuggerDisplay()</c>.
        /// </remarks>
        internal static string ILBuilderToString(
            ILBuilder builder,
            Func<Cci.ILocalDefinition, LocalInfo> mapLocal = null,
            IReadOnlyDictionary<int, string> markers = null)
        {
            var sb = new StringBuilder();
 
            var ilStream = builder.RealizedIL;
            if (mapLocal == null)
            {
                mapLocal = local => new LocalInfo(local.Name, local.Type, local.IsPinned, local.IsReference);
            }
 
            var locals = builder.LocalSlotManager.LocalsInOrder().SelectAsArray(mapLocal);
            var visualizer = new ILBuilderVisualizer(builder.module);
 
            if (!ilStream.IsDefault)
            {
                visualizer.DumpMethod(sb, builder.MaxStack, ilStream, locals, GetHandlerSpans(builder.RealizedExceptionHandlers), markers, builder.AreLocalsZeroed);
            }
            else
            {
                sb.AppendLine("{");
 
                visualizer.VisualizeHeader(sb, 0, builder.MaxStack, locals);
                // serialize blocks as-is
                var current = builder.leaderBlock;
                while (current != null)
                {
                    DumpBlockIL(current, sb);
                    current = current.NextBlock;
                }
 
                sb.AppendLine("}");
            }
 
            return sb.ToString();
        }
 
        internal static string LocalSignatureToString(
            ILBuilder builder,
            Func<Cci.ILocalDefinition, LocalInfo> mapLocal = null)
        {
            var sb = new StringBuilder();
 
            if (mapLocal == null)
            {
                mapLocal = local => new LocalInfo(local.Name, local.Type, local.IsPinned, local.IsReference);
            }
 
            var locals = builder.LocalSlotManager.LocalsInOrder().SelectAsArray(mapLocal);
            var visualizer = new ILBuilderVisualizer(builder.module);
 
            visualizer.VisualizeHeader(sb, -1, -1, locals);
            return sb.ToString();
        }
 
        private static void DumpBlockIL(ILBuilder.BasicBlock block, StringBuilder sb)
        {
            if (block is ILBuilder.SwitchBlock switchBlock)
            {
                DumpSwitchBlockIL(switchBlock, sb);
            }
            else
            {
                DumpBasicBlockIL(block, sb);
            }
        }
 
        private static void DumpBasicBlockIL(ILBuilder.BasicBlock block, StringBuilder sb)
        {
            var instrCnt = block.RegularInstructionsLength;
            if (instrCnt != 0)
            {
                var il = block.RegularInstructions.ToImmutableArray();
                new ILBuilderVisualizer(block.builder.module).DumpILBlock(il, instrCnt, sb, Array.Empty<ILVisualizer.HandlerSpan>(), block.Start);
            }
 
            if (block.BranchCode != ILOpCode.Nop)
            {
                sb.Append(string.Format("  IL_{0:x4}:", block.RegularInstructionsLength + block.Start));
                sb.Append(string.Format("  {0,-10}", GetInstructionName(block.BranchCode)));
 
                if (block.BranchCode.IsBranch())
                {
                    var branchBlock = block.BranchBlock;
                    if (branchBlock == null)
                    {
                        // this happens if label is not yet marked.
                        sb.Append(" <unmarked label>");
                    }
                    else
                    {
                        sb.Append(string.Format(" IL_{0:x4}", branchBlock.Start));
                    }
                }
 
                sb.AppendLine();
            }
        }
 
        private static void DumpSwitchBlockIL(ILBuilder.SwitchBlock block, StringBuilder sb)
        {
            var il = block.RegularInstructions.ToImmutableArray();
            new ILBuilderVisualizer(block.builder.module).DumpILBlock(il, il.Length, sb, Array.Empty<HandlerSpan>(), block.Start);
 
            // switch (N, t1, t2... tN)
            //  IL ==> ILOpCode.Switch < unsigned int32 > < int32 >... < int32 >
 
            sb.Append(string.Format("  IL_{0:x4}:", block.RegularInstructionsLength + block.Start));
            sb.Append(string.Format("  {0,-10}", GetInstructionName(block.BranchCode)));
            sb.Append(string.Format("  IL_{0:x4}:", block.BranchesCount));
 
            var blockBuilder = ArrayBuilder<ILBuilder.BasicBlock>.GetInstance();
            block.GetBranchBlocks(blockBuilder);
 
            foreach (var branchBlock in blockBuilder)
            {
                if (branchBlock == null)
                {
                    // this happens if label is not yet marked.
                    sb.Append(" <unmarked label>");
                }
                else
                {
                    sb.Append(string.Format(" IL_{0:x4}", branchBlock.Start));
                }
            }
 
            blockBuilder.Free();
 
            sb.AppendLine();
        }
 
        private static string GetInstructionName(ILOpCode opcode)
        {
            switch (opcode)
            {
                case ILOpCode.Nop: return "nop";
                case ILOpCode.Break: return "break";
                case ILOpCode.Ldarg_0: return "ldarg.0";
                case ILOpCode.Ldarg_1: return "ldarg.1";
                case ILOpCode.Ldarg_2: return "ldarg.2";
                case ILOpCode.Ldarg_3: return "ldarg.3";
                case ILOpCode.Ldloc_0: return "ldloc.0";
                case ILOpCode.Ldloc_1: return "ldloc.1";
                case ILOpCode.Ldloc_2: return "ldloc.2";
                case ILOpCode.Ldloc_3: return "ldloc.3";
                case ILOpCode.Stloc_0: return "stloc.0";
                case ILOpCode.Stloc_1: return "stloc.1";
                case ILOpCode.Stloc_2: return "stloc.2";
                case ILOpCode.Stloc_3: return "stloc.3";
                case ILOpCode.Ldarg_s: return "ldarg.s";
                case ILOpCode.Ldarga_s: return "ldarga.s";
                case ILOpCode.Starg_s: return "starg.s";
                case ILOpCode.Ldloc_s: return "ldloc.s";
                case ILOpCode.Ldloca_s: return "ldloca.s";
                case ILOpCode.Stloc_s: return "stloc.s";
                case ILOpCode.Ldnull: return "ldnull";
                case ILOpCode.Ldc_i4_m1: return "ldc.i4.m1";
                case ILOpCode.Ldc_i4_0: return "ldc.i4.0";
                case ILOpCode.Ldc_i4_1: return "ldc.i4.1";
                case ILOpCode.Ldc_i4_2: return "ldc.i4.2";
                case ILOpCode.Ldc_i4_3: return "ldc.i4.3";
                case ILOpCode.Ldc_i4_4: return "ldc.i4.4";
                case ILOpCode.Ldc_i4_5: return "ldc.i4.5";
                case ILOpCode.Ldc_i4_6: return "ldc.i4.6";
                case ILOpCode.Ldc_i4_7: return "ldc.i4.7";
                case ILOpCode.Ldc_i4_8: return "ldc.i4.8";
                case ILOpCode.Ldc_i4_s: return "ldc.i4.s";
                case ILOpCode.Ldc_i4: return "ldc.i4";
                case ILOpCode.Ldc_i8: return "ldc.i8";
                case ILOpCode.Ldc_r4: return "ldc.r4";
                case ILOpCode.Ldc_r8: return "ldc.r8";
                case ILOpCode.Dup: return "dup";
                case ILOpCode.Pop: return "pop";
                case ILOpCode.Jmp: return "jmp";
                case ILOpCode.Call: return "call";
                case ILOpCode.Calli: return "calli";
                case ILOpCode.Ret: return "ret";
                case ILOpCode.Br_s: return "br.s";
                case ILOpCode.Brfalse_s: return "brfalse.s";
                case ILOpCode.Brtrue_s: return "brtrue.s";
                case ILOpCode.Beq_s: return "beq.s";
                case ILOpCode.Bge_s: return "bge.s";
                case ILOpCode.Bgt_s: return "bgt.s";
                case ILOpCode.Ble_s: return "ble.s";
                case ILOpCode.Blt_s: return "blt.s";
                case ILOpCode.Bne_un_s: return "bne.un.s";
                case ILOpCode.Bge_un_s: return "bge.un.s";
                case ILOpCode.Bgt_un_s: return "bgt.un.s";
                case ILOpCode.Ble_un_s: return "ble.un.s";
                case ILOpCode.Blt_un_s: return "blt.un.s";
                case ILOpCode.Br: return "br";
                case ILOpCode.Brfalse: return "brfalse";
                case ILOpCode.Brtrue: return "brtrue";
                case ILOpCode.Beq: return "beq";
                case ILOpCode.Bge: return "bge";
                case ILOpCode.Bgt: return "bgt";
                case ILOpCode.Ble: return "ble";
                case ILOpCode.Blt: return "blt";
                case ILOpCode.Bne_un: return "bne.un";
                case ILOpCode.Bge_un: return "bge.un";
                case ILOpCode.Bgt_un: return "bgt.un";
                case ILOpCode.Ble_un: return "ble.un";
                case ILOpCode.Blt_un: return "blt.un";
                case ILOpCode.Switch: return "switch";
                case ILOpCode.Ldind_i1: return "ldind.i1";
                case ILOpCode.Ldind_u1: return "ldind.u1";
                case ILOpCode.Ldind_i2: return "ldind.i2";
                case ILOpCode.Ldind_u2: return "ldind.u2";
                case ILOpCode.Ldind_i4: return "ldind.i4";
                case ILOpCode.Ldind_u4: return "ldind.u4";
                case ILOpCode.Ldind_i8: return "ldind.i8";
                case ILOpCode.Ldind_i: return "ldind.i";
                case ILOpCode.Ldind_r4: return "ldind.r4";
                case ILOpCode.Ldind_r8: return "ldind.r8";
                case ILOpCode.Ldind_ref: return "ldind.ref";
                case ILOpCode.Stind_ref: return "stind.ref";
                case ILOpCode.Stind_i1: return "stind.i1";
                case ILOpCode.Stind_i2: return "stind.i2";
                case ILOpCode.Stind_i4: return "stind.i4";
                case ILOpCode.Stind_i8: return "stind.i8";
                case ILOpCode.Stind_r4: return "stind.r4";
                case ILOpCode.Stind_r8: return "stind.r8";
                case ILOpCode.Add: return "add";
                case ILOpCode.Sub: return "sub";
                case ILOpCode.Mul: return "mul";
                case ILOpCode.Div: return "div";
                case ILOpCode.Div_un: return "div.un";
                case ILOpCode.Rem: return "rem";
                case ILOpCode.Rem_un: return "rem.un";
                case ILOpCode.And: return "and";
                case ILOpCode.Or: return "or";
                case ILOpCode.Xor: return "xor";
                case ILOpCode.Shl: return "shl";
                case ILOpCode.Shr: return "shr";
                case ILOpCode.Shr_un: return "shr.un";
                case ILOpCode.Neg: return "neg";
                case ILOpCode.Not: return "not";
                case ILOpCode.Conv_i1: return "conv.i1";
                case ILOpCode.Conv_i2: return "conv.i2";
                case ILOpCode.Conv_i4: return "conv.i4";
                case ILOpCode.Conv_i8: return "conv.i8";
                case ILOpCode.Conv_r4: return "conv.r4";
                case ILOpCode.Conv_r8: return "conv.r8";
                case ILOpCode.Conv_u4: return "conv.u4";
                case ILOpCode.Conv_u8: return "conv.u8";
                case ILOpCode.Callvirt: return "callvirt";
                case ILOpCode.Cpobj: return "cpobj";
                case ILOpCode.Ldobj: return "ldobj";
                case ILOpCode.Ldstr: return "ldstr";
                case ILOpCode.Newobj: return "newobj";
                case ILOpCode.Castclass: return "castclass";
                case ILOpCode.Isinst: return "isinst";
                case ILOpCode.Conv_r_un: return "conv.r.un";
                case ILOpCode.Unbox: return "unbox";
                case ILOpCode.Throw: return "throw";
                case ILOpCode.Ldfld: return "ldfld";
                case ILOpCode.Ldflda: return "ldflda";
                case ILOpCode.Stfld: return "stfld";
                case ILOpCode.Ldsfld: return "ldsfld";
                case ILOpCode.Ldsflda: return "ldsflda";
                case ILOpCode.Stsfld: return "stsfld";
                case ILOpCode.Stobj: return "stobj";
                case ILOpCode.Conv_ovf_i1_un: return "conv.ovf.i1.un";
                case ILOpCode.Conv_ovf_i2_un: return "conv.ovf.i2.un";
                case ILOpCode.Conv_ovf_i4_un: return "conv.ovf.i4.un";
                case ILOpCode.Conv_ovf_i8_un: return "conv.ovf.i8.un";
                case ILOpCode.Conv_ovf_u1_un: return "conv.ovf.u1.un";
                case ILOpCode.Conv_ovf_u2_un: return "conv.ovf.u2.un";
                case ILOpCode.Conv_ovf_u4_un: return "conv.ovf.u4.un";
                case ILOpCode.Conv_ovf_u8_un: return "conv.ovf.u8.un";
                case ILOpCode.Conv_ovf_i_un: return "conv.ovf.i.un";
                case ILOpCode.Conv_ovf_u_un: return "conv.ovf.u.un";
                case ILOpCode.Box: return "box";
                case ILOpCode.Newarr: return "newarr";
                case ILOpCode.Ldlen: return "ldlen";
                case ILOpCode.Ldelema: return "ldelema";
                case ILOpCode.Ldelem_i1: return "ldelem.i1";
                case ILOpCode.Ldelem_u1: return "ldelem.u1";
                case ILOpCode.Ldelem_i2: return "ldelem.i2";
                case ILOpCode.Ldelem_u2: return "ldelem.u2";
                case ILOpCode.Ldelem_i4: return "ldelem.i4";
                case ILOpCode.Ldelem_u4: return "ldelem.u4";
                case ILOpCode.Ldelem_i8: return "ldelem.i8";
                case ILOpCode.Ldelem_i: return "ldelem.i";
                case ILOpCode.Ldelem_r4: return "ldelem.r4";
                case ILOpCode.Ldelem_r8: return "ldelem.r8";
                case ILOpCode.Ldelem_ref: return "ldelem.ref";
                case ILOpCode.Stelem_i: return "stelem.i";
                case ILOpCode.Stelem_i1: return "stelem.i1";
                case ILOpCode.Stelem_i2: return "stelem.i2";
                case ILOpCode.Stelem_i4: return "stelem.i4";
                case ILOpCode.Stelem_i8: return "stelem.i8";
                case ILOpCode.Stelem_r4: return "stelem.r4";
                case ILOpCode.Stelem_r8: return "stelem.r8";
                case ILOpCode.Stelem_ref: return "stelem.ref";
                case ILOpCode.Ldelem: return "ldelem";
                case ILOpCode.Stelem: return "stelem";
                case ILOpCode.Unbox_any: return "unbox.any";
                case ILOpCode.Conv_ovf_i1: return "conv.ovf.i1";
                case ILOpCode.Conv_ovf_u1: return "conv.ovf.u1";
                case ILOpCode.Conv_ovf_i2: return "conv.ovf.i2";
                case ILOpCode.Conv_ovf_u2: return "conv.ovf.u2";
                case ILOpCode.Conv_ovf_i4: return "conv.ovf.i4";
                case ILOpCode.Conv_ovf_u4: return "conv.ovf.u4";
                case ILOpCode.Conv_ovf_i8: return "conv.ovf.i8";
                case ILOpCode.Conv_ovf_u8: return "conv.ovf.u8";
                case ILOpCode.Refanyval: return "refanyval";
                case ILOpCode.Ckfinite: return "ckfinite";
                case ILOpCode.Mkrefany: return "mkrefany";
                case ILOpCode.Ldtoken: return "ldtoken";
                case ILOpCode.Conv_u2: return "conv.u2";
                case ILOpCode.Conv_u1: return "conv.u1";
                case ILOpCode.Conv_i: return "conv.i";
                case ILOpCode.Conv_ovf_i: return "conv.ovf.i";
                case ILOpCode.Conv_ovf_u: return "conv.ovf.u";
                case ILOpCode.Add_ovf: return "add.ovf";
                case ILOpCode.Add_ovf_un: return "add.ovf.un";
                case ILOpCode.Mul_ovf: return "mul.ovf";
                case ILOpCode.Mul_ovf_un: return "mul.ovf.un";
                case ILOpCode.Sub_ovf: return "sub.ovf";
                case ILOpCode.Sub_ovf_un: return "sub.ovf.un";
                case ILOpCode.Endfinally: return "endfinally";
                case ILOpCode.Leave: return "leave";
                case ILOpCode.Leave_s: return "leave.s";
                case ILOpCode.Stind_i: return "stind.i";
                case ILOpCode.Conv_u: return "conv.u";
                case ILOpCode.Arglist: return "arglist";
                case ILOpCode.Ceq: return "ceq";
                case ILOpCode.Cgt: return "cgt";
                case ILOpCode.Cgt_un: return "cgt.un";
                case ILOpCode.Clt: return "clt";
                case ILOpCode.Clt_un: return "clt.un";
                case ILOpCode.Ldftn: return "ldftn";
                case ILOpCode.Ldvirtftn: return "ldvirtftn";
                case ILOpCode.Ldarg: return "ldarg";
                case ILOpCode.Ldarga: return "ldarga";
                case ILOpCode.Starg: return "starg";
                case ILOpCode.Ldloc: return "ldloc";
                case ILOpCode.Ldloca: return "ldloca";
                case ILOpCode.Stloc: return "stloc";
                case ILOpCode.Localloc: return "localloc";
                case ILOpCode.Endfilter: return "endfilter";
                case ILOpCode.Unaligned: return "unaligned.";
                case ILOpCode.Volatile: return "volatile.";
                case ILOpCode.Tail: return "tail.";
                case ILOpCode.Initobj: return "initobj";
                case ILOpCode.Constrained: return "constrained.";
                case ILOpCode.Cpblk: return "cpblk";
                case ILOpCode.Initblk: return "initblk";
                case ILOpCode.Rethrow: return "rethrow";
                case ILOpCode.Sizeof: return "sizeof";
                case ILOpCode.Refanytype: return "refanytype";
                case ILOpCode.Readonly: return "readonly.";
            }
 
            throw ExceptionUtilities.UnexpectedValue(opcode);
        }
    }
}