Просмотр исходного кода

Working on loading assemblies into the editor, with inspection

Josh Engebretson 10 лет назад
Родитель
Сommit
6a5c9f237f

+ 78 - 0
Build/AtomicNET/AtomicNETBootstrap/AssemblyLoaderCache.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Atomic.Bootstrap
+{
+    internal class AssemblyLoaderCache
+    {
+        private readonly ConcurrentDictionary<AssemblyName, object> _assemblyLoadLocks = new ConcurrentDictionary<AssemblyName, object>(AssemblyNameComparer.Ordinal);
+        private readonly ConcurrentDictionary<AssemblyName, Assembly> _assemblyCache = new ConcurrentDictionary<AssemblyName, Assembly>(AssemblyNameComparer.Ordinal);
+
+        public Assembly GetOrAdd(AssemblyName name, Func<AssemblyName, Assembly> factory)
+        {
+            Console.WriteLine("AssemblyLoaderCache:GetOrAdd {0}", name.Name);
+            // If the assembly was already loaded use it
+            Assembly assembly;
+            if (_assemblyCache.TryGetValue(name, out assembly))
+            {
+                return assembly;
+            }
+
+            var loadLock = _assemblyLoadLocks.GetOrAdd(name, new object());
+
+            try
+            {
+                // Concurrently loading the assembly might result in two distinct instances of the same assembly
+                // being loaded. This was observed when loading via Assembly.LoadStream. Prevent this by locking on the name.
+                lock (loadLock)
+                {
+                    if (_assemblyCache.TryGetValue(name, out assembly))
+                    {
+                        // This would succeed in case the thread was previously waiting on the lock when assembly
+                        // load was in progress
+                        return assembly;
+                    }
+
+                    assembly = factory(name);
+
+                    if (assembly != null)
+                    {
+                        _assemblyCache[name] = assembly;
+                    }
+                }
+            }
+            finally
+            {
+                _assemblyLoadLocks.TryRemove(name, out loadLock);
+            }
+
+            return assembly;
+        }
+
+        private class AssemblyNameComparer : IEqualityComparer<AssemblyName>
+        {
+            public static IEqualityComparer<AssemblyName> Ordinal = new AssemblyNameComparer();
+
+            public bool Equals(AssemblyName x, AssemblyName y)
+            {
+                return
+                    string.Equals(x.Name, y.Name, StringComparison.Ordinal) &&
+                    string.Equals(x.CultureName ?? "", y.CultureName ?? "", StringComparison.Ordinal);
+            }
+
+            public int GetHashCode(AssemblyName obj)
+            {
+                var hashCode = 0;
+                if (obj.Name != null)
+                {
+                    hashCode ^= obj.Name.GetHashCode();
+                }
+
+                hashCode ^= (obj.CultureName ?? "").GetHashCode();
+                return hashCode;
+            }
+        }
+    }
+}

+ 15 - 5
Build/AtomicNET/AtomicNETBootstrap/Bootstrap.cs

@@ -4,13 +4,15 @@ using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Runtime.Loader;
 
-// This must be in TPA list
-public class AtomicLoadContext : AssemblyLoadContext
+namespace Atomic.Bootstrap
 {
 
+// This must be in TPA list
+public class AtomicLoadContext : LoadContext
+{
     public static void Startup()
     {
-      AssemblyLoadContext.InitializeDefaultContext(new AtomicLoadContext());
+      LoadContext.InitializeDefaultContext(new AtomicLoadContext());
       Console.WriteLine("Bootstrap Startup");
     }
 
@@ -26,23 +28,31 @@ public class AtomicLoadContext : AssemblyLoadContext
       }
 
       // do we need to walk paths here?
+      Console.WriteLine("LoadUnmanagedDll: " + unmanagedDllName);
       return dlopen(unmanagedDllName, 1 /*RTLD_LAZY*/);
     }
 
-    protected override Assembly Load(AssemblyName assemblyName)
+    public override Assembly LoadAssembly(AssemblyName assemblyName)
     {
 
       Console.WriteLine(assemblyName.Name);
       Assembly assembly = null;
       try {
-        assembly = LoadFromAssemblyPath("/Users/josh/Desktop/" + assemblyName.Name + ".dll");
+            assembly = LoadFromAssemblyPath("/Users/josh/Desktop/" + assemblyName.Name + ".dll");
       } catch (Exception e)
       {
         Console.WriteLine(e.Message);
       }
+
+      if (assembly == null)
+        assembly = LoadFromAssemblyPath("/Users/josh/Desktop/OSX.x64.Debug/" + assemblyName.Name + ".dll");
+
       Console.WriteLine("Assembly: " + assembly);
       return assembly;
 
     }
 
+
+}
+
 }

+ 34 - 0
Build/AtomicNET/AtomicNETBootstrap/IAssemblyLoadContext.cs

@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace Atomic.Bootstrap
+{
+    /// <summary>
+    /// A context in which assemblies can be loaded.
+    /// </summary>
+    public interface IAssemblyLoadContext : IDisposable
+    {
+        /// <summary>
+        /// Load an assembly by name.
+        /// </summary>
+        /// <param name="assemblyName">The name of the assembly.</param>
+        /// <returns>The loaded assembly.</returns>
+        Assembly Load(AssemblyName assemblyName);
+
+        /// <summary>
+        /// Loads the assembly located at the provided file system path.
+        /// </summary>
+        /// <param name="path">The fully qualified path of the file to load.</param>
+        /// <returns>The loaded assembly.</returns>
+        Assembly LoadFile(string path);
+
+        /// <summary>
+        /// Loads the assembly with a common object file format (COFF)-based image containing an emitted assembly, optionally including symbols for the assembly.
+        /// </summary>
+        /// <param name="assemblyStream">The stream representing the assembly.</param>
+        /// <param name="assemblySymbols">The stream representing the symbols.</param>
+        /// <returns>The loaded assembly.</returns>
+        Assembly LoadStream(Stream assemblyStream, Stream assemblySymbols);
+    }
+}

+ 98 - 0
Build/AtomicNET/AtomicNETBootstrap/LoadContext.cs

@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+//#if DNXCORE50
+using System.Runtime.Loader;
+//#endif
+
+namespace Atomic.Bootstrap
+{
+    public abstract class LoadContext : AssemblyLoadContext, IAssemblyLoadContext
+    {
+        private readonly AssemblyLoaderCache _cache = new AssemblyLoaderCache();
+        private readonly string _friendlyName;
+
+        public LoadContext()
+        {
+        }
+
+        public LoadContext(string friendlyName)
+        {
+            _friendlyName = friendlyName;
+        }
+
+        protected override Assembly Load(AssemblyName assemblyName)
+        {
+            return _cache.GetOrAdd(assemblyName, LoadAssembly);
+        }
+
+        Assembly IAssemblyLoadContext.Load(AssemblyName assemblyName)
+        {
+            return LoadFromAssemblyName(assemblyName);
+        }
+
+        public abstract Assembly LoadAssembly(AssemblyName assemblyName);
+
+        public Assembly LoadFile(string path)
+        {
+            // Look for platform specific native image
+            string nativeImagePath = GetNativeImagePath(path);
+
+            if (nativeImagePath != null)
+            {
+                return LoadFromNativeImagePath(nativeImagePath, path);
+            }
+
+            return LoadFromAssemblyPath(path);
+        }
+
+        public Assembly LoadStream(Stream assembly, Stream assemblySymbols)
+        {
+            if (assemblySymbols == null)
+            {
+                return LoadFromStream(assembly);
+            }
+
+            return LoadFromStream(assembly, assemblySymbols);
+        }
+
+        public static void InitializeDefaultContext(LoadContext loadContext)
+        {
+            AssemblyLoadContext.InitializeDefaultContext(loadContext);
+        }
+
+        private string GetNativeImagePath(string ilPath)
+        {
+            var directory = Path.GetDirectoryName(ilPath);
+            var arch = IntPtr.Size == 4 ? "x86" : "AMD64";
+
+            var nativeImageName = Path.GetFileNameWithoutExtension(ilPath) + ".ni.dll";
+            var nativePath = Path.Combine(directory, arch, nativeImageName);
+
+            if (File.Exists(nativePath))
+            {
+                return nativePath;
+            }
+            else
+            {
+                // Runtime is arch sensitive so the ni is in the same folder as IL
+                nativePath = Path.Combine(directory, nativeImageName);
+                if (File.Exists(nativePath))
+                {
+                    return nativePath;
+                }
+            }
+
+            return null;
+        }
+
+        public virtual void Dispose()
+        {
+
+        }
+    }
+
+}

+ 40 - 4
Build/AtomicNET/AtomicNETRuntime/AtomicNET.cs

@@ -62,9 +62,6 @@ public static class Atomic
     registerSubsystem (NativeCore.WrapNative<Graphics> (csb_AtomicEngine_GetSubsystem("Graphics")));
     registerSubsystem (NativeCore.WrapNative<Renderer> (csb_AtomicEngine_GetSubsystem("Renderer")));
     registerSubsystem (NativeCore.WrapNative<ResourceCache> (csb_AtomicEngine_GetSubsystem("ResourceCache")));
-
-    var zone = GetSubsystem<Renderer>().GetDefaultZone();
-    Console.WriteLine("Allocated: " + zone.nativeInstance);
   }
 
   [DllImport (Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
@@ -122,37 +119,65 @@ static class NativeCore
 
   public static void ReleaseExpiredNativeReferences()
   {
-    List<IntPtr> released = new List<IntPtr> ();
+    List<IntPtr> released = null;
 
     foreach(KeyValuePair<IntPtr, WeakReference> entry in nativeLookup)
     {
 
       if (entry.Value.Target == null || !entry.Value.IsAlive)
       {
+        if (released == null)
+          released = new List<IntPtr> ();
+
         released.Add (entry.Key);
       }
 
     }
 
+    if (released == null)
+      return;
+
     foreach (IntPtr native in released) {
       nativeLookup.Remove (native);
       csb_AtomicEngine_ReleaseRef(native);
+      //Console.WriteLine("Released: " + test);
     }
 
   }
 
+  static IntPtr test = IntPtr.Zero;
+
   // called by native code
   public static void NETUpdate (float timeStep)
   {
+
     GC.Collect();
     GC.WaitForPendingFinalizers();
     GC.Collect();
     ReleaseExpiredNativeReferences();
+
+    if (test == IntPtr.Zero || !nativeLookup.ContainsKey(test))
+    {
+      test = Atomic.GetSubsystem<Renderer>().GetDefaultZone().nativeInstance;
+      //Console.WriteLine("Allocated: " + test);
+    }
+
   }
 
   // called by native code for every refcounted deleted
   public static void RefCountedDeleted (IntPtr native)
   {
+    // native side deleted, immediate remove, if we have a script reference still this is an error
+
+    WeakReference w;
+
+    if (nativeLookup.TryGetValue (native, out w)) {
+
+        if ( w != null && w.IsAlive) {
+          throw new System.InvalidOperationException("Native Refcounted was deleted with live managed instance");
+        }
+    }
+
     nativeLookup.Remove(native);
   }
 
@@ -211,4 +236,15 @@ static class NativeCore
 
 }
 
+public class InspectorAttribute : Attribute
+{
+  public InspectorAttribute(string defaultValue = "")
+  {
+    DefaultValue = defaultValue;
+  }
+
+  public string DefaultValue;
+}
+
+
 }

+ 16 - 1
Build/AtomicNET/build.sh

@@ -1,8 +1,23 @@
 
-mcs /out:/Users/josh/Desktop/OSX.x64.Debug/AtomicNETBootstrap.dll /nostdlib /noconfig /t:library /lib:/Users/josh/Desktop/OSX.x64.Debug /r:System.Console.dll /r:System.Runtime.dll /r:System.IO.dll /r:System.IO.FileSystem.dll /r:mscorlib.dll \
+mcs /out:/Users/josh/Desktop/OSX.x64.Debug/AtomicNETBootstrap.dll /nostdlib /noconfig /t:library /lib:/Users/josh/Desktop/OSX.x64.Debug \
+/r:System.Console.dll /r:System.Runtime.dll /r:System.IO.dll /r:System.IO.FileSystem.dll /r:mscorlib.dll \
+/r:System.Collections.Concurrent.dll \
 /Users/josh/Dev/atomic/AtomicGameEngineSharp/Build/AtomicNET/AtomicNETBootstrap/*.cs
 
 mcs /out:/Users/josh/Desktop/AtomicNETRuntime.dll /nostdlib /noconfig /t:library /w:0 /lib:/Users/josh/Desktop/OSX.x64.Debug /r:System.Console.dll /r:System.Runtime.dll /r:System.IO.dll /r:System.IO.FileSystem.dll /r:mscorlib.dll \
 /Users/josh/Dev/atomic/AtomicGameEngineSharp/Build/Source/Generated/MACOSX/CSharp/Packages/Atomic/Managed/*.cs \
 /Users/josh/Dev/atomic/AtomicGameEngineSharp/Build/Source/Generated/MACOSX/CSharp/Packages/AtomicPlayer/Managed/*.cs \
 /Users/josh/Dev/atomic/AtomicGameEngineSharp/Build/AtomicNET/AtomicNETRuntime/*.cs
+
+#mcs /out:/Users/josh/Desktop/AtomicEditor.dll /t:library \
+#/r:/Users/josh/Desktop/AtomicNETRuntime.dll \
+#/Users/josh/Dev/atomic/AtomicGameEngineSharp/Build/AtomicNETTest/AtomicEditor/AtomicEditor.cs
+
+mcs /unsafe /out:/Users/josh/Desktop/AtomicEditor.dll /nostdlib /noconfig /t:library /w:0 /lib:/Users/josh/Desktop/OSX.x64.Debug /r:System.Console.dll /r:System.Runtime.dll /r:System.IO.dll /r:System.IO.FileSystem.dll /r:mscorlib.dll \
+/r:System.Linq.dll /r:System.Reflection.Metadata.dll /r:System.Collections.dll /r:System.Collections.Immutable.dll /r:/Users/josh/Desktop/AtomicNETRuntime.dll \
+/Users/josh/Dev/atomic/AtomicGameEngineSharp/Build/AtomicNETTest/AtomicEditor/*.cs
+
+mcs /out:/Users/josh/Desktop/AtomicNETTest.dll /nostdlib /noconfig /t:library /w:0 /lib:/Users/josh/Desktop/OSX.x64.Debug \
+/r:System.Console.dll /r:System.Runtime.dll /r:System.IO.dll /r:System.IO.FileSystem.dll /r:mscorlib.dll \
+/r:/Users/josh/Desktop/AtomicNETRuntime.dll \
+/Users/josh/Dev/atomic/AtomicGameEngineSharp/Build/AtomicNETTest/MyClass.cs

+ 111 - 0
Build/AtomicNETTest/AtomicEditor/AtomicEditor.cs

@@ -0,0 +1,111 @@
+using System;
+using System.IO;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using System.Reflection.Metadata.Ecma335;
+using System.Text;
+
+using AtomicEngine;
+
+using File = System.IO.File;
+
+namespace AtomicEditor
+{
+	class AssemblyInspector
+	{
+
+		public static void InspectAssembly (String pathToAssembly)
+		{
+
+			try {
+				using (var stream = File.OpenRead (pathToAssembly))
+				using (var peFile = new PEReader (stream)) {
+
+					var reader = peFile.GetMetadataReader ();
+
+					foreach (var handle in reader.TypeDefinitions)
+          {
+              var typeDef = reader.GetTypeDefinition(handle);
+
+							var baseTypeHandle = typeDef.BaseType;
+
+							if (baseTypeHandle.Kind == HandleKind.TypeReference)
+							{
+									var typeRef = reader.GetTypeReference((TypeReferenceHandle)baseTypeHandle);
+
+									// TODO: validate assembly of CSComponent typeref
+									if (reader.GetString(typeRef.Name) != "CSComponent")
+										continue;
+
+									var inspector = new CSComponentInspector(typeDef, peFile, reader);
+
+									inspector.Inspect();
+
+							}
+
+
+					}
+
+              //uint size, packingSize;
+              //bool hasLayout = entry.GetTypeLayout(out size, out packingSize);
+
+							/*
+							Console.WriteLine(reader.GetString(entry.Name));
+
+							var fields = entry.GetFields();
+							foreach (var fieldHandle in fields)
+							{
+								 // FieldDefinitionHandle
+								 var fieldDef = reader.GetFieldDefinition(fieldHandle);
+								 Console.WriteLine("Field! {0}", reader.GetString(fieldDef.Name));
+							}
+
+							var methods = entry.GetMethods();
+
+							foreach (var methodHandle in methods)
+							{
+								 // FieldDefinitionHandle
+								 var methodDef = reader.GetMethodDefinition(methodHandle);
+
+								 if (reader.GetString(methodDef.Name) == ".ctor")
+								 {
+									 Console.WriteLine("Dumping ctor");
+
+									 var body = peFile.GetMethodBody(methodDef.RelativeVirtualAddress);
+
+									 /*
+									 var value = CSComponentInspector.Instance.DumpMethod(
+									 		reader,
+									 		entry,
+									 		body.MaxStack,
+									 		body.GetILContent(),
+									 		ImmutableArray.Create<CSComponentInspector.LocalInfo>(),     // TODO
+									 		ImmutableArray.Create<CSComponentInspector.HandlerSpan>());
+
+											Console.WriteLine("IL: \n{0}", value);
+										*/
+
+									/*
+								 }
+
+
+							}
+
+          }
+					*/
+				}
+			} catch (Exception ex) {
+				Console.WriteLine (ex.Message);
+			}
+
+
+		}
+
+	}
+
+}

+ 44 - 0
Build/AtomicNETTest/AtomicEditor/AtomicNETEditor.csproj

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{8069C194-8422-4E3B-807E-81ED7819829C}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>AtomicEditor</RootNamespace>
+    <AssemblyName>AtomicEditor</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="AtomicNETRuntime">
+      <HintPath>..\..\..\..\..\..\Desktop\AtomicNETRuntime.dll</HintPath>
+    </Reference>
+    <Reference Include="Mono.Cecil">
+      <HintPath>..\..\..\..\..\..\Desktop\OSX.x64.Debug\Mono.Cecil.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="AtomicEditor.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project>

+ 547 - 0
Build/AtomicNETTest/AtomicEditor/CSComponentInspector.cs

@@ -0,0 +1,547 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+using System.Collections.Immutable;
+
+//using Roslyn.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using System.Reflection.Emit;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+
+namespace AtomicEditor
+{
+
+
+	public class CSComponentInspector
+	{
+		private List<string> inspectorFieldNames = new List<string> ();
+
+		public CSComponentInspector (TypeDefinition typeDef, PEReader peFile, MetadataReader metaReader)
+		{
+			this.typeDef = typeDef;
+			this.peFile = peFile;
+			this.metaReader = metaReader;
+		}
+
+		public bool Inspect ()
+		{
+
+			var fields = typeDef.GetFields ();
+
+			foreach (var fieldHandle in fields) {
+
+				var fieldDef = metaReader.GetFieldDefinition (fieldHandle);
+
+				var customAttr = fieldDef.GetCustomAttributes ();
+
+				foreach (var caHandle in customAttr) {
+
+					var ca = metaReader.GetCustomAttribute (caHandle);
+
+					// MethodDefinitionHandle or MemberReferenceHandle
+					if (ca.Constructor.Kind == HandleKind.MemberReference) {
+
+						var memberRef = metaReader.GetMemberReference ((MemberReferenceHandle)ca.Constructor);
+
+						var parent = memberRef.Parent;
+
+						if (parent.Kind == HandleKind.TypeReference) {
+
+							var parentTypeRef = metaReader.GetTypeReference ((TypeReferenceHandle)parent);
+
+							var attrName = metaReader.GetString (parentTypeRef.Name);
+
+							if (attrName == "InspectorAttribute") {
+
+								var fieldName = metaReader.GetString (fieldDef.Name);
+								inspectorFieldNames.Add (fieldName);
+								Console.WriteLine ("Found inspector field: {0}", fieldName);
+
+								var blobReader =  metaReader.GetBlobReader( (BlobHandle) ca.Value);
+
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+								Console.WriteLine("Blob: {0}", blobReader.ReadByte());
+
+								//Console.WriteLine("Blob: {0}", blobReader.ReadSerializedString());
+								//Console.WriteLine("Blob: {0}", blobReader.ReadUInt16());
+								//Console.WriteLine("Blob: {0}", blobReader.ReadSerializedString());
+
+							}
+
+							//Console.WriteLine("CustomAttr MemberReference {0} {1}", , metaReader.GetString(memberRef.Name));
+
+						}
+					}
+				}
+			}
+
+			var methods = typeDef.GetMethods ();
+
+			foreach (var methodHandle in methods) {
+				var methodDef = metaReader.GetMethodDefinition (methodHandle);
+
+				if (metaReader.GetString (methodDef.Name) == ".ctor") {
+					var body = peFile.GetMethodBody (methodDef.RelativeVirtualAddress);
+				}
+
+			}
+
+			return true;
+
+		}
+
+		public void InspectILBlock (
+			ImmutableArray<byte> ilBytes,
+			int length,
+			IReadOnlyList<HandlerSpan> spans = null,
+			int blockOffset = 0,
+			IReadOnlyDictionary<int, string> markers = null)
+		{
+			if (ilBytes == null) {
+				return;
+			}
+
+			int spanIndex = 0;
+			int curIndex = InspectILBlock (ilBytes, length, spans, blockOffset, 0, spanIndex, markers, out spanIndex);
+		}
+
+		private int InspectILBlock (
+			ImmutableArray<byte> ilBytes,
+			int length,
+			IReadOnlyList<HandlerSpan> spans,
+			int blockOffset,
+			int curIndex,
+			int spanIndex,
+			IReadOnlyDictionary<int, string> markers,
+			out int nextSpanIndex)
+		{
+			int lastSpanIndex = spanIndex - 1;
+
+			while (curIndex < length) {
+				if (lastSpanIndex > 0 && StartsFilterHandler (spans, lastSpanIndex, curIndex + blockOffset)) {
+
+				}
+
+				if (StartsSpan (spans, spanIndex, curIndex + blockOffset)) {
+					curIndex = InspectILBlock (ilBytes, length, spans, blockOffset, curIndex, spanIndex + 1, markers, out spanIndex);
+				} else {
+					int ilOffset = curIndex + blockOffset;
+					string marker;
+					if (markers != null && markers.TryGetValue (ilOffset, out marker)) {
+					} else {
+					}
+
+					OpCode opCode;
+					int expectedSize;
+
+					byte op1 = ilBytes [curIndex++];
+					if (op1 == 0xfe && curIndex < length) {
+						byte op2 = ilBytes [curIndex++];
+						opCode = s_twoByteOpCodes [op2];
+						expectedSize = 2;
+					} else {
+						opCode = s_oneByteOpCodes [op1];
+						expectedSize = 1;
+					}
+
+					if (opCode.Size != expectedSize) {
+						//sb.AppendLine(string.Format("  <unknown 0x{0}{1:X2}>", expectedSize == 2 ? "fe" : "", op1));
+						continue;
+					}
+
+					//sb.Append("  ");
+					//sb.AppendFormat(opCode.OperandType == OperandType.InlineNone ? "{0}" : "{0,-10}", opCode);
+
+					switch (opCode.OperandType) {
+					case OperandType.InlineField:
+
+                          // read token
+						uint fieldToken = ReadUInt32 (ilBytes, ref curIndex);
+                          // get the kind
+						uint tokenKind = fieldToken & TokenTypeIds.TokenTypeMask;
+                          // and the rowId
+						uint rowId = fieldToken & TokenTypeIds.RIDMask;
+
+						var fieldHandle = MetadataTokens.FieldDefinitionHandle ((int)rowId);
+
+						var fieldDef = metaReader.GetFieldDefinition (fieldHandle);
+
+						break;
+					case OperandType.InlineMethod:
+					case OperandType.InlineTok:
+					case OperandType.InlineType:
+						ReadUInt32 (ilBytes, ref curIndex);
+						break;
+
+					case OperandType.InlineSig: // signature (calli), not emitted by C#/VB
+						ReadUInt32 (ilBytes, ref curIndex);
+						break;
+
+					case OperandType.InlineString:
+                          //sb.Append(" 391 ");
+                          //sb.Append(VisualizeUserString());
+
+						uint stringToken = ReadUInt32 (ilBytes, ref curIndex);
+
+                          // get the kind
+                          //uint tokenKind = stringToken & TokenTypeIds.TokenTypeMask;
+                          // and the rowId
+                          //uint rowId = stringToken & TokenTypeIds.RIDMask;
+
+
+						UserStringHandle handle = MetadataTokens.UserStringHandle ((int)stringToken);
+						string stringValue = metaReader.GetUserString (handle);
+
+                          //sb.AppendFormat("\"{0}\"", );
+
+
+						break;
+
+					case OperandType.InlineNone:
+                          // ldc.i4.1 // load 1 for instance
+                          //sb.AppendFormat(" InlineNone");
+						break;
+
+					case OperandType.ShortInlineI:
+						ReadSByte (ilBytes, ref curIndex);
+						break;
+
+					case OperandType.ShortInlineVar:
+						ReadByte (ilBytes, ref curIndex);
+						break;
+
+					case OperandType.InlineVar:
+						ReadUInt16 (ilBytes, ref curIndex);
+						break;
+
+					case OperandType.InlineI:
+						ReadUInt32 (ilBytes, ref curIndex);
+						break;
+
+					case OperandType.InlineI8:
+						ReadUInt64 (ilBytes, ref curIndex);
+						break;
+
+					case OperandType.ShortInlineR:
+						{
+							var value = ReadSingle (ilBytes, ref curIndex);
+							if (value == 0 && 1 / value < 0) {
+								//sb.Append(" 423 -0.0");
+							} else {
+								//sb.AppendFormat("477 {0}", value.ToString(CultureInfo.InvariantCulture));
+							}
+						}
+						break;
+
+					case OperandType.InlineR:
+						{
+							var value = ReadDouble (ilBytes, ref curIndex);
+							if (value == 0 && 1 / value < 0) {
+								//sb.Append("437 -0.0");
+							} else {
+								//sb.AppendFormat("441 {0}", value.ToString(CultureInfo.InvariantCulture));
+							}
+						}
+						break;
+
+					case OperandType.ShortInlineBrTarget:
+						var sbyteValue = ReadSByte (ilBytes, ref curIndex) + curIndex + blockOffset;
+						break;
+
+					case OperandType.InlineBrTarget:
+						var int32value = ReadInt32 (ilBytes, ref curIndex) + curIndex + blockOffset;
+						break;
+
+					case OperandType.InlineSwitch:
+						int labelCount = ReadInt32 (ilBytes, ref curIndex);
+						int instrEnd = curIndex + labelCount * 4;
+						for (int i = 0; i < labelCount; i++) {
+							var int32LabelValue = ReadInt32 (ilBytes, ref curIndex) + instrEnd + blockOffset;
+							//sb.AppendLine((i == labelCount - 1) ? ")" : ",");
+						}
+						break;
+
+					default:
+						throw new InvalidOperationException ();
+					//throw ExceptionUtilities.UnexpectedValue(opCode.OperandType);
+					}
+
+					//sb.AppendLine();
+				}
+
+				if (EndsSpan (spans, lastSpanIndex, curIndex + blockOffset)) {
+					break;
+				}
+			}
+
+			nextSpanIndex = spanIndex;
+			return curIndex;
+		}
+
+		TypeDefinition typeDef;
+		PEReader peFile;
+		MetadataReader metaReader;
+
+		private static readonly OpCode[] s_oneByteOpCodes;
+		private static readonly OpCode[] s_twoByteOpCodes;
+
+		static CSComponentInspector ()
+		{
+			s_oneByteOpCodes = new OpCode[0x100];
+			s_twoByteOpCodes = new OpCode[0x100];
+
+			var typeOfOpCode = typeof(OpCode);
+
+			foreach (FieldInfo fi in typeof(OpCodes).GetTypeInfo().DeclaredFields) {
+				if (fi.FieldType != typeOfOpCode) {
+					continue;
+				}
+
+				OpCode opCode = (OpCode)fi.GetValue (null);
+				var value = unchecked((ushort)opCode.Value);
+				if (value < 0x100) {
+					s_oneByteOpCodes [value] = opCode;
+				} else if ((value & 0xff00) == 0xfe00) {
+					s_twoByteOpCodes [value & 0xff] = opCode;
+				}
+			}
+		}
+
+		private static ulong ReadUInt64 (ImmutableArray<byte> buffer, ref int pos)
+		{
+			ulong result =
+				buffer [pos] |
+				(ulong)buffer [pos + 1] << 8 |
+				(ulong)buffer [pos + 2] << 16 |
+				(ulong)buffer [pos + 3] << 24 |
+				(ulong)buffer [pos + 4] << 32 |
+				(ulong)buffer [pos + 5] << 40 |
+				(ulong)buffer [pos + 6] << 48 |
+				(ulong)buffer [pos + 7] << 56;
+
+			pos += sizeof(ulong);
+			return result;
+		}
+
+		private static uint ReadUInt32 (ImmutableArray<byte> buffer, ref int pos)
+		{
+			uint result = buffer [pos] | (uint)buffer [pos + 1] << 8 | (uint)buffer [pos + 2] << 16 | (uint)buffer [pos + 3] << 24;
+			pos += sizeof(uint);
+			return result;
+		}
+
+		private static int ReadInt32 (ImmutableArray<byte> buffer, ref int pos)
+		{
+			return unchecked((int)ReadUInt32 (buffer, ref pos));
+		}
+
+		private static ushort ReadUInt16 (ImmutableArray<byte> buffer, ref int pos)
+		{
+			ushort result = (ushort)(buffer [pos] | buffer [pos + 1] << 8);
+			pos += sizeof(ushort);
+			return result;
+		}
+
+		private static byte ReadByte (ImmutableArray<byte> buffer, ref int pos)
+		{
+			byte result = buffer [pos];
+			pos += sizeof(byte);
+			return result;
+		}
+
+		private static sbyte ReadSByte (ImmutableArray<byte> buffer, ref int pos)
+		{
+			sbyte result = unchecked((sbyte)buffer [pos]);
+			pos += 1;
+			return result;
+		}
+
+		private unsafe static float ReadSingle (ImmutableArray<byte> buffer, ref int pos)
+		{
+			uint value = ReadUInt32 (buffer, ref pos);
+			return *(float*)&value;
+		}
+
+		private unsafe static double ReadDouble (ImmutableArray<byte> buffer, ref int pos)
+		{
+			ulong value = ReadUInt64 (buffer, ref pos);
+			return *(double*)&value;
+		}
+
+		public enum HandlerKind
+		{
+			Try,
+			Catch,
+			Filter,
+			Finally,
+			Fault
+		}
+
+		public struct HandlerSpan : IComparable<HandlerSpan>
+		{
+			public readonly HandlerKind Kind;
+			public readonly object ExceptionType;
+			public readonly int StartOffset;
+			public readonly int FilterHandlerStart;
+			public readonly int EndOffset;
+
+			public HandlerSpan (HandlerKind kind, object exceptionType, int startOffset, int endOffset, int filterHandlerStart = 0)
+			{
+				this.Kind = kind;
+				this.ExceptionType = exceptionType;
+				this.StartOffset = startOffset;
+				this.EndOffset = endOffset;
+				this.FilterHandlerStart = filterHandlerStart;
+			}
+
+			public int CompareTo (HandlerSpan other)
+			{
+				int result = this.StartOffset - other.StartOffset;
+				if (result == 0) {
+					// Both blocks have same start. Order larger (outer) before smaller (inner).
+					result = other.EndOffset - this.EndOffset;
+				}
+
+				return result;
+			}
+
+			public string ToString (CSComponentInspector visualizer)
+			{
+				switch (this.Kind) {
+				default:
+					return ".try";
+				case HandlerKind.Catch:
+					return "catch **exceptiontype**";// + visualizer.VisualizeLocalType(this.ExceptionType);
+				case HandlerKind.Filter:
+					return "filter";
+				case HandlerKind.Finally:
+					return "finally";
+				case HandlerKind.Fault:
+					return "fault";
+				}
+			}
+
+			public override string ToString ()
+			{
+				throw new NotSupportedException ("Use ToString(CSComponentInspector)");
+			}
+		}
+
+		private static bool StartsSpan (IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+		{
+			return spans != null && spanIndex < spans.Count && spans [spanIndex].StartOffset == (uint)curIndex;
+		}
+
+		private static bool EndsSpan (IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+		{
+			return spans != null && spanIndex >= 0 && spans [spanIndex].EndOffset == (uint)curIndex;
+		}
+
+		private static bool StartsFilterHandler (IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+		{
+			return spans != null &&
+			spanIndex < spans.Count &&
+			spans [spanIndex].Kind == HandlerKind.Filter &&
+			spans [spanIndex].FilterHandlerStart == (uint)curIndex;
+		}
+
+		public static IReadOnlyList<HandlerSpan> GetHandlerSpans (ImmutableArray<ExceptionRegion> entries)
+		{
+			if (entries.Length == 0) {
+				return new HandlerSpan[0];
+			}
+
+			var result = new List<HandlerSpan> ();
+			foreach (ExceptionRegion entry in entries) {
+				int tryStartOffset = entry.TryOffset;
+				int tryEndOffset = entry.TryOffset + entry.TryLength;
+				var span = new HandlerSpan (HandlerKind.Try, null, tryStartOffset, tryEndOffset);
+
+				if (result.Count == 0 || span.CompareTo (result [result.Count - 1]) != 0) {
+					result.Add (span);
+				}
+			}
+
+			foreach (ExceptionRegion entry in entries) {
+				int handlerStartOffset = entry.HandlerOffset;
+				int handlerEndOffset = entry.HandlerOffset + entry.HandlerLength;
+
+				HandlerSpan span;
+				switch (entry.Kind) {
+				case ExceptionRegionKind.Catch:
+					span = new HandlerSpan (HandlerKind.Catch, MetadataTokens.GetToken (entry.CatchType), handlerStartOffset, handlerEndOffset);
+					break;
+
+				case ExceptionRegionKind.Fault:
+					span = new HandlerSpan (HandlerKind.Fault, null, handlerStartOffset, handlerEndOffset);
+					break;
+
+				case ExceptionRegionKind.Filter:
+					span = new HandlerSpan (HandlerKind.Filter, null, handlerStartOffset, handlerEndOffset, entry.FilterOffset);
+					break;
+
+				case ExceptionRegionKind.Finally:
+					span = new HandlerSpan (HandlerKind.Finally, null, handlerStartOffset, handlerEndOffset);
+					break;
+
+				default:
+					throw new InvalidOperationException ();
+				}
+
+				result.Add (span);
+			}
+
+			return result;
+		}
+
+	}
+
+	internal static class TokenTypeIds
+	{
+		internal const uint Module = 0x00000000;
+		internal const uint TypeRef = 0x01000000;
+		internal const uint TypeDef = 0x02000000;
+		internal const uint FieldDef = 0x04000000;
+		internal const uint MethodDef = 0x06000000;
+		internal const uint ParamDef = 0x08000000;
+		internal const uint InterfaceImpl = 0x09000000;
+		internal const uint MemberRef = 0x0a000000;
+		internal const uint CustomAttribute = 0x0c000000;
+		internal const uint Permission = 0x0e000000;
+		internal const uint Signature = 0x11000000;
+		internal const uint Event = 0x14000000;
+		internal const uint Property = 0x17000000;
+		internal const uint ModuleRef = 0x1a000000;
+		internal const uint TypeSpec = 0x1b000000;
+		internal const uint Assembly = 0x20000000;
+		internal const uint AssemblyRef = 0x23000000;
+		internal const uint File = 0x26000000;
+		internal const uint ExportedType = 0x27000000;
+		internal const uint ManifestResource = 0x28000000;
+		internal const uint GenericParam = 0x2a000000;
+		internal const uint MethodSpec = 0x2b000000;
+		internal const uint GenericParamConstraint = 0x2c000000;
+		internal const uint String = 0x70000000;
+		internal const uint Name = 0x71000000;
+		internal const uint BaseType = 0x72000000;
+		// Leave this on the high end value. This does not correspond to metadata table???
+
+		internal const uint RIDMask = 0x00FFFFFF;
+		internal const uint TokenTypeMask = 0xFF000000;
+	}
+
+
+}

+ 644 - 0
Build/AtomicNETTest/AtomicEditor/CSComponentInspectorOld.cs

@@ -0,0 +1,644 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+#if nope
+
+using System.Collections.Immutable;
+//using Roslyn.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using System.Reflection.Emit;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+namespace AtomicEditor
+{
+  public class CSComponentInspector
+  {
+      private static readonly OpCode[] s_oneByteOpCodes;
+      private static readonly OpCode[] s_twoByteOpCodes;
+
+      public static readonly CSComponentInspector Instance = new CSComponentInspector();
+
+      static CSComponentInspector()
+      {
+          s_oneByteOpCodes = new OpCode[0x100];
+          s_twoByteOpCodes = new OpCode[0x100];
+
+          var typeOfOpCode = typeof(OpCode);
+
+          foreach (FieldInfo fi in typeof(OpCodes).GetTypeInfo().DeclaredFields)
+          {
+              if (fi.FieldType != typeOfOpCode)
+              {
+                  continue;
+              }
+
+              OpCode opCode = (OpCode)fi.GetValue(null);
+              var value = unchecked((ushort)opCode.Value);
+              if (value < 0x100)
+              {
+                  s_oneByteOpCodes[value] = opCode;
+              }
+              else if ((value & 0xff00) == 0xfe00)
+              {
+                  s_twoByteOpCodes[value & 0xff] = opCode;
+              }
+          }
+      }
+
+      public enum HandlerKind
+      {
+          Try,
+          Catch,
+          Filter,
+          Finally,
+          Fault
+      }
+
+      public struct HandlerSpan : IComparable<HandlerSpan>
+      {
+          public readonly HandlerKind Kind;
+          public readonly object ExceptionType;
+          public readonly int StartOffset;
+          public readonly int FilterHandlerStart;
+          public readonly int EndOffset;
+
+          public HandlerSpan(HandlerKind kind, object exceptionType, int startOffset, int endOffset, int filterHandlerStart = 0)
+          {
+              this.Kind = kind;
+              this.ExceptionType = exceptionType;
+              this.StartOffset = startOffset;
+              this.EndOffset = endOffset;
+              this.FilterHandlerStart = filterHandlerStart;
+          }
+
+          public int CompareTo(HandlerSpan other)
+          {
+              int result = this.StartOffset - other.StartOffset;
+              if (result == 0)
+              {
+                  // Both blocks have same start. Order larger (outer) before smaller (inner).
+                  result = other.EndOffset - this.EndOffset;
+              }
+
+              return result;
+          }
+
+          public string ToString(CSComponentInspector visualizer)
+          {
+              switch (this.Kind)
+              {
+                  default:
+                      return ".try";
+                  case HandlerKind.Catch:
+                      return "catch " + visualizer.VisualizeLocalType(this.ExceptionType);
+                  case HandlerKind.Filter:
+                      return "filter";
+                  case HandlerKind.Finally:
+                      return "finally";
+                  case HandlerKind.Fault:
+                      return "fault";
+              }
+          }
+
+          public override string ToString()
+          {
+              throw new NotSupportedException("Use ToString(CSComponentInspector)");
+          }
+      }
+
+      public struct LocalInfo
+      {
+          public readonly string Name;
+          public readonly bool IsPinned;
+          public readonly bool IsByRef;
+          public readonly object Type; // ITypeReference or ITypeSymbol
+
+          public LocalInfo(string name, object type, bool isPinned, bool isByRef)
+          {
+              Name = name;
+              Type = type;
+              IsPinned = isPinned;
+              IsByRef = isByRef;
+          }
+      }
+
+      public const string IndentString = "  ";
+
+      public string VisualizeUserString(uint token)
+      {
+        return "";
+
+      }
+
+      public string VisualizeSymbol(uint token)
+      {
+        return "";
+      }
+
+      public string VisualizeLocalType(object type)
+      {
+        return "";
+      }
+
+      private static ulong ReadUInt64(ImmutableArray<byte> buffer, ref int pos)
+      {
+          ulong result =
+              buffer[pos] |
+              (ulong)buffer[pos + 1] << 8 |
+              (ulong)buffer[pos + 2] << 16 |
+              (ulong)buffer[pos + 3] << 24 |
+              (ulong)buffer[pos + 4] << 32 |
+              (ulong)buffer[pos + 5] << 40 |
+              (ulong)buffer[pos + 6] << 48 |
+              (ulong)buffer[pos + 7] << 56;
+
+          pos += sizeof(ulong);
+          return result;
+      }
+
+      private static uint ReadUInt32(ImmutableArray<byte> buffer, ref int pos)
+      {
+          uint result = buffer[pos] | (uint)buffer[pos + 1] << 8 | (uint)buffer[pos + 2] << 16 | (uint)buffer[pos + 3] << 24;
+          pos += sizeof(uint);
+          return result;
+      }
+
+      private static int ReadInt32(ImmutableArray<byte> buffer, ref int pos)
+      {
+          return unchecked((int)ReadUInt32(buffer, ref pos));
+      }
+
+      private static ushort ReadUInt16(ImmutableArray<byte> buffer, ref int pos)
+      {
+          ushort result = (ushort)(buffer[pos]| buffer[pos + 1] << 8);
+          pos += sizeof(ushort);
+          return result;
+      }
+
+      private static byte ReadByte(ImmutableArray<byte> buffer, ref int pos)
+      {
+          byte result = buffer[pos];
+          pos += sizeof(byte);
+          return result;
+      }
+
+      private static sbyte ReadSByte(ImmutableArray<byte> buffer, ref int pos)
+      {
+          sbyte result = unchecked((sbyte)buffer[pos]);
+          pos += 1;
+          return result;
+      }
+
+      private unsafe static float ReadSingle(ImmutableArray<byte> buffer, ref int pos)
+      {
+          uint value = ReadUInt32(buffer, ref pos);
+          return *(float*)&value;
+      }
+
+      private unsafe static double ReadDouble(ImmutableArray<byte> buffer, ref int pos)
+      {
+          ulong value = ReadUInt64(buffer, ref pos);
+          return *(double*)&value;
+      }
+
+      public void VisualizeHeader(StringBuilder sb, int codeSize, int maxStack, ImmutableArray<LocalInfo> locals)
+      {
+          if (codeSize >= 0 && maxStack >= 0)
+          {
+              if (codeSize == 0)
+              {
+                  sb.AppendLine("  // Unrealized IL");
+              }
+              else
+              {
+                  sb.AppendLine(string.Format("  // Code size {0,8} (0x{0:x})", codeSize));
+              }
+
+              sb.AppendLine(string.Format("  .maxstack  {0}", maxStack));
+          }
+
+          int i = 0;
+          foreach (var local in locals)
+          {
+              sb.Append(i == 0 ? "  .locals init (" : new string(' ', "  .locals init (".Length));
+              if (local.IsPinned)
+              {
+                  sb.Append("pinned ");
+              }
+
+              sb.Append(VisualizeLocalType(local.Type));
+              if (local.IsByRef)
+              {
+                  sb.Append("&");
+              }
+
+              sb.Append(" ");
+              sb.Append("V_" + i);
+
+              sb.Append(i == locals.Length - 1 ? ")" : ",");
+
+              var name = local.Name;
+              if (name != null)
+              {
+                  sb.Append(" //");
+                  sb.Append(name);
+              }
+
+              sb.AppendLine();
+
+              i++;
+          }
+      }
+
+      public string DumpMethod(
+          MetadataReader reader,
+          TypeDefinition typeDef,
+          int maxStack,
+          ImmutableArray<byte> ilBytes,
+          ImmutableArray<LocalInfo> locals,
+          IReadOnlyList<HandlerSpan> exceptionHandlers,
+          IReadOnlyDictionary<int, string> markers = null)
+      {
+          var builder = new StringBuilder();
+          this.DumpMethod(reader, typeDef, builder, maxStack, ilBytes, locals, exceptionHandlers, markers);
+          return builder.ToString();
+      }
+
+      public void DumpMethod(
+          MetadataReader reader,
+          TypeDefinition typeDef,
+          StringBuilder sb,
+          int maxStack,
+          ImmutableArray<byte> ilBytes,
+          ImmutableArray<LocalInfo> locals,
+          IReadOnlyList<HandlerSpan> exceptionHandlers,
+          IReadOnlyDictionary<int, string> markers = null)
+      {
+          sb.AppendLine("{");
+
+          VisualizeHeader(sb, ilBytes.Length, maxStack, locals);
+          DumpILBlock(reader, typeDef, ilBytes, ilBytes.Length, sb, exceptionHandlers, 0, markers);
+
+          sb.AppendLine("}");
+      }
+
+      /// <summary>
+      /// Dumps all instructions in the stream into provided string builder.
+      /// The blockOffset specifies the relative position of the block within method body (if known).
+      /// </summary>
+      public void DumpILBlock(
+          MetadataReader reader,
+          TypeDefinition typeDef,
+          ImmutableArray<byte> ilBytes,
+          int length,
+          StringBuilder sb,
+          IReadOnlyList<HandlerSpan> spans = null,
+          int blockOffset = 0,
+          IReadOnlyDictionary<int, string> markers = null)
+      {
+          if (ilBytes == null)
+          {
+              return;
+          }
+
+          int spanIndex = 0;
+          int curIndex = DumpILBlock(reader, typeDef, ilBytes, length, sb, spans, blockOffset, 0, spanIndex, IndentString, markers, out spanIndex);
+      }
+
+      private int DumpILBlock(
+          MetadataReader reader,
+          TypeDefinition typeDef,
+          ImmutableArray<byte> ilBytes,
+          int length,
+          StringBuilder sb,
+          IReadOnlyList<HandlerSpan> spans,
+          int blockOffset,
+          int curIndex,
+          int spanIndex,
+          string indent,
+          IReadOnlyDictionary<int, string> markers,
+          out int nextSpanIndex)
+      {
+          int lastSpanIndex = spanIndex - 1;
+
+          while (curIndex < length)
+          {
+              if (lastSpanIndex > 0 && StartsFilterHandler(spans, lastSpanIndex, curIndex + blockOffset))
+              {
+                  sb.Append(indent.Substring(0, indent.Length - IndentString.Length));
+                  sb.Append("}  // end filter");
+                  sb.AppendLine();
+
+                  sb.Append(indent.Substring(0, indent.Length - IndentString.Length));
+                  sb.Append("{  // handler");
+                  sb.AppendLine();
+              }
+
+              if (StartsSpan(spans, spanIndex, curIndex + blockOffset))
+              {
+                  sb.Append(indent);
+                  sb.Append(spans[spanIndex].ToString(this));
+                  sb.AppendLine();
+                  sb.Append(indent);
+                  sb.Append("{");
+                  sb.AppendLine();
+
+                  curIndex = DumpILBlock(reader, typeDef, ilBytes, length, sb, spans, blockOffset, curIndex, spanIndex + 1, indent + IndentString, markers, out spanIndex);
+
+                  sb.Append(indent);
+                  sb.Append("}");
+                  sb.AppendLine();
+              }
+              else
+              {
+                  int ilOffset = curIndex + blockOffset;
+                  string marker;
+                  if (markers != null && markers.TryGetValue(ilOffset, out marker))
+                  {
+                      sb.Append(indent.Substring(0, indent.Length - marker.Length));
+                      sb.Append(marker);
+                  }
+                  else
+                  {
+                      sb.Append(indent);
+                  }
+
+                  sb.AppendFormat("IL_{0:x4}:", ilOffset);
+
+                  OpCode opCode;
+                  int expectedSize;
+
+                  byte op1 = ilBytes[curIndex++];
+                  if (op1 == 0xfe && curIndex < length)
+                  {
+                      byte op2 = ilBytes[curIndex++];
+                      opCode = s_twoByteOpCodes[op2];
+                      expectedSize = 2;
+                  }
+                  else
+                  {
+                      opCode = s_oneByteOpCodes[op1];
+                      expectedSize = 1;
+                  }
+
+                  if (opCode.Size != expectedSize)
+                  {
+                      sb.AppendLine(string.Format("  <unknown 0x{0}{1:X2}>", expectedSize == 2 ? "fe" : "", op1));
+                      continue;
+                  }
+
+                  sb.Append("  ");
+                  sb.AppendFormat(opCode.OperandType == OperandType.InlineNone ? "{0}" : "{0,-10}", opCode);
+
+                  switch (opCode.OperandType)
+                  {
+                      case OperandType.InlineField:
+
+                          // read token
+                          uint fieldToken = ReadUInt32(ilBytes, ref curIndex);
+                          // get the kind
+                          uint tokenKind = fieldToken & TokenTypeIds.TokenTypeMask;
+                          // and the rowId
+                          uint rowId = fieldToken & TokenTypeIds.RIDMask;
+
+                          var fieldHandle = MetadataTokens.FieldDefinitionHandle( (int) rowId);
+
+                          var fieldDef = reader.GetFieldDefinition(fieldHandle);
+
+         								  sb.AppendFormat("{0}", reader.GetString(fieldDef.Name));
+
+
+                          break;
+                      case OperandType.InlineMethod:
+                      case OperandType.InlineTok:
+                      case OperandType.InlineType:
+                          sb.Append(VisualizeSymbol(ReadUInt32(ilBytes, ref curIndex)));
+                          break;
+
+                      case OperandType.InlineSig: // signature (calli), not emitted by C#/VB
+                          sb.AppendFormat(" 387 0x{0:x}", ReadUInt32(ilBytes, ref curIndex));
+                          break;
+
+                      case OperandType.InlineString:
+                          //sb.Append(" 391 ");
+                          //sb.Append(VisualizeUserString());
+
+                          uint stringToken = ReadUInt32(ilBytes, ref curIndex);
+
+                          // get the kind
+                          //uint tokenKind = stringToken & TokenTypeIds.TokenTypeMask;
+                          // and the rowId
+                          //uint rowId = stringToken & TokenTypeIds.RIDMask;
+
+
+                          UserStringHandle handle = MetadataTokens.UserStringHandle((int)stringToken);
+
+                          sb.AppendFormat("\"{0}\"", reader.GetUserString(handle));
+
+
+                          break;
+
+                      case OperandType.InlineNone:
+                          // ldc.i4.1 // load 1 for instance
+                          //sb.AppendFormat(" InlineNone");
+                          break;
+
+                      case OperandType.ShortInlineI:
+                          sb.AppendFormat(" 399 {0}", ReadSByte(ilBytes, ref curIndex));
+                          break;
+
+                      case OperandType.ShortInlineVar:
+                          sb.AppendFormat(" 403 V_{0}", ReadByte(ilBytes, ref curIndex));
+                          break;
+
+                      case OperandType.InlineVar:
+                          sb.AppendFormat(" 407 V_{0}", ReadUInt16(ilBytes, ref curIndex));
+                          break;
+
+                      case OperandType.InlineI:
+                          sb.AppendFormat(" 411 0x{0:x}", ReadUInt32(ilBytes, ref curIndex));
+                          break;
+
+                      case OperandType.InlineI8:
+                          sb.AppendFormat(" 415 0x{0:x8}", ReadUInt64(ilBytes, ref curIndex));
+                          break;
+
+                      case OperandType.ShortInlineR:
+                          {
+                              var value = ReadSingle(ilBytes, ref curIndex);
+                              if (value == 0 && 1 / value < 0)
+                              {
+                                  sb.Append(" 423 -0.0");
+                              }
+                              else
+                              {
+                                  sb.AppendFormat("477 {0}", value.ToString(CultureInfo.InvariantCulture));
+                              }
+                          }
+                          break;
+
+                      case OperandType.InlineR:
+                          {
+                              var value = ReadDouble(ilBytes, ref curIndex);
+                              if (value == 0 && 1 / value < 0)
+                              {
+                                  sb.Append("437 -0.0");
+                              }
+                              else
+                              {
+                                  sb.AppendFormat("441 {0}", value.ToString(CultureInfo.InvariantCulture));
+                              }
+                          }
+                          break;
+
+                      case OperandType.ShortInlineBrTarget:
+                          sb.AppendFormat(" 447 IL_{0:x4}", ReadSByte(ilBytes, ref curIndex) + curIndex + blockOffset);
+                          break;
+
+                      case OperandType.InlineBrTarget:
+                          sb.AppendFormat(" 451 IL_{0:x4}", ReadInt32(ilBytes, ref curIndex) + curIndex + blockOffset);
+                          break;
+
+                      case OperandType.InlineSwitch:
+                          int labelCount = ReadInt32(ilBytes, ref curIndex);
+                          int instrEnd = curIndex + labelCount * 4;
+                          sb.AppendLine("(");
+                          for (int i = 0; i < labelCount; i++)
+                          {
+                              sb.AppendFormat("        IL_{0:x4}", ReadInt32(ilBytes, ref curIndex) + instrEnd + blockOffset);
+                              sb.AppendLine((i == labelCount - 1) ? ")" : ",");
+                          }
+                          break;
+
+                      default:
+                          throw new InvalidOperationException();
+                          //throw ExceptionUtilities.UnexpectedValue(opCode.OperandType);
+                  }
+
+                  sb.AppendLine();
+              }
+
+              if (EndsSpan(spans, lastSpanIndex, curIndex + blockOffset))
+              {
+                  break;
+              }
+          }
+
+          nextSpanIndex = spanIndex;
+          return curIndex;
+      }
+
+      private static bool StartsSpan(IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+      {
+          return spans != null && spanIndex < spans.Count && spans[spanIndex].StartOffset == (uint)curIndex;
+      }
+
+      private static bool EndsSpan(IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+      {
+          return spans != null && spanIndex >= 0 && spans[spanIndex].EndOffset == (uint)curIndex;
+      }
+
+      private static bool StartsFilterHandler(IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+      {
+          return spans != null &&
+              spanIndex < spans.Count &&
+              spans[spanIndex].Kind == HandlerKind.Filter &&
+              spans[spanIndex].FilterHandlerStart == (uint)curIndex;
+      }
+
+      public static IReadOnlyList<HandlerSpan> GetHandlerSpans(ImmutableArray<ExceptionRegion> entries)
+      {
+          if (entries.Length == 0)
+          {
+              return new HandlerSpan[0];
+          }
+
+          var result = new List<HandlerSpan>();
+          foreach (ExceptionRegion entry in entries)
+          {
+              int tryStartOffset = entry.TryOffset;
+              int tryEndOffset = entry.TryOffset + entry.TryLength;
+              var span = new HandlerSpan(HandlerKind.Try, null, tryStartOffset, tryEndOffset);
+
+              if (result.Count == 0 || span.CompareTo(result[result.Count - 1]) != 0)
+              {
+                  result.Add(span);
+              }
+          }
+
+          foreach (ExceptionRegion entry in entries)
+          {
+              int handlerStartOffset = entry.HandlerOffset;
+              int handlerEndOffset = entry.HandlerOffset + entry.HandlerLength;
+
+              HandlerSpan span;
+              switch (entry.Kind)
+              {
+                  case ExceptionRegionKind.Catch:
+                      span = new HandlerSpan(HandlerKind.Catch, MetadataTokens.GetToken(entry.CatchType), handlerStartOffset, handlerEndOffset);
+                      break;
+
+                  case ExceptionRegionKind.Fault:
+                      span = new HandlerSpan(HandlerKind.Fault, null, handlerStartOffset, handlerEndOffset);
+                      break;
+
+                  case ExceptionRegionKind.Filter:
+                      span = new HandlerSpan(HandlerKind.Filter, null, handlerStartOffset, handlerEndOffset, entry.FilterOffset);
+                      break;
+
+                  case ExceptionRegionKind.Finally:
+                      span = new HandlerSpan(HandlerKind.Finally, null, handlerStartOffset, handlerEndOffset);
+                      break;
+
+                  default:
+                      throw new InvalidOperationException();
+              }
+
+              result.Add(span);
+          }
+
+          return result;
+      }
+  }
+
+  internal static class TokenTypeIds {
+      internal const uint Module = 0x00000000;
+      internal const uint TypeRef = 0x01000000;
+      internal const uint TypeDef = 0x02000000;
+      internal const uint FieldDef = 0x04000000;
+      internal const uint MethodDef = 0x06000000;
+      internal const uint ParamDef = 0x08000000;
+      internal const uint InterfaceImpl = 0x09000000;
+      internal const uint MemberRef = 0x0a000000;
+      internal const uint CustomAttribute = 0x0c000000;
+      internal const uint Permission = 0x0e000000;
+      internal const uint Signature = 0x11000000;
+      internal const uint Event = 0x14000000;
+      internal const uint Property = 0x17000000;
+      internal const uint ModuleRef = 0x1a000000;
+      internal const uint TypeSpec = 0x1b000000;
+      internal const uint Assembly = 0x20000000;
+      internal const uint AssemblyRef = 0x23000000;
+      internal const uint File = 0x26000000;
+      internal const uint ExportedType = 0x27000000;
+      internal const uint ManifestResource = 0x28000000;
+      internal const uint GenericParam = 0x2a000000;
+      internal const uint MethodSpec = 0x2b000000;
+      internal const uint GenericParamConstraint = 0x2c000000;
+      internal const uint String = 0x70000000;
+      internal const uint Name = 0x71000000;
+      internal const uint BaseType = 0x72000000;       // Leave this on the high end value. This does not correspond to metadata table???
+
+      internal const uint RIDMask = 0x00FFFFFF;
+      internal const uint TokenTypeMask = 0xFF000000;
+    }
+
+
+}
+
+#endif

+ 555 - 0
Build/AtomicNETTest/AtomicEditor/ILVisualizer.cs

@@ -0,0 +1,555 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+using System.Collections.Immutable;
+//using Roslyn.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using System.Reflection.Emit;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+namespace AtomicEditor
+{
+    public abstract class ILVisualizer
+    {
+        private static readonly OpCode[] s_oneByteOpCodes;
+        private static readonly OpCode[] s_twoByteOpCodes;
+
+        static ILVisualizer()
+        {
+            s_oneByteOpCodes = new OpCode[0x100];
+            s_twoByteOpCodes = new OpCode[0x100];
+
+            var typeOfOpCode = typeof(OpCode);
+
+            foreach (FieldInfo fi in typeof(OpCodes).GetTypeInfo().DeclaredFields)
+            {
+                if (fi.FieldType != typeOfOpCode)
+                {
+                    continue;
+                }
+
+                OpCode opCode = (OpCode)fi.GetValue(null);
+                var value = unchecked((ushort)opCode.Value);
+                if (value < 0x100)
+                {
+                    s_oneByteOpCodes[value] = opCode;
+                }
+                else if ((value & 0xff00) == 0xfe00)
+                {
+                    s_twoByteOpCodes[value & 0xff] = opCode;
+                }
+            }
+        }
+
+        public enum HandlerKind
+        {
+            Try,
+            Catch,
+            Filter,
+            Finally,
+            Fault
+        }
+
+        public struct HandlerSpan : IComparable<HandlerSpan>
+        {
+            public readonly HandlerKind Kind;
+            public readonly object ExceptionType;
+            public readonly int StartOffset;
+            public readonly int FilterHandlerStart;
+            public readonly int EndOffset;
+
+            public HandlerSpan(HandlerKind kind, object exceptionType, int startOffset, int endOffset, int filterHandlerStart = 0)
+            {
+                this.Kind = kind;
+                this.ExceptionType = exceptionType;
+                this.StartOffset = startOffset;
+                this.EndOffset = endOffset;
+                this.FilterHandlerStart = filterHandlerStart;
+            }
+
+            public int CompareTo(HandlerSpan other)
+            {
+                int result = this.StartOffset - other.StartOffset;
+                if (result == 0)
+                {
+                    // Both blocks have same start. Order larger (outer) before smaller (inner).
+                    result = other.EndOffset - this.EndOffset;
+                }
+
+                return result;
+            }
+
+            public string ToString(ILVisualizer visualizer)
+            {
+                switch (this.Kind)
+                {
+                    default:
+                        return ".try";
+                    case HandlerKind.Catch:
+                        return "catch " + visualizer.VisualizeLocalType(this.ExceptionType);
+                    case HandlerKind.Filter:
+                        return "filter";
+                    case HandlerKind.Finally:
+                        return "finally";
+                    case HandlerKind.Fault:
+                        return "fault";
+                }
+            }
+
+            public override string ToString()
+            {
+                throw new NotSupportedException("Use ToString(ILVisualizer)");
+            }
+        }
+
+        public struct LocalInfo
+        {
+            public readonly string Name;
+            public readonly bool IsPinned;
+            public readonly bool IsByRef;
+            public readonly object Type; // ITypeReference or ITypeSymbol
+
+            public LocalInfo(string name, object type, bool isPinned, bool isByRef)
+            {
+                Name = name;
+                Type = type;
+                IsPinned = isPinned;
+                IsByRef = isByRef;
+            }
+        }
+
+        public const string IndentString = "  ";
+
+        public abstract string VisualizeUserString(uint token);
+        public abstract string VisualizeSymbol(uint token);
+        public abstract string VisualizeLocalType(object type);
+
+        private static ulong ReadUInt64(ImmutableArray<byte> buffer, ref int pos)
+        {
+            ulong result =
+                buffer[pos] |
+                (ulong)buffer[pos + 1] << 8 |
+                (ulong)buffer[pos + 2] << 16 |
+                (ulong)buffer[pos + 3] << 24 |
+                (ulong)buffer[pos + 4] << 32 |
+                (ulong)buffer[pos + 5] << 40 |
+                (ulong)buffer[pos + 6] << 48 |
+                (ulong)buffer[pos + 7] << 56;
+
+            pos += sizeof(ulong);
+            return result;
+        }
+
+        private static uint ReadUInt32(ImmutableArray<byte> buffer, ref int pos)
+        {
+            uint result = buffer[pos] | (uint)buffer[pos + 1] << 8 | (uint)buffer[pos + 2] << 16 | (uint)buffer[pos + 3] << 24;
+            pos += sizeof(uint);
+            return result;
+        }
+
+        private static int ReadInt32(ImmutableArray<byte> buffer, ref int pos)
+        {
+            return unchecked((int)ReadUInt32(buffer, ref pos));
+        }
+
+        private static ushort ReadUInt16(ImmutableArray<byte> buffer, ref int pos)
+        {
+            ushort result = (ushort)(buffer[pos]| buffer[pos + 1] << 8);
+            pos += sizeof(ushort);
+            return result;
+        }
+
+        private static byte ReadByte(ImmutableArray<byte> buffer, ref int pos)
+        {
+            byte result = buffer[pos];
+            pos += sizeof(byte);
+            return result;
+        }
+
+        private static sbyte ReadSByte(ImmutableArray<byte> buffer, ref int pos)
+        {
+            sbyte result = unchecked((sbyte)buffer[pos]);
+            pos += 1;
+            return result;
+        }
+
+        private unsafe static float ReadSingle(ImmutableArray<byte> buffer, ref int pos)
+        {
+            uint value = ReadUInt32(buffer, ref pos);
+            return *(float*)&value;
+        }
+
+        private unsafe static double ReadDouble(ImmutableArray<byte> buffer, ref int pos)
+        {
+            ulong value = ReadUInt64(buffer, ref pos);
+            return *(double*)&value;
+        }
+
+        public void VisualizeHeader(StringBuilder sb, int codeSize, int maxStack, ImmutableArray<LocalInfo> locals)
+        {
+            if (codeSize >= 0 && maxStack >= 0)
+            {
+                if (codeSize == 0)
+                {
+                    sb.AppendLine("  // Unrealized IL");
+                }
+                else
+                {
+                    sb.AppendLine(string.Format("  // Code size {0,8} (0x{0:x})", codeSize));
+                }
+
+                sb.AppendLine(string.Format("  .maxstack  {0}", maxStack));
+            }
+
+            int i = 0;
+            foreach (var local in locals)
+            {
+                sb.Append(i == 0 ? "  .locals init (" : new string(' ', "  .locals init (".Length));
+                if (local.IsPinned)
+                {
+                    sb.Append("pinned ");
+                }
+
+                sb.Append(VisualizeLocalType(local.Type));
+                if (local.IsByRef)
+                {
+                    sb.Append("&");
+                }
+
+                sb.Append(" ");
+                sb.Append("V_" + i);
+
+                sb.Append(i == locals.Length - 1 ? ")" : ",");
+
+                var name = local.Name;
+                if (name != null)
+                {
+                    sb.Append(" //");
+                    sb.Append(name);
+                }
+
+                sb.AppendLine();
+
+                i++;
+            }
+        }
+
+        public string DumpMethod(
+            int maxStack,
+            ImmutableArray<byte> ilBytes,
+            ImmutableArray<LocalInfo> locals,
+            IReadOnlyList<HandlerSpan> exceptionHandlers,
+            IReadOnlyDictionary<int, string> markers = null)
+        {
+            var builder = new StringBuilder();
+            this.DumpMethod(builder, maxStack, ilBytes, locals, exceptionHandlers, markers);
+            return builder.ToString();
+        }
+
+        public void DumpMethod(
+            StringBuilder sb,
+            int maxStack,
+            ImmutableArray<byte> ilBytes,
+            ImmutableArray<LocalInfo> locals,
+            IReadOnlyList<HandlerSpan> exceptionHandlers,
+            IReadOnlyDictionary<int, string> markers = null)
+        {
+            sb.AppendLine("{");
+
+            VisualizeHeader(sb, ilBytes.Length, maxStack, locals);
+            DumpILBlock(ilBytes, ilBytes.Length, sb, exceptionHandlers, 0, markers);
+
+            sb.AppendLine("}");
+        }
+
+        /// <summary>
+        /// Dumps all instructions in the stream into provided string builder.
+        /// The blockOffset specifies the relative position of the block within method body (if known).
+        /// </summary>
+        public void DumpILBlock(
+            ImmutableArray<byte> ilBytes,
+            int length,
+            StringBuilder sb,
+            IReadOnlyList<HandlerSpan> spans = null,
+            int blockOffset = 0,
+            IReadOnlyDictionary<int, string> markers = null)
+        {
+            if (ilBytes == null)
+            {
+                return;
+            }
+
+            int spanIndex = 0;
+            int curIndex = DumpILBlock(ilBytes, length, sb, spans, blockOffset, 0, spanIndex, IndentString, markers, out spanIndex);
+            //Debug.Assert(curIndex == length);
+            //Debug.Assert(spans == null || spanIndex == spans.Count);
+        }
+
+        private int DumpILBlock(
+            ImmutableArray<byte> ilBytes,
+            int length,
+            StringBuilder sb,
+            IReadOnlyList<HandlerSpan> spans,
+            int blockOffset,
+            int curIndex,
+            int spanIndex,
+            string indent,
+            IReadOnlyDictionary<int, string> markers,
+            out int nextSpanIndex)
+        {
+            int lastSpanIndex = spanIndex - 1;
+
+            while (curIndex < length)
+            {
+                if (lastSpanIndex > 0 && StartsFilterHandler(spans, lastSpanIndex, curIndex + blockOffset))
+                {
+                    sb.Append(indent.Substring(0, indent.Length - IndentString.Length));
+                    sb.Append("}  // end filter");
+                    sb.AppendLine();
+
+                    sb.Append(indent.Substring(0, indent.Length - IndentString.Length));
+                    sb.Append("{  // handler");
+                    sb.AppendLine();
+                }
+
+                if (StartsSpan(spans, spanIndex, curIndex + blockOffset))
+                {
+                    sb.Append(indent);
+                    sb.Append(spans[spanIndex].ToString(this));
+                    sb.AppendLine();
+                    sb.Append(indent);
+                    sb.Append("{");
+                    sb.AppendLine();
+
+                    curIndex = DumpILBlock(ilBytes, length, sb, spans, blockOffset, curIndex, spanIndex + 1, indent + IndentString, markers, out spanIndex);
+
+                    sb.Append(indent);
+                    sb.Append("}");
+                    sb.AppendLine();
+                }
+                else
+                {
+                    int ilOffset = curIndex + blockOffset;
+                    string marker;
+                    if (markers != null && markers.TryGetValue(ilOffset, out marker))
+                    {
+                        sb.Append(indent.Substring(0, indent.Length - marker.Length));
+                        sb.Append(marker);
+                    }
+                    else
+                    {
+                        sb.Append(indent);
+                    }
+
+                    sb.AppendFormat("IL_{0:x4}:", ilOffset);
+
+                    OpCode opCode;
+                    int expectedSize;
+
+                    byte op1 = ilBytes[curIndex++];
+                    if (op1 == 0xfe && curIndex < length)
+                    {
+                        byte op2 = ilBytes[curIndex++];
+                        opCode = s_twoByteOpCodes[op2];
+                        expectedSize = 2;
+                    }
+                    else
+                    {
+                        opCode = s_oneByteOpCodes[op1];
+                        expectedSize = 1;
+                    }
+
+                    if (opCode.Size != expectedSize)
+                    {
+                        sb.AppendLine(string.Format("  <unknown 0x{0}{1:X2}>", expectedSize == 2 ? "fe" : "", op1));
+                        continue;
+                    }
+
+                    sb.Append("  ");
+                    sb.AppendFormat(opCode.OperandType == OperandType.InlineNone ? "{0}" : "{0,-10}", opCode);
+
+                    switch (opCode.OperandType)
+                    {
+                        case OperandType.InlineField:
+                        case OperandType.InlineMethod:
+                        case OperandType.InlineTok:
+                        case OperandType.InlineType:
+                            sb.Append(' ');
+                            sb.Append(VisualizeSymbol(ReadUInt32(ilBytes, ref curIndex)));
+                            break;
+
+                        case OperandType.InlineSig: // signature (calli), not emitted by C#/VB
+                            sb.AppendFormat(" 0x{0:x}", ReadUInt32(ilBytes, ref curIndex));
+                            break;
+
+                        case OperandType.InlineString:
+                            sb.Append(' ');
+                            sb.Append(VisualizeUserString(ReadUInt32(ilBytes, ref curIndex)));
+                            break;
+
+                        case OperandType.InlineNone:
+                            break;
+
+                        case OperandType.ShortInlineI:
+                            sb.AppendFormat(" {0}", ReadSByte(ilBytes, ref curIndex));
+                            break;
+
+                        case OperandType.ShortInlineVar:
+                            sb.AppendFormat(" V_{0}", ReadByte(ilBytes, ref curIndex));
+                            break;
+
+                        case OperandType.InlineVar:
+                            sb.AppendFormat(" V_{0}", ReadUInt16(ilBytes, ref curIndex));
+                            break;
+
+                        case OperandType.InlineI:
+                            sb.AppendFormat(" 0x{0:x}", ReadUInt32(ilBytes, ref curIndex));
+                            break;
+
+                        case OperandType.InlineI8:
+                            sb.AppendFormat(" 0x{0:x8}", ReadUInt64(ilBytes, ref curIndex));
+                            break;
+
+                        case OperandType.ShortInlineR:
+                            {
+                                var value = ReadSingle(ilBytes, ref curIndex);
+                                if (value == 0 && 1 / value < 0)
+                                {
+                                    sb.Append(" -0.0");
+                                }
+                                else
+                                {
+                                    sb.AppendFormat(" {0}", value.ToString(CultureInfo.InvariantCulture));
+                                }
+                            }
+                            break;
+
+                        case OperandType.InlineR:
+                            {
+                                var value = ReadDouble(ilBytes, ref curIndex);
+                                if (value == 0 && 1 / value < 0)
+                                {
+                                    sb.Append(" -0.0");
+                                }
+                                else
+                                {
+                                    sb.AppendFormat(" {0}", value.ToString(CultureInfo.InvariantCulture));
+                                }
+                            }
+                            break;
+
+                        case OperandType.ShortInlineBrTarget:
+                            sb.AppendFormat(" IL_{0:x4}", ReadSByte(ilBytes, ref curIndex) + curIndex + blockOffset);
+                            break;
+
+                        case OperandType.InlineBrTarget:
+                            sb.AppendFormat(" IL_{0:x4}", ReadInt32(ilBytes, ref curIndex) + curIndex + blockOffset);
+                            break;
+
+                        case OperandType.InlineSwitch:
+                            int labelCount = ReadInt32(ilBytes, ref curIndex);
+                            int instrEnd = curIndex + labelCount * 4;
+                            sb.AppendLine("(");
+                            for (int i = 0; i < labelCount; i++)
+                            {
+                                sb.AppendFormat("        IL_{0:x4}", ReadInt32(ilBytes, ref curIndex) + instrEnd + blockOffset);
+                                sb.AppendLine((i == labelCount - 1) ? ")" : ",");
+                            }
+                            break;
+
+                        default:
+                            throw new InvalidOperationException();
+                            //throw ExceptionUtilities.UnexpectedValue(opCode.OperandType);
+                    }
+
+                    sb.AppendLine();
+                }
+
+                if (EndsSpan(spans, lastSpanIndex, curIndex + blockOffset))
+                {
+                    break;
+                }
+            }
+
+            nextSpanIndex = spanIndex;
+            return curIndex;
+        }
+
+        private static bool StartsSpan(IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+        {
+            return spans != null && spanIndex < spans.Count && spans[spanIndex].StartOffset == (uint)curIndex;
+        }
+
+        private static bool EndsSpan(IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+        {
+            return spans != null && spanIndex >= 0 && spans[spanIndex].EndOffset == (uint)curIndex;
+        }
+
+        private static bool StartsFilterHandler(IReadOnlyList<HandlerSpan> spans, int spanIndex, int curIndex)
+        {
+            return spans != null &&
+                spanIndex < spans.Count &&
+                spans[spanIndex].Kind == HandlerKind.Filter &&
+                spans[spanIndex].FilterHandlerStart == (uint)curIndex;
+        }
+
+        public static IReadOnlyList<HandlerSpan> GetHandlerSpans(ImmutableArray<ExceptionRegion> entries)
+        {
+            if (entries.Length == 0)
+            {
+                return new HandlerSpan[0];
+            }
+
+            var result = new List<HandlerSpan>();
+            foreach (ExceptionRegion entry in entries)
+            {
+                int tryStartOffset = entry.TryOffset;
+                int tryEndOffset = entry.TryOffset + entry.TryLength;
+                var span = new HandlerSpan(HandlerKind.Try, null, tryStartOffset, tryEndOffset);
+
+                if (result.Count == 0 || span.CompareTo(result[result.Count - 1]) != 0)
+                {
+                    result.Add(span);
+                }
+            }
+
+            foreach (ExceptionRegion entry in entries)
+            {
+                int handlerStartOffset = entry.HandlerOffset;
+                int handlerEndOffset = entry.HandlerOffset + entry.HandlerLength;
+
+                HandlerSpan span;
+                switch (entry.Kind)
+                {
+                    case ExceptionRegionKind.Catch:
+                        span = new HandlerSpan(HandlerKind.Catch, MetadataTokens.GetToken(entry.CatchType), handlerStartOffset, handlerEndOffset);
+                        break;
+
+                    case ExceptionRegionKind.Fault:
+                        span = new HandlerSpan(HandlerKind.Fault, null, handlerStartOffset, handlerEndOffset);
+                        break;
+
+                    case ExceptionRegionKind.Filter:
+                        span = new HandlerSpan(HandlerKind.Filter, null, handlerStartOffset, handlerEndOffset, entry.FilterOffset);
+                        break;
+
+                    case ExceptionRegionKind.Finally:
+                        span = new HandlerSpan(HandlerKind.Finally, null, handlerStartOffset, handlerEndOffset);
+                        break;
+
+                    default:
+                        throw new InvalidOperationException();
+                }
+
+                result.Add(span);
+            }
+
+            return result;
+        }
+    }
+}

+ 24 - 0
Build/AtomicNETTest/AtomicEditor/ILVisualizerAsTokens.cs

@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+namespace AtomicEditor
+{
+    public sealed class ILVisualizerAsTokens : ILVisualizer
+    {
+        public static readonly ILVisualizerAsTokens Instance = new ILVisualizerAsTokens();
+
+        public override string VisualizeUserString(uint token)
+        {
+            return string.Format("0x{0:X8}", token);
+        }
+
+        public override string VisualizeSymbol(uint token)
+        {
+            return string.Format("0x{0:X8}", token);
+        }
+
+        public override string VisualizeLocalType(object type)
+        {
+            return string.Format("0x{0:X8}", type); // Should be a token.
+        }
+    }
+}

+ 1803 - 0
Build/AtomicNETTest/AtomicEditor/MetadataVisualizer.cs

@@ -0,0 +1,1803 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+#if nope
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection.Metadata;
+//using System.Reflection.Metadata.Decoding;
+using System.Reflection.Metadata.Ecma335;
+using System.Text;
+
+namespace Roslyn.Test.MetadataUtilities
+{
+    [Flags]
+    public enum MetadataVisualizerOptions
+    {
+        None = 0,
+        ShortenBlobs = 1,
+    }
+
+    public sealed class MetadataVisualizer
+    {
+        private enum BlobKind
+        {
+            None,
+            Key,
+            FileHash,
+
+            MethodSignature,
+            FieldSignature,
+            MemberRefSignature,
+            StandAloneSignature,
+
+            TypeSpec,
+            MethodSpec,
+
+            ConstantValue,
+            Marshalling,
+            PermissionSet,
+            CustomAttribute,
+
+            DocumentName,
+            DocumentHash,
+            SequencePoints,
+            Imports,
+            ImportAlias,
+            ImportNamespace,
+            LocalConstantSignature,
+            CustomDebugInformation,
+
+            Count
+        }
+
+        private readonly TextWriter _writer;
+        private readonly IReadOnlyList<MetadataReader> _readers;
+        private readonly MetadataAggregator _aggregator;
+        private readonly MetadataVisualizerOptions _options;
+
+        // enc map for each delta reader
+        private readonly ImmutableArray<ImmutableArray<EntityHandle>> _encMaps;
+
+        private MetadataReader _reader;
+        private readonly List<string[]> _pendingRows = new List<string[]>();
+        private readonly Dictionary<BlobHandle, BlobKind> _blobKinds = new Dictionary<BlobHandle, BlobKind>();
+
+        private MetadataVisualizer(TextWriter writer, IReadOnlyList<MetadataReader> readers, MetadataVisualizerOptions options = MetadataVisualizerOptions.None)
+        {
+            _writer = writer;
+            _readers = readers;
+            _options = options;
+
+            if (readers.Count > 1)
+            {
+                var deltaReaders = new List<MetadataReader>(readers.Skip(1));
+                _aggregator = new MetadataAggregator(readers[0], deltaReaders);
+
+                _encMaps = ImmutableArray.CreateRange(deltaReaders.Select(reader => ImmutableArray.CreateRange(reader.GetEditAndContinueMapEntries())));
+            }
+        }
+
+        public MetadataVisualizer(MetadataReader reader, TextWriter writer, MetadataVisualizerOptions options = MetadataVisualizerOptions.None)
+            : this(writer, new[] { reader }, options)
+        {
+            _reader = reader;
+        }
+
+        public MetadataVisualizer(IReadOnlyList<MetadataReader> readers, TextWriter writer, MetadataVisualizerOptions options = MetadataVisualizerOptions.None)
+            : this(writer, readers, options)
+        {
+        }
+
+        public void VisualizeAllGenerations()
+        {
+            for (int i = 0; i < _readers.Count; i++)
+            {
+                _writer.WriteLine(">>>");
+                _writer.WriteLine($">>> Generation {i}:");
+                _writer.WriteLine(">>>");
+                _writer.WriteLine();
+
+                Visualize(i);
+            }
+        }
+
+        public void Visualize(int generation = -1)
+        {
+            _reader = (generation >= 0) ? _readers[generation] : _readers[_readers.Count - 1];
+
+            WriteModule();
+            WriteTypeRef();
+            WriteTypeDef();
+            WriteField();
+            WriteMethod();
+            WriteParam();
+            WriteMemberRef();
+            WriteConstant();
+            WriteCustomAttribute();
+            WriteDeclSecurity();
+            WriteStandAloneSig();
+            WriteEvent();
+            WriteProperty();
+            WriteMethodImpl();
+            WriteModuleRef();
+            WriteTypeSpec();
+            WriteEnCLog();
+            WriteEnCMap();
+            WriteAssembly();
+            WriteAssemblyRef();
+            WriteFile();
+            WriteExportedType();
+            WriteManifestResource();
+            WriteGenericParam();
+            WriteMethodSpec();
+            WriteGenericParamConstraint();
+
+            // debug tables:
+            WriteDocument();
+            WriteMethodBody();
+            WriteLocalScope();
+            WriteLocalVariable();
+            WriteLocalConstant();
+            WriteLocalImport();
+            WriteCustomDebugInformation();
+
+            // heaps:
+            WriteUserStrings();
+            WriteStrings();
+            WriteBlobs();
+            WriteGuids();
+        }
+
+        private bool IsDelta
+        {
+            get
+            {
+                return _reader.GetTableRowCount(TableIndex.EncLog) > 0;
+            }
+        }
+
+        private void WriteTableName(TableIndex index)
+        {
+            WriteRows(MakeTableName(index));
+        }
+
+        private string MakeTableName(TableIndex index)
+        {
+            return $"{index} (index: 0x{(byte)index:X2}, size: {_reader.GetTableRowCount(index) * _reader.GetTableRowSize(index)}): ";
+        }
+
+        private void AddHeader(params string[] header)
+        {
+            Debug.Assert(_pendingRows.Count == 0);
+            _pendingRows.Add(header);
+        }
+
+        private void AddRow(params string[] fields)
+        {
+            Debug.Assert(_pendingRows.Count > 0 && _pendingRows.Last().Length == fields.Length);
+            _pendingRows.Add(fields);
+        }
+
+        private void WriteRows(string title)
+        {
+            Debug.Assert(_pendingRows.Count > 0);
+
+            if (_pendingRows.Count == 1)
+            {
+                _pendingRows.Clear();
+                return;
+            }
+
+            _writer.Write(title);
+            _writer.WriteLine();
+
+            string columnSeparator = "  ";
+            int rowNumberWidth = _pendingRows.Count.ToString("x").Length;
+
+            int[] columnWidths = new int[_pendingRows.First().Length];
+            foreach (var row in _pendingRows)
+            {
+                for (int c = 0; c < row.Length; c++)
+                {
+                    columnWidths[c] = Math.Max(columnWidths[c], row[c].Length + columnSeparator.Length);
+                }
+            }
+
+            int tableWidth = columnWidths.Sum() + columnWidths.Length;
+            string horizontalSeparator = new string('=', tableWidth);
+
+            for (int r = 0; r < _pendingRows.Count; r++)
+            {
+                var row = _pendingRows[r];
+
+                // header
+                if (r == 0)
+                {
+                    _writer.WriteLine(horizontalSeparator);
+                    _writer.Write(new string(' ', rowNumberWidth + 2));
+                }
+                else
+                {
+                    string rowNumber = r.ToString("x");
+                    _writer.Write(new string(' ', rowNumberWidth - rowNumber.Length));
+                    _writer.Write(rowNumber);
+                    _writer.Write(": ");
+                }
+
+                for (int c = 0; c < row.Length; c++)
+                {
+                    var field = row[c];
+
+                    _writer.Write(field);
+                    _writer.Write(new string(' ', columnWidths[c] - field.Length));
+                }
+
+                _writer.WriteLine();
+
+                // header
+                if (r == 0)
+                {
+                    _writer.WriteLine(horizontalSeparator);
+                }
+            }
+
+            _writer.WriteLine();
+            _pendingRows.Clear();
+        }
+
+        private EntityHandle GetAggregateHandle(EntityHandle generationHandle, int generation)
+        {
+            var encMap = _encMaps[generation - 1];
+
+            int start, count;
+            if (!TryGetHandleRange(encMap, generationHandle.Kind, out start, out count))
+            {
+                throw new BadImageFormatException(string.Format("EncMap is missing record for {0:8X}.", MetadataTokens.GetToken(generationHandle)));
+            }
+
+            return encMap[start + MetadataTokens.GetRowNumber(generationHandle) - 1];
+        }
+
+        private static bool TryGetHandleRange(ImmutableArray<EntityHandle> handles, HandleKind handleType, out int start, out int count)
+        {
+            TableIndex tableIndex;
+            MetadataTokens.TryGetTableIndex(handleType, out tableIndex);
+
+            int mapIndex = handles.BinarySearch(MetadataTokens.Handle(tableIndex, 0), TokenTypeComparer.Instance);
+            if (mapIndex < 0)
+            {
+                start = 0;
+                count = 0;
+                return false;
+            }
+
+            int s = mapIndex;
+            while (s >= 0 && handles[s].Kind == handleType)
+            {
+                s--;
+            }
+
+            int e = mapIndex;
+            while (e < handles.Length && handles[e].Kind == handleType)
+            {
+                e++;
+            }
+
+            start = s + 1;
+            count = e - start;
+            return true;
+        }
+
+        private MethodDefinition GetMethod(MethodDefinitionHandle handle)
+        {
+            return Get(handle, (reader, h) => reader.GetMethodDefinition((MethodDefinitionHandle)h));
+        }
+
+        private BlobHandle GetLocalSignature(StandaloneSignatureHandle handle)
+        {
+            return Get(handle, (reader, h) => reader.GetStandaloneSignature((StandaloneSignatureHandle)h).Signature);
+        }
+
+        private TEntity Get<TEntity>(Handle handle, Func<MetadataReader, Handle, TEntity> getter)
+        {
+            if (_aggregator != null)
+            {
+                int generation;
+                var generationHandle = _aggregator.GetGenerationHandle(handle, out generation);
+                return getter(_readers[generation], generationHandle);
+            }
+            else
+            {
+                return getter(_reader, handle);
+            }
+        }
+
+        private string Literal(StringHandle handle)
+        {
+            return Literal(handle, BlobKind.None, (r, h) => "'" + StringUtilities.EscapeNonPrintableCharacters(r.GetString((StringHandle)h)) + "'");
+        }
+
+        private string Literal(NamespaceDefinitionHandle handle)
+        {
+            return Literal(handle, BlobKind.None, (r, h) => "'" + StringUtilities.EscapeNonPrintableCharacters(r.GetString((NamespaceDefinitionHandle)h)) + "'");
+        }
+
+        private string Literal(GuidHandle handle)
+        {
+            return Literal(handle, BlobKind.None, (r, h) => "{" + r.GetGuid((GuidHandle)h) + "}");
+        }
+
+        private static Guid CSharpGuid = new Guid("3f5162f8-07c6-11d3-9053-00c04fa302a1");
+        private static Guid VisualBasicGuid = new Guid("3a12d0b8-c26c-11d0-b442-00a0244a1dd2");
+        private static Guid FSharpGuid = new Guid("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3");
+        private static Guid Sha1Guid = new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460");
+        private static Guid Sha256Guid = new Guid("8829d00f-11b8-4213-878b-770e8597ac16");
+
+        private string GetLanguage(Guid guid)
+        {
+            if (guid == CSharpGuid) return "C#";
+            if (guid == VisualBasicGuid) return "Visual Basic";
+            if (guid == FSharpGuid) return "F#";
+
+            return "{" + guid + "}";
+        }
+
+        private string GetHashAlgorithm(Guid guid)
+        {
+            if (guid == Sha1Guid) return "SHA-1";
+            if (guid == Sha256Guid) return "SHA-256";
+
+            return "{" + guid + "}";
+        }
+
+        private string Language(GuidHandle handle)
+        {
+            return Literal(handle, BlobKind.None, (r, h) => GetLanguage(r.GetGuid((GuidHandle)h)));
+        }
+
+        private string HashAlgorithm(GuidHandle handle)
+        {
+            return Literal(handle, BlobKind.None, (r, h) => GetHashAlgorithm(r.GetGuid((GuidHandle)h)));
+        }
+
+/*
+        private string Literal(DocumentNameBlobHandle handle)
+        {
+            return Literal((BlobHandle)handle, BlobKind.DocumentName, (r, h) => "'" + r.GetString((DocumentNameBlobHandle)(BlobHandle)h) + "'");
+        }
+*/
+        private string LiteralUtf8Blob(BlobHandle handle, BlobKind kind)
+        {
+            return Literal(handle, kind, (r, h) =>
+            {
+                var bytes = r.GetBlobBytes((BlobHandle)h);
+                return "'" + Encoding.UTF8.GetString(bytes, 0, bytes.Length) + "'";
+            });
+        }
+
+        private string Literal(BlobHandle handle, BlobKind kind)
+        {
+            return Literal(handle, kind, (r, h) => BitConverter.ToString(r.GetBlobBytes((BlobHandle)h)));
+        }
+
+        private string Literal(Handle handle, BlobKind kind, Func<MetadataReader, Handle, string> getValue)
+        {
+            if (handle.IsNil)
+            {
+                return "nil";
+            }
+
+            if (kind != BlobKind.None)
+            {
+                _blobKinds[(BlobHandle)handle] = kind;
+            }
+
+            if (_aggregator != null)
+            {
+                int generation;
+                Handle generationHandle = _aggregator.GetGenerationHandle(handle, out generation);
+
+                var generationReader = _readers[generation];
+                string value = GetValueChecked(getValue, generationReader, generationHandle);
+                int offset = generationReader.GetHeapOffset(handle);
+                int generationOffset = generationReader.GetHeapOffset(generationHandle);
+
+                if (offset == generationOffset)
+                {
+                    return $"{value} (#{offset:x})";
+                }
+                else
+                {
+                    return $"{value} (#{offset:x}/{generationOffset:x})";
+                }
+            }
+
+            if (IsDelta)
+            {
+                // we can't resolve the literal without aggregate reader
+                return string.Format("#{0:x}", _reader.GetHeapOffset(handle));
+            }
+
+            // virtual heap handles don't have offset:
+            int heapOffset = MetadataTokens.GetHeapOffset(handle);
+            return $"{GetValueChecked(getValue, _reader, handle):x}" + (heapOffset >= 0 ? $" (#{heapOffset:x})" : "");
+        }
+
+        private string GetValueChecked(Func<MetadataReader, Handle, string> getValue, MetadataReader reader, Handle handle)
+        {
+            try
+            {
+                return getValue(reader, handle);
+            }
+            catch (BadImageFormatException)
+            {
+                return "<bad metadata>";
+            }
+        }
+
+        private string Hex(ushort value)
+        {
+            return "0x" + value.ToString("X4");
+        }
+
+        private string Hex(int value)
+        {
+            return "0x" + value.ToString("X8");
+        }
+
+        public string Token(Func<Handle> handleFunc, bool displayTable = true)
+        {
+            Handle handle;
+
+            try
+            {
+                handle = handleFunc();
+            }
+            catch (BadImageFormatException)
+            {
+                return "<bad metadata>";
+            }
+
+            if (handle.IsNil)
+            {
+                return "nil";
+            }
+
+            TableIndex table;
+            if (displayTable && MetadataTokens.TryGetTableIndex(handle.Kind, out table))
+            {
+                return string.Format("0x{0:x8} ({1})", _reader.GetToken(handle), table);
+            }
+            else
+            {
+                return string.Format("0x{0:x8}", _reader.GetToken(handle));
+            }
+        }
+
+        private static string EnumValue<T>(object value) where T : IEquatable<T>
+        {
+            T integralValue = (T)value;
+            if (integralValue.Equals(default(T)))
+            {
+                return "0";
+            }
+
+            return string.Format("0x{0:x8} ({1})", integralValue, value);
+        }
+
+        // TODO (tomat): handle collections should implement IReadOnlyCollection<Handle>
+        private string TokenRange<THandle>(IReadOnlyCollection<THandle> handles, Func<THandle, EntityHandle> conversion)
+        {
+            var genericHandles = handles.Select(conversion);
+
+            if (handles.Count < 0)
+            {
+                return "<bad token range>";
+            }
+
+            return (handles.Count == 0) ? "nil" : Token(() => genericHandles.First(), displayTable: false) + "-" + Token(() => genericHandles.Last(), displayTable: false);
+        }
+
+        public string TokenList(IReadOnlyCollection<EntityHandle> handles, bool displayTable = false)
+        {
+            if (handles.Count == 0)
+            {
+                return "nil";
+            }
+
+            return string.Join(", ", handles.Select(h => Token(() => h, displayTable)));
+        }
+
+        private string FormatAwaits(BlobHandle handle)
+        {
+            var sb = new StringBuilder();
+            var blobReader = _reader.GetBlobReader(handle);
+
+            while (blobReader.RemainingBytes > 0)
+            {
+                if (blobReader.Offset > 0)
+                {
+                    sb.Append(", ");
+                }
+
+                int value;
+                sb.Append("(");
+                sb.Append(blobReader.TryReadCompressedInteger(out value) ? value.ToString() : "?");
+                sb.Append(", ");
+                sb.Append(blobReader.TryReadCompressedInteger(out value) ? value.ToString() : "?");
+                sb.Append(", ");
+                sb.Append(blobReader.TryReadCompressedInteger(out value) ? Token(() => MetadataTokens.MethodDefinitionHandle(value)) : "?");
+                sb.Append(')');
+            }
+
+            return sb.ToString();
+        }
+
+        private string FormatImports(BlobHandle handle)
+        {
+            if (handle.IsNil)
+            {
+                return "nil";
+            }
+
+            var sb = new StringBuilder();
+
+            var importsReader = _reader.GetImportsReader(handle);
+            while (importsReader.MoveNext())
+            {
+                if (sb.Length > 0)
+                {
+                    sb.Append(", ");
+                }
+
+                var import = importsReader.Current;
+                switch (import.Kind)
+                {
+                    case ImportDefinitionKind.ImportNamespace:
+                        sb.AppendFormat("{0}", LiteralUtf8Blob(import.TargetNamespace, BlobKind.ImportNamespace));
+                        break;
+
+                    case ImportDefinitionKind.ImportAssemblyNamespace:
+                        sb.AppendFormat("{0}::{1}",
+                            Token(() => import.TargetAssembly),
+                            LiteralUtf8Blob(import.TargetNamespace, BlobKind.ImportNamespace));
+                        break;
+
+                    case ImportDefinitionKind.ImportType:
+                        sb.AppendFormat("{0}::{1}",
+                            Token(() => import.TargetAssembly),
+                            Token(() => import.TargetType));
+                        break;
+
+                    case ImportDefinitionKind.ImportXmlNamespace:
+                        sb.AppendFormat("<{0} = {1}>",
+                            LiteralUtf8Blob(import.Alias, BlobKind.ImportAlias),
+                            LiteralUtf8Blob(import.TargetNamespace, BlobKind.ImportNamespace));
+                        break;
+
+                    case ImportDefinitionKind.ImportAssemblyReferenceAlias:
+                        sb.AppendFormat("Extern Alias {0}",
+                            LiteralUtf8Blob(import.Alias, BlobKind.ImportAlias));
+                        break;
+
+                    case ImportDefinitionKind.AliasAssemblyReference:
+                        sb.AppendFormat("{0} = {1}",
+                            LiteralUtf8Blob(import.Alias, BlobKind.ImportAlias),
+                            Token(() => import.TargetAssembly));
+                        break;
+
+                    case ImportDefinitionKind.AliasNamespace:
+                        sb.AppendFormat("{0} = {1}",
+                            LiteralUtf8Blob(import.Alias, BlobKind.ImportAlias),
+                            LiteralUtf8Blob(import.TargetNamespace, BlobKind.ImportNamespace));
+                        break;
+
+                    case ImportDefinitionKind.AliasAssemblyNamespace:
+                        sb.AppendFormat("{0} = {1}::{2}",
+                            LiteralUtf8Blob(import.Alias, BlobKind.ImportAlias),
+                            Token(() => import.TargetAssembly),
+                            LiteralUtf8Blob(import.TargetNamespace, BlobKind.ImportNamespace));
+                        break;
+
+                    case ImportDefinitionKind.AliasType:
+                        sb.AppendFormat("{0} = {1}",
+                            LiteralUtf8Blob(import.Alias, BlobKind.ImportAlias),
+                            Token(() => import.TargetType));
+                        break;
+                }
+            }
+
+            return sb.ToString();
+        }
+
+/*
+        private string SequencePoint(SequencePoint sequencePoint)
+        {
+            string range = sequencePoint.IsHidden ? "<hidden>" : $"({sequencePoint.StartLine}, {sequencePoint.StartColumn}) - ({sequencePoint.EndLine}, {sequencePoint.EndColumn})";
+            return $"IL_{sequencePoint.Offset:X4}: " + range;
+        }
+*/
+        public void VisualizeHeaders()
+        {
+            _reader = _readers[0];
+
+            _writer.WriteLine("MetadataVersion: {0}", _reader.MetadataVersion);
+
+            if (_reader.DebugMetadataHeader != null)
+            {
+                if (!_reader.DebugMetadataHeader.EntryPoint.IsNil)
+                {
+                    _writer.WriteLine("EntryPoint: {0}", Token(() => _reader.DebugMetadataHeader.EntryPoint));
+                }
+            }
+
+            _writer.WriteLine();
+        }
+
+        private void WriteModule()
+        {
+            if (_reader.DebugMetadataHeader != null)
+            {
+                return;
+            }
+
+            var def = _reader.GetModuleDefinition();
+
+            AddHeader(
+                "Gen",
+                "Name",
+                "Mvid",
+                "EncId",
+                "EncBaseId"
+            );
+
+            AddRow(
+                def.Generation.ToString(),
+                Literal(def.Name),
+                Literal(def.Mvid),
+                Literal(def.GenerationId),
+                Literal(def.BaseGenerationId));
+
+            WriteRows("Module (0x00):");
+        }
+
+        private void WriteTypeRef()
+        {
+            AddHeader(
+                "Scope",
+                "Name",
+                "Namespace"
+            );
+
+            foreach (var handle in _reader.TypeReferences)
+            {
+                var entry = _reader.GetTypeReference(handle);
+
+                AddRow(
+                    Token(() => entry.ResolutionScope),
+                    Literal(entry.Name),
+                    Literal(entry.Namespace)
+                );
+            }
+
+            WriteRows("TypeRef (0x01):");
+        }
+
+        private void WriteTypeDef()
+        {
+            AddHeader(
+                "Name",
+                "Namespace",
+                "EnclosingType",
+                "BaseType",
+                "Interfaces",
+                "Fields",
+                "Methods",
+                "Attributes",
+                "ClassSize",
+                "PackingSize"
+            );
+
+            foreach (var handle in _reader.TypeDefinitions)
+            {
+                var entry = _reader.GetTypeDefinition(handle);
+
+                var layout = entry.GetLayout();
+
+                // TODO: Visualize InterfaceImplementations
+                var implementedInterfaces = entry.GetInterfaceImplementations().Select(h => _reader.GetInterfaceImplementation(h).Interface).ToArray();
+
+                AddRow(
+                    Literal(entry.Name),
+                    Literal(entry.Namespace),
+                    Token(() => entry.GetDeclaringType()),
+                    Token(() => entry.BaseType),
+                    TokenList(implementedInterfaces),
+                    TokenRange(entry.GetFields(), h => h),
+                    TokenRange(entry.GetMethods(), h => h),
+                    EnumValue<int>(entry.Attributes),
+                    !layout.IsDefault ? layout.Size.ToString() : "n/a",
+                    !layout.IsDefault ? layout.PackingSize.ToString() : "n/a"
+                );
+            }
+
+            WriteRows("TypeDef (0x02):");
+        }
+
+        private void WriteField()
+        {
+            AddHeader(
+                "Name",
+                "Signature",
+                "Attributes",
+                "Marshalling",
+                "Offset",
+                "RVA"
+            );
+
+            foreach (var handle in _reader.FieldDefinitions)
+            {
+                var entry = _reader.GetFieldDefinition(handle);
+
+                int offset = entry.GetOffset();
+
+                AddRow(
+                    Literal(entry.Name),
+                    Literal(entry.Signature, BlobKind.FieldSignature),
+                    EnumValue<int>(entry.Attributes),
+                    Literal(entry.GetMarshallingDescriptor(), BlobKind.Marshalling),
+                    offset >= 0 ? offset.ToString() : "n/a",
+                    entry.GetRelativeVirtualAddress().ToString()
+                );
+            }
+
+            WriteRows("Field (0x04):");
+        }
+
+        private void WriteMethod()
+        {
+            AddHeader(
+                "Name",
+                "Signature",
+                "RVA",
+                "Parameters",
+                "GenericParameters",
+                "ImplAttributes",
+                "Attributes",
+                "ImportAttributes",
+                "ImportName",
+                "ImportModule"
+            );
+
+            foreach (var handle in _reader.MethodDefinitions)
+            {
+                var entry = _reader.GetMethodDefinition(handle);
+                var import = entry.GetImport();
+
+                AddRow(
+                    Literal(entry.Name),
+                    Literal(entry.Signature, BlobKind.MethodSignature),
+                    Hex(entry.RelativeVirtualAddress),
+                    TokenRange(entry.GetParameters(), h => h),
+                    TokenRange(entry.GetGenericParameters(), h => h),
+                    EnumValue<int>(entry.Attributes),    // TODO: we need better visualizer than the default enum
+                    EnumValue<int>(entry.ImplAttributes),
+                    EnumValue<short>(import.Attributes),
+                    Literal(import.Name),
+                    Token(() => import.Module)
+                );
+            }
+
+            WriteRows("Method (0x06, 0x1C):");
+        }
+
+        private void WriteParam()
+        {
+            AddHeader(
+                "Name",
+                "Seq#",
+                "Attributes",
+                "Marshalling"
+            );
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.Param); i <= count; i++)
+            {
+                var entry = _reader.GetParameter(MetadataTokens.ParameterHandle(i));
+
+                AddRow(
+                    Literal(entry.Name),
+                    entry.SequenceNumber.ToString(),
+                    EnumValue<int>(entry.Attributes),
+                    Literal(entry.GetMarshallingDescriptor(), BlobKind.Marshalling)
+                );
+            }
+
+            WriteRows("Param (0x08):");
+        }
+
+        private void WriteMemberRef()
+        {
+            AddHeader(
+                "Parent",
+                "Name",
+                "Signature"
+            );
+
+            foreach (var handle in _reader.MemberReferences)
+            {
+                var entry = _reader.GetMemberReference(handle);
+
+                AddRow(
+                    Token(() => entry.Parent),
+                    Literal(entry.Name),
+                    Literal(entry.Signature, BlobKind.MemberRefSignature)
+                );
+            }
+
+            WriteRows("MemberRef (0x0a):");
+        }
+
+        private void WriteConstant()
+        {
+            AddHeader(
+                "Parent",
+                "Type",
+                "Value"
+            );
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.Constant); i <= count; i++)
+            {
+                var entry = _reader.GetConstant(MetadataTokens.ConstantHandle(i));
+
+                AddRow(
+                    Token(() => entry.Parent),
+                    EnumValue<byte>(entry.TypeCode),
+                    Literal(entry.Value, BlobKind.ConstantValue)
+                );
+            }
+
+            WriteRows("Constant (0x0b):");
+        }
+
+        private void WriteCustomAttribute()
+        {
+            AddHeader(
+                "Parent",
+                "Constructor",
+                "Value"
+            );
+
+            foreach (var handle in _reader.CustomAttributes)
+            {
+                var entry = _reader.GetCustomAttribute(handle);
+
+                AddRow(
+                    Token(() => entry.Parent),
+                    Token(() => entry.Constructor),
+                    Literal(entry.Value, BlobKind.CustomAttribute)
+                );
+            }
+
+            WriteRows("CustomAttribute (0x0c):");
+        }
+
+        private void WriteDeclSecurity()
+        {
+            AddHeader(
+                "Parent",
+                "PermissionSet",
+                "Action"
+            );
+
+            foreach (var handle in _reader.DeclarativeSecurityAttributes)
+            {
+                var entry = _reader.GetDeclarativeSecurityAttribute(handle);
+
+                AddRow(
+                    Token(() => entry.Parent),
+                    Literal(entry.PermissionSet, BlobKind.PermissionSet),
+                    EnumValue<short>(entry.Action)
+                );
+            }
+
+            WriteRows("DeclSecurity (0x0e):");
+        }
+
+        private void WriteStandAloneSig()
+        {
+            AddHeader("Signature");
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.StandAloneSig); i <= count; i++)
+            {
+                var value = _reader.GetStandaloneSignature(MetadataTokens.StandaloneSignatureHandle(i)).Signature;
+
+                AddRow(Literal(value, BlobKind.StandAloneSignature));
+            }
+
+            WriteRows("StandAloneSig (0x11):");
+        }
+
+        private void WriteEvent()
+        {
+            AddHeader(
+                "Name",
+                "Add",
+                "Remove",
+                "Fire",
+                "Attributes"
+            );
+
+            foreach (var handle in _reader.EventDefinitions)
+            {
+                var entry = _reader.GetEventDefinition(handle);
+                var accessors = entry.GetAccessors();
+
+                AddRow(
+                    Literal(entry.Name),
+                    Token(() => accessors.Adder),
+                    Token(() => accessors.Remover),
+                    Token(() => accessors.Raiser),
+                    EnumValue<int>(entry.Attributes)
+                );
+            }
+
+            WriteRows("Event (0x12, 0x14, 0x18):");
+        }
+
+        private void WriteProperty()
+        {
+            AddHeader(
+                "Name",
+                "Get",
+                "Set",
+                "Attributes"
+            );
+
+            foreach (var handle in _reader.PropertyDefinitions)
+            {
+                var entry = _reader.GetPropertyDefinition(handle);
+                var accessors = entry.GetAccessors();
+
+                AddRow(
+                    Literal(entry.Name),
+                    Token(() => accessors.Getter),
+                    Token(() => accessors.Setter),
+                    EnumValue<int>(entry.Attributes)
+                );
+            }
+
+            WriteRows("Property (0x15, 0x17, 0x18):");
+        }
+
+        private void WriteMethodImpl()
+        {
+            AddHeader(
+                "Type",
+                "Body",
+                "Declaration"
+            );
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.MethodImpl); i <= count; i++)
+            {
+                var entry = _reader.GetMethodImplementation(MetadataTokens.MethodImplementationHandle(i));
+
+                AddRow(
+                    Token(() => entry.Type),
+                    Token(() => entry.MethodBody),
+                    Token(() => entry.MethodDeclaration)
+                );
+            }
+
+            WriteRows("MethodImpl (0x19):");
+        }
+
+        private void WriteModuleRef()
+        {
+            AddHeader("Name");
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.ModuleRef); i <= count; i++)
+            {
+                var value = _reader.GetModuleReference(MetadataTokens.ModuleReferenceHandle(i)).Name;
+                AddRow(Literal(value));
+            }
+
+            WriteRows("ModuleRef (0x1a):");
+        }
+
+        private void WriteTypeSpec()
+        {
+            AddHeader("Name");
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.TypeSpec); i <= count; i++)
+            {
+                var value = _reader.GetTypeSpecification(MetadataTokens.TypeSpecificationHandle(i)).Signature;
+                AddRow(Literal(value, BlobKind.TypeSpec));
+            }
+
+            WriteRows("TypeSpec (0x1b):");
+        }
+
+        private void WriteEnCLog()
+        {
+            AddHeader(
+                "Entity",
+                "Operation");
+
+            foreach (var entry in _reader.GetEditAndContinueLogEntries())
+            {
+                AddRow(
+                    Token(() => entry.Handle),
+                    EnumValue<int>(entry.Operation));
+            }
+
+            WriteRows("EnC Log (0x1e):");
+        }
+
+        private void WriteEnCMap()
+        {
+            if (_aggregator != null)
+            {
+                AddHeader("Entity", "Gen", "Row", "Edit");
+            }
+            else
+            {
+                AddHeader("Entity");
+            }
+
+
+            foreach (var entry in _reader.GetEditAndContinueMapEntries())
+            {
+                if (_aggregator != null)
+                {
+                    int generation;
+                    EntityHandle primary = (EntityHandle)_aggregator.GetGenerationHandle(entry, out generation);
+                    bool isUpdate = _readers[generation] != _reader;
+
+                    var primaryModule = _readers[generation].GetModuleDefinition();
+
+                    AddRow(
+                        Token(() => entry),
+                        primaryModule.Generation.ToString(),
+                        "0x" + MetadataTokens.GetRowNumber(primary).ToString("x6"),
+                        isUpdate ? "update" : "add");
+                }
+                else
+                {
+                    AddRow(Token(() => entry));
+                }
+            }
+
+            WriteRows("EnC Map (0x1f):");
+        }
+
+        private void WriteAssembly()
+        {
+            if (!_reader.IsAssembly)
+            {
+                return;
+            }
+
+            AddHeader(
+                "Name",
+                "Version",
+                "Culture",
+                "PublicKey",
+                "Flags",
+                "HashAlgorithm"
+            );
+
+            var entry = _reader.GetAssemblyDefinition();
+
+            AddRow(
+                Literal(entry.Name),
+                entry.Version.Major + "." + entry.Version.Minor + "." + entry.Version.Revision + "." + entry.Version.Build,
+                Literal(entry.Culture),
+                Literal(entry.PublicKey, BlobKind.Key),
+                EnumValue<int>(entry.Flags),
+                EnumValue<int>(entry.HashAlgorithm)
+            );
+
+            WriteRows("Assembly (0x20):");
+        }
+
+        private void WriteAssemblyRef()
+        {
+            AddHeader(
+                "Name",
+                "Version",
+                "Culture",
+                "PublicKeyOrToken",
+                "Flags"
+            );
+
+            foreach (var handle in _reader.AssemblyReferences)
+            {
+                var entry = _reader.GetAssemblyReference(handle);
+
+                AddRow(
+                    Literal(entry.Name),
+                    entry.Version.Major + "." + entry.Version.Minor + "." + entry.Version.Revision + "." + entry.Version.Build,
+                    Literal(entry.Culture),
+                    Literal(entry.PublicKeyOrToken, BlobKind.Key),
+                    EnumValue<int>(entry.Flags)
+                );
+            }
+
+            WriteRows("AssemblyRef (0x23):");
+        }
+
+        private void WriteFile()
+        {
+            AddHeader(
+                "Name",
+                "Metadata",
+                "HashValue"
+            );
+
+            foreach (var handle in _reader.AssemblyFiles)
+            {
+                var entry = _reader.GetAssemblyFile(handle);
+
+                AddRow(
+                    Literal(entry.Name),
+                    entry.ContainsMetadata ? "Yes" : "No",
+                    Literal(entry.HashValue, BlobKind.FileHash)
+                );
+            }
+
+            WriteRows("File (0x26):");
+        }
+        private void WriteExportedType()
+        {
+            AddHeader(
+                "Name",
+                "Namespace",
+                "Attributes",
+                "Implementation",
+                "TypeDefinitionId"
+            );
+
+            foreach (var handle in _reader.ExportedTypes)
+            {
+                var entry = _reader.GetExportedType(handle);
+                AddRow(
+                    Literal(entry.Name),
+                    Literal(entry.Namespace),
+                    entry.Attributes.ToString(),
+                    Token(() => entry.Implementation),
+                    Hex(entry.GetTypeDefinitionId())
+                );
+            }
+
+            WriteRows("ExportedType (0x27):");
+        }
+
+        private void WriteManifestResource()
+        {
+            AddHeader(
+                "Name",
+                "Attributes",
+                "Offset",
+                "Implementation"
+            );
+
+            foreach (var handle in _reader.ManifestResources)
+            {
+                var entry = _reader.GetManifestResource(handle);
+
+                AddRow(
+                    Literal(entry.Name),
+                    entry.Attributes.ToString(),
+                    entry.Offset.ToString(),
+                    Token(() => entry.Implementation)
+                );
+            }
+
+            WriteRows("ManifestResource (0x28):");
+        }
+
+        private void WriteGenericParam()
+        {
+            AddHeader(
+                "Name",
+                "Seq#",
+                "Attributes",
+                "Parent",
+                "TypeConstraints"
+            );
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.GenericParam); i <= count; i++)
+            {
+                var entry = _reader.GetGenericParameter(MetadataTokens.GenericParameterHandle(i));
+
+                AddRow(
+                    Literal(entry.Name),
+                    entry.Index.ToString(),
+                    EnumValue<int>(entry.Attributes),
+                    Token(() => entry.Parent),
+                    TokenRange(entry.GetConstraints(), h => h)
+                );
+            }
+
+            WriteRows("GenericParam (0x2a):");
+        }
+
+        private void WriteMethodSpec()
+        {
+            AddHeader(
+                "Method",
+                "Signature"
+            );
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.MethodSpec); i <= count; i++)
+            {
+                var entry = _reader.GetMethodSpecification(MetadataTokens.MethodSpecificationHandle(i));
+
+                AddRow(
+                    Token(() => entry.Method),
+                    Literal(entry.Signature, BlobKind.MethodSpec)
+                );
+            }
+
+            WriteRows("MethodSpec (0x2b):");
+        }
+
+        private void WriteGenericParamConstraint()
+        {
+            AddHeader(
+                "Parent",
+                "Type"
+            );
+
+            for (int i = 1, count = _reader.GetTableRowCount(TableIndex.GenericParamConstraint); i <= count; i++)
+            {
+                var entry = _reader.GetGenericParameterConstraint(MetadataTokens.GenericParameterConstraintHandle(i));
+
+                AddRow(
+                    Token(() => entry.Parameter),
+                    Token(() => entry.Type)
+                );
+            }
+
+            WriteRows("GenericParamConstraint (0x2c):");
+        }
+
+        private void WriteUserStrings()
+        {
+            int size = _reader.GetHeapSize(HeapIndex.UserString);
+            if (size == 0)
+            {
+                return;
+            }
+
+            // TODO: the heap is aligned, don't display the trailing empty strings
+            _writer.WriteLine($"#US (size = {size}):");
+            var handle = MetadataTokens.UserStringHandle(0);
+            do
+            {
+                string value = StringUtilities.EscapeNonPrintableCharacters(_reader.GetUserString(handle));
+                _writer.WriteLine($"  {_reader.GetHeapOffset(handle):x}: '{value}'");
+                handle = _reader.GetNextHandle(handle);
+            }
+            while (!handle.IsNil);
+
+            _writer.WriteLine();
+        }
+
+        private void WriteStrings()
+        {
+            int size = _reader.GetHeapSize(HeapIndex.String);
+            if (size == 0)
+            {
+                return;
+            }
+
+            _writer.WriteLine($"#String (size = {size}):");
+            var handle = MetadataTokens.StringHandle(0);
+            do
+            {
+                string value = StringUtilities.EscapeNonPrintableCharacters(_reader.GetString(handle));
+                _writer.WriteLine($"  {_reader.GetHeapOffset(handle):x}: '{value}'");
+                handle = _reader.GetNextHandle(handle);
+            }
+            while (!handle.IsNil);
+
+            _writer.WriteLine();
+        }
+
+        private void WriteBlobs()
+        {
+            int size = _reader.GetHeapSize(HeapIndex.Blob);
+            if (size == 0)
+            {
+                return;
+            }
+
+            int[] sizePerKind = new int[(int)BlobKind.Count];
+
+            _writer.WriteLine($"#Blob (size = {size}):");
+            var handle = MetadataTokens.BlobHandle(0);
+            do
+            {
+                byte[] value = _reader.GetBlobBytes(handle);
+
+                BlobKind kind;
+                string kindString;
+                if (_blobKinds.TryGetValue(handle, out kind))
+                {
+                    kindString = " (" + kind + ")";
+
+                    // ignoring the compressed blob size:
+                    sizePerKind[(int)kind] += value.Length;
+                }
+                else
+                {
+                    kindString = "";
+                }
+
+                int displayLength = (_options & MetadataVisualizerOptions.ShortenBlobs) != 0 ? Math.Min(4, value.Length) : value.Length;
+                string valueString = BitConverter.ToString(value, 0, displayLength) + (displayLength < value.Length ? "-..." : null);
+
+                _writer.WriteLine($"  {_reader.GetHeapOffset(handle):x}{kindString}: {valueString}");
+                handle = _reader.GetNextHandle(handle);
+            }
+            while (!handle.IsNil);
+
+            _writer.WriteLine();
+            _writer.WriteLine("Sizes:");
+
+            for (int i = 0; i < sizePerKind.Length; i++)
+            {
+                if (sizePerKind[i] > 0)
+                {
+                    _writer.WriteLine($"  {(BlobKind)i}: {(decimal)sizePerKind[i]} bytes");
+                }
+            }
+
+            // don't calculate statistics for EnC delta, it's not interesting
+            if (_aggregator == null)
+            {
+                _writer.WriteLine();
+                _writer.WriteLine("CustomAttribute sizes by constructor:");
+                try
+                {
+                    foreach (var grouping in from caHandle in _reader.CustomAttributes
+                                             let ca = _reader.GetCustomAttribute(caHandle)
+                                             group ca.Constructor by ca.Value into values   // blob -> { ctor1, ctor2, ... }
+                                             group values.Key by values.First() into g      // ctor1 -> { blob1, ... }
+                                             select new { Ctor = g.Key, Size = g.Sum(ca => _reader.GetBlobReader(ca).Length) } into ctorAndSize
+                                             orderby ctorAndSize.Size descending
+                                             select ctorAndSize)
+                    {
+                        string typeStr = null;
+                        switch (grouping.Ctor.Kind)
+                        {
+                            case HandleKind.MemberReference:
+                                var memberRef = _reader.GetMemberReference((MemberReferenceHandle)grouping.Ctor);
+
+                                switch (memberRef.Parent.Kind)
+                                {
+                                    case HandleKind.TypeReference:
+                                        var typeRef = _reader.GetTypeReference((TypeReferenceHandle)memberRef.Parent);
+                                        typeStr = typeRef.Namespace.IsNil ? _reader.GetString(typeRef.Name) : _reader.GetString(typeRef.Namespace) + "." + _reader.GetString(typeRef.Name);
+                                        break;
+
+                                    case HandleKind.TypeDefinition:
+                                        var typeDef = _reader.GetTypeDefinition((TypeDefinitionHandle)memberRef.Parent);
+                                        typeStr = typeDef.Namespace.IsNil ? _reader.GetString(typeDef.Name) : _reader.GetString(typeDef.Namespace) + "." + _reader.GetString(typeDef.Name);
+                                        break;
+
+                                    case HandleKind.MethodDefinition:
+                                    case HandleKind.ModuleReference:
+                                    case HandleKind.TypeSpecification:
+                                        break;
+                                }
+
+                                break;
+
+                            case HandleKind.MethodDefinition:
+                                // TODO
+                                break;
+                        }
+
+
+                        // grouping.Key
+                        _writer.WriteLine($"  {typeStr ?? Token(() => grouping.Ctor)}: {grouping.Size} bytes");
+                    }
+                }
+                catch (BadImageFormatException)
+                {
+                    _writer.WriteLine("<bad metadata>");
+                }
+
+                _writer.WriteLine();
+            }
+        }
+
+        private void WriteGuids()
+        {
+            int size = _reader.GetHeapSize(HeapIndex.Guid);
+            if (size == 0)
+            {
+                return;
+            }
+
+            _writer.WriteLine(string.Format("#Guid (size = {0}):", size));
+            int i = 1;
+            while (i <= size / 16)
+            {
+                string value = _reader.GetGuid(MetadataTokens.GuidHandle(i)).ToString();
+                _writer.WriteLine("  {0:x}: {{{1}}}", i, value);
+                i++;
+            }
+
+            _writer.WriteLine();
+        }
+
+        private void WriteDocument()
+        {
+            AddHeader(
+                "Name",
+                "Language",
+                "HashAlgorithm",
+                "Hash"
+            );
+
+            foreach (var handle in _reader.Documents)
+            {
+                var entry = _reader.GetDocument(handle);
+
+                AddRow(
+                    Literal(entry.Name),
+                    Language(entry.Language),
+                    HashAlgorithm(entry.HashAlgorithm),
+                    Literal(entry.Hash, BlobKind.DocumentHash)
+               );
+            }
+
+            WriteTableName(TableIndex.Document);
+        }
+
+        private void WriteMethodBody()
+        {
+            if (_reader.MethodBodies.Count == 0)
+            {
+                return;
+            }
+
+            _writer.WriteLine(MakeTableName(TableIndex.MethodBody));
+            _writer.WriteLine(new string('=', 50));
+
+            foreach (var handle in _reader.MethodBodies)
+            {
+                if (handle.IsNil)
+                {
+                    continue;
+                }
+
+                var entry = _reader.GetMethodBody(handle);
+
+                _writer.WriteLine($"{MetadataTokens.GetRowNumber(handle)}: #{_reader.GetHeapOffset(entry.SequencePoints)}");
+
+                if (entry.SequencePoints.IsNil)
+                {
+                    continue;
+                }
+
+                _blobKinds[entry.SequencePoints] = BlobKind.SequencePoints;
+
+                _writer.WriteLine("{");
+
+                bool addLineBreak = false;
+
+                var kickoffMethod = entry.GetStateMachineKickoffMethod();
+                if (!kickoffMethod.IsNil)
+                {
+                    _writer.WriteLine($"  Kickoff Method: {Token(() => kickoffMethod)}");
+                    addLineBreak = true;
+                }
+
+                if (!entry.LocalSignature.IsNil)
+                {
+                    _writer.WriteLine($"  Locals: {Token(() => entry.LocalSignature)}");
+                    addLineBreak = true;
+                }
+
+                if (addLineBreak)
+                {
+                    _writer.WriteLine();
+                }
+
+                try
+                {
+                    var spReader = _reader.GetSequencePointsReader(entry.SequencePoints);
+                    while (spReader.MoveNext())
+                    {
+                        _writer.Write("  ");
+                        _writer.WriteLine(SequencePoint(spReader.Current));
+                    }
+                }
+                catch (BadImageFormatException)
+                {
+                    _writer.WriteLine("<bad metadata>");
+                }
+
+                _writer.WriteLine("}");
+            }
+
+            _writer.WriteLine();
+        }
+
+        private void WriteLocalScope()
+        {
+            AddHeader(
+                "Method",
+                "ImportScope",
+                "Variables",
+                "Constants",
+                "StartOffset",
+                "Length"
+            );
+
+            foreach (var handle in _reader.LocalScopes)
+            {
+                var entry = _reader.GetLocalScope(handle);
+
+                AddRow(
+                    Token(() => entry.Method),
+                    Token(() => entry.ImportScope),
+                    TokenRange(entry.GetLocalVariables(), h => h),
+                    TokenRange(entry.GetLocalConstants(), h => h),
+                    entry.StartOffset.ToString("X4"),
+                    entry.Length.ToString()
+               );
+            }
+
+            WriteTableName(TableIndex.LocalScope);
+        }
+
+        private void WriteLocalVariable()
+        {
+            AddHeader(
+                "Name",
+                "Index",
+                "Attributes"
+            );
+
+            foreach (var handle in _reader.LocalVariables)
+            {
+                var entry = _reader.GetLocalVariable(handle);
+
+                AddRow(
+                    Literal(entry.Name),
+                    entry.Index.ToString(),
+                    entry.Attributes.ToString()
+               );
+            }
+
+            WriteTableName(TableIndex.LocalVariable);
+        }
+
+        private void WriteLocalConstant()
+        {
+            AddHeader(
+                "Name",
+                "Signature"
+            );
+
+            foreach (var handle in _reader.LocalConstants)
+            {
+                var entry = _reader.GetLocalConstant(handle);
+
+                AddRow(
+                    Literal(entry.Name),
+                    Literal(entry.Signature, BlobKind.LocalConstantSignature, (r, h) => FormatLocalConstant(r, (BlobHandle)h))
+               );
+            }
+
+            WriteTableName(TableIndex.LocalConstant);
+        }
+
+/*
+        private SignatureTypeCode ReadConstantTypeCode(ref BlobReader sigReader, List<CustomModifier<Handle>> modifiers)
+        {
+            while (true)
+            {
+                var s = sigReader.ReadSignatureTypeCode();
+                if (s == SignatureTypeCode.OptionalModifier || s == SignatureTypeCode.RequiredModifier)
+                {
+                    var type = sigReader.ReadTypeHandle();
+                    modifiers.Add(new CustomModifier<Handle>(type, isRequired: s == SignatureTypeCode.RequiredModifier));
+                }
+                else
+                {
+                    return s;
+                }
+            }
+        }
+
+        private string FormatLocalConstant(MetadataReader reader, BlobHandle signature)
+        {
+            var sigReader = reader.GetBlobReader(signature);
+
+            var modifiers = new List<CustomModifier<Handle>>();
+
+            SignatureTypeCode typeCode = ReadConstantTypeCode(ref sigReader, modifiers);
+
+            Handle typeHandle = default(Handle);
+            object value;
+            if (IsPrimitiveType(typeCode))
+            {
+                if (typeCode == SignatureTypeCode.String)
+                {
+                    if (sigReader.RemainingBytes == 1)
+                    {
+                        value = (sigReader.ReadByte() == 0xff) ? "null" : "<bad metadata>";
+                    }
+                    else if (sigReader.RemainingBytes % 2 != 0)
+                    {
+                        value = "<bad metadata>";
+                    }
+                    else
+                    {
+                        value = "'" + sigReader.ReadUTF16(sigReader.RemainingBytes) + "'";
+                    }
+                }
+                else
+                {
+                    value = string.Format(CultureInfo.InvariantCulture, "{0}", sigReader.ReadConstant((ConstantTypeCode)typeCode));
+                }
+
+                if (sigReader.RemainingBytes > 0)
+                {
+                    typeHandle = sigReader.ReadTypeHandle();
+                }
+            }
+            else if (typeCode == SignatureTypeCode.TypeHandle)
+            {
+                typeHandle = sigReader.ReadTypeHandle();
+                value = (sigReader.RemainingBytes > 0) ? BitConverter.ToString(sigReader.ReadBytes(sigReader.RemainingBytes)) : "default";
+            }
+            else
+            {
+                value = (typeCode == SignatureTypeCode.Object) ? "null" : $"<bad type code: {typeCode}>";
+            }
+
+            return string.Format("{0} [{1}{2}]",
+                value,
+                FormatCustomModifiers(modifiers),
+                typeHandle.IsNil ? typeCode.ToString() : Token(() => typeHandle));
+        }
+
+        private string FormatCustomModifiers(IEnumerable<CustomModifier<Handle>> modifiers)
+        {
+            return string.Join(" ", modifiers.Select(m => (m.IsRequired ? "modreq" : "modopt") + "(" + Token(() => m.Type) + ")"));
+        }
+*/
+        private static bool IsPrimitiveType(SignatureTypeCode typeCode)
+        {
+            switch (typeCode)
+            {
+                case SignatureTypeCode.Boolean:
+                case SignatureTypeCode.Char:
+                case SignatureTypeCode.SByte:
+                case SignatureTypeCode.Byte:
+                case SignatureTypeCode.Int16:
+                case SignatureTypeCode.UInt16:
+                case SignatureTypeCode.Int32:
+                case SignatureTypeCode.UInt32:
+                case SignatureTypeCode.Int64:
+                case SignatureTypeCode.UInt64:
+                case SignatureTypeCode.Single:
+                case SignatureTypeCode.Double:
+                case SignatureTypeCode.String:
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+
+        private void WriteLocalImport()
+        {
+            AddHeader(
+                "Parent",
+                "Imports"
+            );
+
+            foreach (var handle in _reader.ImportScopes)
+            {
+                var entry = _reader.GetImportScope(handle);
+
+                _blobKinds[entry.Imports] = BlobKind.Imports;
+
+                AddRow(
+                    Token(() => entry.Parent),
+                    FormatImports(entry.Imports)
+               );
+            }
+
+            WriteTableName(TableIndex.ImportScope);
+        }
+
+        private void WriteCustomDebugInformation()
+        {
+            AddHeader(
+                "Parent",
+                "Value"
+            );
+
+            foreach (var handle in _reader.CustomDebugInformation)
+            {
+                var entry = _reader.GetCustomDebugInformation(handle);
+
+                AddRow(
+                    Token(() => entry.Parent),
+                    Literal(entry.Value, BlobKind.CustomDebugInformation)
+               );
+            }
+
+            WriteTableName(TableIndex.CustomDebugInformation);
+        }
+
+        public void VisualizeMethodBody(MethodBodyBlock body, MethodDefinitionHandle generationHandle, int generation)
+        {
+            VisualizeMethodBody(body, (MethodDefinitionHandle)GetAggregateHandle(generationHandle, generation));
+        }
+
+        public void VisualizeMethodBody(MethodBodyBlock body, MethodDefinitionHandle methodHandle, bool emitHeader = true)
+        {
+            StringBuilder builder = new StringBuilder();
+
+            // TODO: Inspect EncLog to find a containing type and display qualified name.
+            var method = GetMethod(methodHandle);
+            if (emitHeader)
+            {
+                builder.AppendFormat("Method {0} (0x{1:X8})", Literal(method.Name), MetadataTokens.GetToken(methodHandle));
+                builder.AppendLine();
+            }
+
+            // TODO: decode signature
+            if (!body.LocalSignature.IsNil)
+            {
+                var localSignature = GetLocalSignature(body.LocalSignature);
+                builder.AppendFormat("  Locals: {0}", Literal(localSignature, BlobKind.StandAloneSignature));
+                builder.AppendLine();
+            }
+
+            ILVisualizerAsTokens.Instance.DumpMethod(
+                builder,
+                body.MaxStack,
+                body.GetILContent(),
+                ImmutableArray.Create<ILVisualizer.LocalInfo>(),     // TODO
+                ImmutableArray.Create<ILVisualizer.HandlerSpan>());  // TOOD: ILVisualizer.GetHandlerSpans(body.ExceptionRegions)
+
+            builder.AppendLine();
+
+            _writer.Write(builder.ToString());
+        }
+
+        public void WriteLine(string line)
+        {
+            _writer.WriteLine(line);
+        }
+
+        private sealed class TokenTypeComparer : IComparer<EntityHandle>
+        {
+            public static readonly TokenTypeComparer Instance = new TokenTypeComparer();
+
+            public int Compare(EntityHandle x, EntityHandle y)
+            {
+                return x.Kind.CompareTo(y.Kind);
+            }
+        }
+    }
+}
+
+#endif

+ 57 - 0
Build/AtomicNETTest/AtomicEditor/MethodILExtensions.cs

@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Text;
+
+namespace AtomicEditor
+{
+    public static class MethodILExtensions
+    {
+        public static unsafe string GetMethodIL(this ImmutableArray<byte> ilArray)
+        {
+            var result = new StringBuilder();
+            fixed (byte* ilPtr = ilArray.ToArray())
+            {
+                int offset = 0;
+                while (true)
+                {
+                    // skip padding:
+                    while (offset < ilArray.Length && ilArray[offset] == 0)
+                    {
+                        offset++;
+                    }
+
+                    if (offset == ilArray.Length)
+                    {
+                        break;
+                    }
+
+                    var reader = new BlobReader(ilPtr + offset, ilArray.Length - offset);
+                    var methodIL = MethodBodyBlock.Create(reader);
+
+                    if (methodIL == null)
+                    {
+                        result.AppendFormat("<invalid byte 0x{0:X2} at offset {1}>", ilArray[offset], offset);
+                        offset++;
+                    }
+                    else
+                    {
+                        ILVisualizerAsTokens.Instance.DumpMethod(
+                            result,
+                            methodIL.MaxStack,
+                            methodIL.GetILContent(),
+                            ImmutableArray.Create<ILVisualizer.LocalInfo>(),
+                            ImmutableArray.Create<ILVisualizer.HandlerSpan>());
+
+                        offset += methodIL.Size;
+                    }
+                }
+            }
+
+            return result.ToString();
+        }
+    }
+}

+ 27 - 0
Build/AtomicNETTest/AtomicEditor/Properties/AssemblyInfo.cs

@@ -0,0 +1,27 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle ("AtomicEditor")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("josh")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion ("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+

+ 18 - 0
Build/AtomicNETTest/MyClass.cs

@@ -0,0 +1,18 @@
+
+using AtomicEngine;
+using System;
+
+namespace AtomicNETTest
+{
+
+    public class MyComponent : CSComponent
+    {
+        public bool MyBoolValue = true;
+
+        [Inspector]
+        public float MyFloatValue = 42.0f;
+
+        public string MyStringValue = "Hey!";
+    }
+
+}

+ 34 - 9
Source/AtomicNET/NETCore/NETCore.cpp

@@ -315,25 +315,23 @@ bool NETCore::Initialize(const String &coreCLRFilesAbsPath, String& errorMsg)
         return false;
     }
 
-    // Reflection only load, may be useful when bringing into editor for component inspector values
-    // http://shazwazza.com/post/how-to-inspect-assemblies-before-including-them-in-your-application-with-reflection/
-
-    // It’s best to execute all of this logic in a separate AppDomain because once assemblies are loaded
-    // in to a context, they cannot be unloaded and since we are loading in from files, those files will
-    // remain locked until the AppDomain is shutdown.
-
     typedef void (*StartupFunction)();
     StartupFunction startup;
 
     // The coreclr binding model will become locked upon loading the first assembly that is not on the TPA list, or
     // upon initializing the default context for the first time. For this test, test assemblies are located alongside
     // corerun, and hence will be on the TPA list. So, we should be able to set the default context once successfully,
-    // and fail on the second try.
+    // and fail on the second try.    
+
+    // AssemblyLoadContext
+    // https://github.com/dotnet/corefx/issues/3054
+    // dnx loader
+    // https://github.com/aspnet/dnx/tree/dev/src/Microsoft.Dnx.Loader
 
     st = sCreateDelegate(hostHandle_,
                     domainId_,
                     "AtomicNETBootstrap",
-                    "AtomicLoadContext",
+                    "Atomic.Bootstrap.AtomicLoadContext",
                     "Startup",
                     (void**) &startup);
 
@@ -382,6 +380,33 @@ bool NETCore::Initialize(const String &coreCLRFilesAbsPath, String& errorMsg)
         GetSubsystem<NETManaged>()->SetNETUpdate(updateFunction);
     }
 
+    typedef void (*InpectAssemblyFuctionPtr)(const char* path);
+
+    InpectAssemblyFuctionPtr inspectAssembly;
+
+    //https://github.com/Microsoft/dotnetsamples/tree/master/System.Reflection.Metadata
+    //https://github.com/dotnet/corefx/tree/master/src/System.Reflection.Metadata/tests
+    //http://www.cnetion.com/getting-field-values-using-mono-cecil-qq-AUvBjRFgivICeoL1jxJy.php
+
+    // https://github.com/Reactive-Extensions/IL2JS/blob/master/CCI2/PeReader/ILReader.cs
+
+    // https://github.com/Reactive-Extensions/IL2JS
+
+    // custom attr loading: https://github.com/Reactive-Extensions/IL2JS/blob/a4570f9c69b6c40d001e7539b952266d67609ca9/CST/PELoader.cs#L2352
+    // custom attr: https://www.simple-talk.com/blogs/2011/06/03/anatomy-of-a-net-assembly-custom-attribute-encoding/
+    // custom attr: https://github.com/jbevain/cecil/blob/67a2569688a13a6cb487f9af5c3418f7a8f43e3c/Mono.Cecil/AssemblyReader.cs
+    st = sCreateDelegate(hostHandle_,
+                    domainId_,
+                    "AtomicEditor",
+                    "AtomicEditor.AssemblyInspector",
+                    "InspectAssembly",
+                    (void**) &inspectAssembly);
+
+    if (st >= 0)
+    {
+        inspectAssembly("/Users/josh/Desktop/AtomicNETTest.dll");
+    }
+
 
     /*
     unsigned int exitCode;