using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.IO; using System.Globalization; using System.Xml; using System.Diagnostics; using Indy.IL2CPU.IL; namespace Indy.IL2CPU.Compiler { public class CompilerHelper { public class CacheState: IComparable, IEquatable { public List UsedGenericMethods = new List(); public List UsedGenericTypes = new List(); internal void Load(string aFile) { if (!File.Exists(aFile)) { return; } var xDoc = new XmlDocument(); xDoc.Load(aFile); var xElemMethods = xDoc.SelectSingleNode("/CacheState/GenericMethods"); foreach (XmlNode xMethodNode in xElemMethods.ChildNodes) { UsedGenericMethods.Add(xMethodNode.InnerText); } var xElemTypes = xDoc.SelectSingleNode("/CacheState/GenericTypes"); foreach (XmlNode xTypeNode in xElemTypes.ChildNodes) { UsedGenericTypes.Add(xTypeNode.InnerText); } UsedGenericTypes.Sort(); UsedGenericMethods.Sort(); } internal void Save(string aFile) { using (var xOut = XmlWriter.Create(aFile)) { xOut.WriteStartDocument(); xOut.WriteStartElement("CacheState"); { xOut.WriteStartElement("GenericMethods"); { foreach (var xMethod in UsedGenericMethods) { xOut.WriteStartElement("Method"); { xOut.WriteCData(xMethod); } xOut.WriteEndElement(); } } xOut.WriteEndElement(); xOut.WriteStartElement("GenericTypes"); { foreach (var xType in UsedGenericTypes) { xOut.WriteStartElement("Type"); { xOut.WriteCData(xType); } xOut.WriteEndElement(); } } xOut.WriteEndElement(); } xOut.WriteEndElement(); xOut.WriteEndDocument(); } } #region IComparable Members public int CompareTo(CacheState other) { var xResult = this.UsedGenericMethods.Count.CompareTo(other.UsedGenericMethods.Count); if (xResult != 0) { return xResult; } xResult = this.UsedGenericTypes.Count.CompareTo(other.UsedGenericTypes.Count); if (xResult != 0) { return xResult; } for (int i = 0; i < UsedGenericMethods.Count; i++) { xResult = UsedGenericMethods[i].CompareTo(other.UsedGenericMethods[i]); if (xResult != 0) { return xResult; } } for (int i = 0; i < UsedGenericTypes.Count; i++) { xResult = UsedGenericTypes[i].CompareTo(other.UsedGenericTypes[i]); if (xResult != 0) { return xResult; } } return 0; } #endregion #region IEquatable Members public bool Equals(CacheState other) { return CompareTo(other) == 0; } #endregion } public List SkipList { get; set; } public event Func GetCacheStateFile; public event Func GetChecksumFile; public event Func GetAssembler; public event Action SaveAssembler; public event Func GetOpCodeMap; public event Action DebugLog; public List Plugs = new List(); public CompilerHelper() { SkipList = new List(); } private Assembly mEntryAssembly; private IEnumerable mAllAssemblies; public void CompileExe(Assembly aAssembly) { if (aAssembly == null) { throw new ArgumentNullException("aAssembly"); } mEntryAssembly=aAssembly; mAllAssemblies = Scanner.GetAllAssemblies(aAssembly, SkipList); var xAssembliesToScanForGenerics = GetAssembliesToScanForGenerics(mAllAssemblies); LoadCacheStates(mAllAssemblies); bool xNeedsFullRecompile = false; // now scan for generics usage foreach (var xAsm in xAssembliesToScanForGenerics) { var xTest = this.ScanAssembly(xAsm); if(!xTest.Equals(mCacheStates[xAsm])){ xNeedsFullRecompile =true; } } if (xNeedsFullRecompile) { DoFullRecompile(); } else { SmartRecompile(); } } private void CompileAssembly(Assembly aAssembly) { Console.Write(new String('-', Console.BufferWidth)); Console.WriteLine("Starting compilation of assembly: " + aAssembly.GetName().Name); var xCompiler = new AssemblyCompiler(); xCompiler.DebugLog = delegate(LogSeverityEnum aSeverity, string aMessage) { this.DebugLog(aSeverity, aMessage); }; xCompiler.IsEntrypointAssembly = aAssembly == mEntryAssembly; xCompiler.Assembler = GetAssembler(aAssembly, xCompiler.IsEntrypointAssembly); xCompiler.Assembly = aAssembly; if (xCompiler.IsEntrypointAssembly) { foreach (var xRef in mAllAssemblies) { xCompiler.AssemblyReferences.Add(xRef.GetName().FullName); } } // todo: move this out to a property/event xCompiler.OpCodeMap = GetOpCodeMap(); xCompiler.Plugs.AddRange(Plugs); xCompiler.Types.AddRange(mGenericTypeInstancesToGenerate[aAssembly]); xCompiler.Methods.AddRange(mGenericMethodInstancesToGenerate[aAssembly]); xCompiler.Execute(); SaveAssembler(aAssembly, xCompiler.Assembler); } private void DoFullRecompile() { foreach (var xAsm in mAllAssemblies) { CompileAssembly(xAsm); } } private void SmartRecompile() { foreach (var xAsm in GetAssembliesToScanForGenerics(mAllAssemblies)) { CompileAssembly(xAsm); } } private void LoadCacheStates(IEnumerable xAllAssemblies) { mCacheStates.Clear(); foreach (var xAsm in xAllAssemblies) { var xState = new CacheState(); xState.Load(GetCacheStateFile(xAsm)); mCacheStates.Add(xAsm, xState); } } private Dictionary mCacheStates = new Dictionary(); private Dictionary> mGenericTypeInstancesToGenerate = new Dictionary>(); private Dictionary> mGenericMethodInstancesToGenerate = new Dictionary>(); private CacheState ScanAssembly(Assembly aAsm) { var xResult = new CacheState(); bool xShouldSkipMain = aAsm == mEntryAssembly; if (!mGenericTypeInstancesToGenerate.ContainsKey(aAsm)) { mGenericTypeInstancesToGenerate.Add(aAsm, new List()); } var xDeclaringType_TypeInstances = mGenericTypeInstancesToGenerate[aAsm]; if (!mGenericMethodInstancesToGenerate.ContainsKey(aAsm)) { mGenericMethodInstancesToGenerate.Add(aAsm, new List()); } var xDeclaringType_MethodInstances = mGenericMethodInstancesToGenerate[aAsm]; Action xCheckType = null; xCheckType = new Action(delegate(Type aType) { if (aType.IsGenericType && !aType.IsGenericTypeDefinition) { // add to the list of the current assembly if (!xResult.UsedGenericTypes.Contains(aType.AssemblyQualifiedName)) { xResult.UsedGenericTypes.Add(aType.AssemblyQualifiedName); } // add to the list of the declaring assembly, so it can generate it later on if (!xDeclaringType_TypeInstances.Contains(aType)) { xDeclaringType_TypeInstances.Add(aType); } foreach (var xArg in aType.GetGenericArguments()) { xCheckType(xArg); } } }); foreach (var xType in aAsm.GetTypes()) { if (xType.BaseType != null) { xCheckType(xType.BaseType); } foreach (var xMethod in xType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { if (xShouldSkipMain && xMethod == aAsm.EntryPoint) { continue; } xCheckType(xMethod.ReturnType); foreach (var xParam in xMethod.GetParameters()) { xCheckType(xParam.ParameterType); } try { if (xMethod.GetMethodBody() == null) { continue; } } catch (System.Security.VerificationException VE) { // apparently, ms uses some scary code for the .net framework.. continue; } catch (Exception E) { throw; } var xReader = new ILReader(xMethod); while (xReader.Read()) { switch (xReader.OpCode) { case OpCodeEnum.Call: case OpCodeEnum.Callvirt: case OpCodeEnum.Newobj: xCheckType(xReader.OperandValueMethod.DeclaringType); if (xReader.OperandValueMethod.IsGenericMethod && !xReader.OperandValueMethod.IsGenericMethodDefinition) { var xName = xReader.OperandValueMethod.GetFullName(); if (!xResult.UsedGenericMethods.Contains(xName)) { xResult.UsedGenericMethods.Add(xName); } // add to the list of the declaring assembly, so it can generate it later on if (!xDeclaringType_MethodInstances.Contains(xReader.OperandValueMethod)) { xDeclaringType_MethodInstances.Add(xReader.OperandValueMethod); } } break; } } } } return xResult; } private List GetAssembliesToScanForGenerics(IEnumerable xAllAssemblies) { var xAssembliesToScanForGenerics = new List(); foreach (var xAsm in xAllAssemblies) { var xLastTime = File.GetLastWriteTimeUtc(xAsm.Location).ToBinary(); var xChecksumFileName = GetChecksumFile(xAsm); if (File.Exists(xChecksumFileName)) { var xChecksumStr = File.ReadAllText(xChecksumFileName); long xChecksum; if (Int64.TryParse(xChecksumStr, out xChecksum)) { if (xChecksum == xLastTime) { continue; } } } xAssembliesToScanForGenerics.Add(xAsm); } return xAssembliesToScanForGenerics; } } }