using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using Indy.IL2CPU.Plugs; namespace Cosmos.IL2CPU { public class ILScanner { // Here are old comments - we moved to a Dictionary + List, which is much better esp // now that we need lookups to the indexes // List is needed for processing. // //Note: We have both HashSet and List because HashSet.Contains is much faster // than List.Contains. Also in the future we may remove items from the List // which have already been processed yet need to keep them in HashSet. //TODO: When we go threaded, these two should be encapselated into a single // class with thread safety. //TODO: These store the MethodBase which also have the IL for the body in memory // For large asms this could eat lot of RAM. Should convert this to remove // items from the list after they are processed but keep them in HashSet so we // know they are already done. Currently HashSet uses a reference though, so we // need to hash on some UID instead of the refernce. Do not use strings, they are // super slow. // TODO: We need to scan for static fields too. private Dictionary mKnownMethods = new Dictionary(); // We need a separate list because we cannot iterate keys by index, and any functions // to get a list of keys will do a on demand copy, which won't meet our needs either // becuase we have to walk the list dynamically as it grows, which is also why we need to // index it rather than enumerate it with foreach. // We also need a separate list becuase Execute is called multiple // times to process plugs and so known methods accumulates, // but we dont want to reproces old methods from previous Execute calls. private List mMethodsToProcess = new List(); // ExecuteInternal is called multiple times, we don't want to rescan // ones that are "finished" so we update this "pointer" private int mMethodsToProcessStart; // List of plug implementations. // Key: MethodBase of targetted method // Value: index into mMethodsToProcess private Dictionary mMethodPlugs = new Dictionary(); //TODO: Likely change this to be like Methods to be more efficient. Might only need Dictionary private HashSet mTypesSet = new HashSet(); private List mTypes = new List(); protected ILReader mReader; protected Assembler mAsmblr; public ILScanner(Assembler aAsmblr) { mAsmblr = aAsmblr; mReader = new ILReader(); } public void Execute(System.Reflection.MethodInfo aStartMethod) { // Scan plugs first, so when we scan from // entry point plugs will be found. //TODO: Move plug scans etc into Scanner foreach (var xAsm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var xType in xAsm.GetTypes()) { foreach (var xAttrib1 in xType.GetCustomAttributes(false)) { // Find all classes marked as a Plug if (xAttrib1 is PlugAttribute) { var xTypeAttrib = (PlugAttribute)xAttrib1; // See if there is a custom PlugMethod attribute // Plug implementations must be static and public, so // we narrow the search to meet these requirements foreach (var xMethod in xType.GetMethods(BindingFlags.Static | BindingFlags.Public)) { PlugMethodAttribute xMethodAttrib = null; foreach (var xAttrib2 in xMethod.GetCustomAttributes(false)) { if (xAttrib2 is PlugMethodAttribute) { xMethodAttrib = (PlugMethodAttribute)xAttrib2; } } // See if we need to disable this plug bool xEnabled = true; if (xMethodAttrib != null) { //TODO: Check this against build options //TODO: Two exclusive IsOnly's dont make sense // refactor these as a positive rather than negative if (xMethodAttrib.IsMonoOnly) { xEnabled = false; } else { xEnabled = xMethodAttrib.Enabled; } } if (xEnabled) { // for PlugMethodAttribute: //TODO: public string Signature; //[PlugMethod(Signature = "System_Void__Indy_IL2CPU_Assembler_Assembler__cctor__")] //TODO: public Type Assembler = null; // Scan the plug implementation uint xUID = ExecuteInternal(xMethod, true); // Add the method to the list of plugged methods var xParams = xMethod.GetParameters(); //TODO: Static method plugs dont seem to be separated // from instance ones, so the only way seems to be to try // to match instance first, and if no match try static. // I really don't like this and feel we need to find // an explicit way to determine or mark the method // implementations. // // Plug implementations take this as first argument // so when matching we don't include it in the search Type[] xTypesInst = null; Type[] xTypesStatic = new Type[xParams.Length]; // If 0 params, has to be a static plug so we skip // any copying and leave xTypesInst = null // If 1 params, xTypesInst must be converted to Type[0] if (xParams.Length == 1) { xTypesInst = new Type[0]; xTypesStatic[0] = xParams[0].ParameterType; } else if (xParams.Length > 1) { xTypesInst = new Type[xParams.Length - 1]; for (int i = 0; i <= xTypesInst.Length - 1; i++) { xTypesInst[i] = xParams[i + 1].ParameterType; } for (int i = 0; i <= xTypesStatic.Length - 1; i++) { xTypesStatic[i] = xParams[i].ParameterType; } } System.Reflection.MethodInfo xTargetMethod = null; if (xTypesInst != null) { xTargetMethod = xTypeAttrib.Target.GetMethod(xMethod.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, xTypesInst, null); } // Not an instance method, try static if (xTargetMethod == null) { xTargetMethod = xTypeAttrib.Target.GetMethod(xMethod.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, xTypesStatic, null); } if (xTargetMethod == null) { throw new Exception("Plug target method not found."); } mMethodPlugs.Add(xTargetMethod, xUID); } } } } //TODO: Look for Field plugs } } // Scan from entry point of this program //TODO: Now that we scan plugs first, we might need to put a jump // in the asm to jump to the entry point? ExecuteInternal(aStartMethod, false); } private uint ExecuteInternal(System.Reflection.MethodInfo aStartMethod, bool aIsPlug) { // See comment at mMethodsToProcessStart declaration mMethodsToProcessStart = mMethodsToProcess.Count; uint xResult = QueueMethod(aStartMethod, aIsPlug); // Cannot use foreach, the list changes as we go // and we dont start at 0 for (int i = mMethodsToProcessStart; i < mMethodsToProcess.Count; i++) { var xMethod = mMethodsToProcess[i]; if (xMethod.Type != MethodInfo.TypeEnum.NeedsPlug) { ScanMethod(xMethod); } } // ie // var xSB = new StringBuilder("test"); // object x = xSB; // string y = xSB.ToString(); // // Now that we did a full normal scan, rescan and find all virtuals // and for each virtual scan all included types and include descendant overrides. // I think we need to scan for ancestor calls too... // This process will add more classes etc.. so the process will need to be repeated // until no more new methods are found. // //TODO: Speed this up somehow.... int xMethodCount; do { xMethodCount = mMethodsToProcess.Count; // Cannot use foreach, the list changes as we go for (int i = mMethodsToProcessStart; i < mMethodsToProcess.Count; i++) { var xMethodBase = mMethodsToProcess[i].MethodBase; if (xMethodBase.IsVirtual) { foreach (var xType in mTypes) { // Find ancestors and descendants if (xType.IsSubclassOf(xMethodBase.DeclaringType) || xMethodBase.DeclaringType.IsSubclassOf(xType)) { var xParams = xMethodBase.GetParameters(); var xParamTypes = new Type[xParams.Length]; for (int j = 0; j < xParams.Length; j++) { xParamTypes[j] = xParams[j].ParameterType; } var xNewMethod = xType.GetMethod(xMethodBase.Name, xParamTypes); if (xNewMethod != null) { if (!xNewMethod.IsAbstract) { // abstract methods dont have an implementation QueueMethod(xNewMethod, false); } } } } } } } while (xMethodCount != mMethodsToProcess.Count); return xResult; } private void ScanMethod(MethodInfo aMethodInfo) { var xMethodBase = aMethodInfo.MethodBase; // Call ProcessMethod first, later in a threaded environment it will // allow more threads to work slightly sooner var xOpCodes = mReader.ProcessMethod(xMethodBase); if (xOpCodes != null) { foreach (var xOpCode in xOpCodes) { //InstructionCount++; if (xOpCode is ILOpCodes.OpMethod) { ((ILOpCodes.OpMethod)xOpCode).ValueUID = QueueMethod(((ILOpCodes.OpMethod)xOpCode).Value, false); } else if (xOpCode is ILOpCodes.OpType) { QueueType(((ILOpCodes.OpType)xOpCode).Value); } } // Assemble the method mAsmblr.ProcessMethod(aMethodInfo, xOpCodes); } } public uint QueueMethod(MethodBase aMethodBase, bool aIsPlug) { uint xResult; // If already queued, skip it if (mKnownMethods.TryGetValue(aMethodBase, out xResult)) { return xResult; } xResult = (uint)mMethodsToProcess.Count; mKnownMethods.Add(aMethodBase, xResult); MethodInfo.TypeEnum xMethodType; if (aIsPlug) { xMethodType = MethodInfo.TypeEnum.Plug; } else { xMethodType = MethodInfo.TypeEnum.Normal; if ((aMethodBase.Attributes & MethodAttributes.PinvokeImpl) != 0) { // pinvoke methods dont have an embedded implementation xMethodType = MethodInfo.TypeEnum.NeedsPlug; } else { var xImplFlags = aMethodBase.GetMethodImplementationFlags(); if ((xImplFlags & MethodImplAttributes.Native) != 0) { // native implementations cannot be compiled xMethodType = MethodInfo.TypeEnum.NeedsPlug; } } } uint xPlugId = 0; MethodInfo xPlug = null; if (mMethodPlugs.TryGetValue(aMethodBase, out xPlugId)) { xPlug = mMethodsToProcess[(int)xPlugId]; } var xMethod = new MethodInfo(aMethodBase, xResult, xMethodType); if (xMethodType == MethodInfo.TypeEnum.Plug) { mMethodPlugs.Add(aMethodBase, xResult); } //TODO: Might still need this one, see after we get assembly output again //Im hoping the operand walking we have now will include this on its own. //QueueType(aMethod.DeclaringType); //var xMethodInfo = aMethod as MethodInfo; //if (xMethodInfo != null) { // QueueType(xMethodInfo.ReturnType); //} //foreach (var xParam in aMethod.GetParameters()) { // QueueType(xParam.ParameterType); //} return xResult; } //protected void QueueStaticField(FieldInfo aFieldInfo) { // if (!mFieldsSet.Contains(aFieldInfo)) { // if (!aFieldInfo.IsStatic) { // throw new Exception("Cannot queue instance fields!"); // } // mFieldsSet.Add(aFieldInfo); // QueueType(aFieldInfo.DeclaringType); // QueueType(aFieldInfo.FieldType); // } //} protected void QueueType(Type aType) { if (!mTypesSet.Contains(aType)) { mTypesSet.Add(aType); mTypes.Add(aType); if (aType.BaseType != null) { QueueType(aType.BaseType); } // queue static constructor foreach (var xCctor in aType.GetConstructors(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) { if (xCctor.DeclaringType == aType) { QueueMethod(xCctor, false); } } } } public int MethodCount { get { return mMethodsToProcess.Count; } } } }