From c924cd6aef5028b828055009f0b0578e1d0b95f5 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 22 Jul 2012 22:59:47 +0200 Subject: [PATCH] Lazy-load the debugger type system. --- .../Debugger.Core/TypeSystemExtensions.cs | 110 +++++++++++------- .../TypeSystem/CecilLoader.cs | 70 ++++++++--- 2 files changed, 125 insertions(+), 55 deletions(-) diff --git a/src/AddIns/Debugger/Debugger.Core/TypeSystemExtensions.cs b/src/AddIns/Debugger/Debugger.Core/TypeSystemExtensions.cs index 094da9fa4a..3173880da5 100644 --- a/src/AddIns/Debugger/Debugger.Core/TypeSystemExtensions.cs +++ b/src/AddIns/Debugger/Debugger.Core/TypeSystemExtensions.cs @@ -30,20 +30,61 @@ namespace Debugger public Dictionary MetadataTokens = new Dictionary(); public Dictionary TokenToMethod = new Dictionary(); public Dictionary LocalVariableTypes = new Dictionary(); + readonly CecilLoader typeRefLoader; - public ModuleMetadataInfo(Module module) + public ModuleMetadataInfo(Module module, Mono.Cecil.ModuleDefinition cecilModule) { this.Module = module; + + typeRefLoader = new CecilLoader(); + typeRefLoader.SetCurrentModule(cecilModule); } - public void AddMethod(IUnresolvedMethod method, CecilLoader loader) + public void AddMember(IUnresolvedEntity entity, Mono.Cecil.MemberReference cecilObject) { - var cecilMethod = loader.GetCecilObject(method); - uint token = cecilMethod.MetadataToken.ToUInt32(); - this.MetadataTokens[method] = token; - this.TokenToMethod[token] = method; - if (cecilMethod.HasBody) - this.LocalVariableTypes[method] = cecilMethod.Body.Variables.Select(v => loader.ReadTypeReference(v.VariableType)).ToArray(); + uint token = cecilObject.MetadataToken.ToUInt32(); + this.MetadataTokens[entity] = token; + + var cecilMethod = cecilObject as Mono.Cecil.MethodDefinition; + if (cecilMethod != null) { + IUnresolvedMethod method = (IUnresolvedMethod)entity; + this.TokenToMethod[token] = method; + if (cecilMethod.HasBody) { + var locals = cecilMethod.Body.Variables; + if (locals.Count > 0) { + this.LocalVariableTypes[method] = locals.Select(v => typeRefLoader.ReadTypeReference(v.VariableType)).ToArray(); + } + if (cecilMethod.RVA != 0) { + // The method was loaded from image - we can free the memory for the body + // because Cecil will re-initialize it on demand + cecilMethod.Body = null; + } + } + } + } + } + + // used to prevent Cecil from loading referenced assemblies + sealed class DummyAssemblyResolver : Mono.Cecil.IAssemblyResolver + { + public Mono.Cecil.AssemblyDefinition Resolve(Mono.Cecil.AssemblyNameReference name) + { + return null; + } + + public Mono.Cecil.AssemblyDefinition Resolve(string fullName) + { + return null; + } + + public Mono.Cecil.AssemblyDefinition Resolve(Mono.Cecil.AssemblyNameReference name, Mono.Cecil.ReaderParameters parameters) + { + return null; + } + + public Mono.Cecil.AssemblyDefinition Resolve(string fullName, Mono.Cecil.ReaderParameters parameters) + { + return null; } } @@ -57,39 +98,18 @@ namespace Debugger return Task.Run(() => LoadModule(module, name)); } - static IUnresolvedAssembly LoadModule(Module module, string name) + static IUnresolvedAssembly LoadModule(Module module, string fileName) { - CecilLoader loader = new CecilLoader(true); + var param = new Mono.Cecil.ReaderParameters { AssemblyResolver = new DummyAssemblyResolver() }; + var cecilModule = Mono.Cecil.ModuleDefinition.ReadModule(fileName, param); + + var moduleMetadataInfo = new ModuleMetadataInfo(module, cecilModule); + var loader = new CecilLoader(); loader.IncludeInternalMembers = true; loader.LazyLoad = true; - var asm = loader.LoadAssemblyFile(name); - var moduleMetadataInfo = new ModuleMetadataInfo(module); - foreach (var typeDef in asm.GetAllTypeDefinitions()) { - var cecilTypeDef = loader.GetCecilObject(typeDef); - loader.SetCurrentModule(cecilTypeDef.Module); - moduleMetadataInfo.MetadataTokens[typeDef] = cecilTypeDef.MetadataToken.ToUInt32(); - foreach (var member in typeDef.Fields) { - var cecilMember = loader.GetCecilObject(member); - moduleMetadataInfo.MetadataTokens[member] = cecilMember.MetadataToken.ToUInt32(); - } - foreach (var member in typeDef.Methods) { - moduleMetadataInfo.AddMethod(member, loader); - } - foreach (var member in typeDef.Properties) { - if (member.CanGet) - moduleMetadataInfo.AddMethod(member.Getter, loader); - if (member.CanSet) - moduleMetadataInfo.AddMethod(member.Setter, loader); - } - foreach (var member in typeDef.Events) { - if (member.CanAdd) - moduleMetadataInfo.AddMethod(member.AddAccessor, loader); - if (member.CanRemove) - moduleMetadataInfo.AddMethod(member.RemoveAccessor, loader); - if (member.CanInvoke) - moduleMetadataInfo.AddMethod(member.InvokeAccessor, loader); - } - } + loader.OnEntityLoaded = moduleMetadataInfo.AddMember; + + var asm = loader.LoadAssembly(cecilModule.Assembly); weakTable.Add(asm, moduleMetadataInfo); return asm; } @@ -441,8 +461,18 @@ namespace Debugger { Module module = compilation.GetAppDomain().Process.GetModule(corFunction.GetModule()); var info = GetInfo(module.Assembly); - var unresolved = info.TokenToMethod[corFunction.GetToken()]; - return unresolved.Resolve(new SimpleTypeResolveContext(module.Assembly)); + uint functionToken = corFunction.GetToken(); + IUnresolvedMethod unresolvedMethod; + if (!info.TokenToMethod.TryGetValue(functionToken, out unresolvedMethod)) { + // The type containing this function wasn't loaded yet + uint classToken = corFunction.GetClass().GetToken(); + var definition = ToTypeDefinitionReference(module, classToken).Resolve(new SimpleTypeResolveContext(module.Assembly)).GetDefinition(); + if (definition == null) + throw new InvalidOperationException("Could not find class for token " + classToken); + definition.Methods.ToList(); // enforce loading the methods so that they get added to the dictionary + unresolvedMethod = info.TokenToMethod[functionToken]; + } + return unresolvedMethod.Resolve(new SimpleTypeResolveContext(module.Assembly)); } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index e4322822f9..0eb735a759 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -55,6 +55,12 @@ namespace ICSharpCode.NRefactory.TypeSystem /// the Cecil objects to stay in memory (which can significantly increase memory usage). /// It also prevents serialization of the Cecil-loaded type system. /// + /// + /// Because the type system can be used on multiple threads, but Cecil is not + /// thread-safe for concurrent read access, the CecilLoader will lock on the instance + /// for every delay-loading operation. + /// If you access the Cecil objects directly in your application, you may need to take the same lock. + /// public bool LazyLoad { get; set; } /// @@ -72,6 +78,17 @@ namespace ICSharpCode.NRefactory.TypeSystem /// public CancellationToken CancellationToken { get; set; } + /// + /// This delegate gets executed whenever an entity was loaded. + /// + /// + /// This callback may be to build a dictionary that maps between + /// entities and cecil objects. + /// Warning: if delay-loading is used and the type system is accessed by multiple threads, + /// the callback may be invoked concurrently on multiple threads. + /// + public Action OnEntityLoaded { get; set; } + /// /// Gets a value indicating whether this instance stores references to the cecil objects. /// @@ -84,19 +101,26 @@ namespace ICSharpCode.NRefactory.TypeSystem ModuleDefinition currentModule; CecilUnresolvedAssembly currentAssembly; + /// + /// Initializes a new instance of the class. + /// + public CecilLoader() + { + // Enable interning by default. + this.InterningProvider = new SimpleInterningProvider(); + } + /// /// Initializes a new instance of the class. /// /// /// If true references to the cecil objects are hold. In this case the cecil loader can do a type system -> cecil mapping. /// - public CecilLoader (bool createCecilReferences = false) + [Obsolete("The built-in entity<->cecil mapping is obsolete. Use the OnEntityLoaded callback instead!")] + public CecilLoader(bool createCecilReferences) : this() { if (createCecilReferences) typeSystemTranslationTable = new Dictionary (); - - // Enable interning by default. - this.InterningProvider = new SimpleInterningProvider(); } /// @@ -108,6 +132,7 @@ namespace ICSharpCode.NRefactory.TypeSystem this.typeSystemTranslationTable = loader.typeSystemTranslationTable; this.IncludeInternalMembers = loader.IncludeInternalMembers; this.LazyLoad = loader.LazyLoad; + this.OnEntityLoaded = loader.OnEntityLoaded; this.currentModule = loader.currentModule; this.currentAssembly = loader.currentAssembly; // don't use interning - the interning provider is most likely not thread-safe @@ -169,12 +194,15 @@ namespace ICSharpCode.NRefactory.TypeSystem continue; if (this.LazyLoad) { - currentAssembly.AddTypeDefinition(new LazyCecilTypeDefinition(cecilLoaderCloneForLazyLoading, td)); + var t = new LazyCecilTypeDefinition(cecilLoaderCloneForLazyLoading, td); + currentAssembly.AddTypeDefinition(t); + RegisterCecilObject(t, td); } else { var t = CreateTopLevelTypeDefinition(td); cecilTypeDefs.Add(td); typeDefs.Add(t); currentAssembly.AddTypeDefinition(t); + // The registration will happen after the members are initialized } } } @@ -184,7 +212,7 @@ namespace ICSharpCode.NRefactory.TypeSystem InitTypeDefinition(cecilTypeDefs[i], typeDefs[i]); } - RegisterCecilObject(this.currentAssembly, assemblyDefinition); + AddToTypeSystemTranslationTable(this.currentAssembly, assemblyDefinition); var result = this.currentAssembly; this.currentAssembly = null; @@ -249,9 +277,7 @@ namespace ICSharpCode.NRefactory.TypeSystem throw new ArgumentNullException("fileName"); var param = new ReaderParameters { AssemblyResolver = new DummyAssemblyResolver() }; AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(fileName, param); - var result = LoadAssembly(asm); - RegisterCecilObject(result, asm); - return result; + return LoadAssembly(asm); } // used to prevent Cecil from loading referenced assemblies @@ -1742,7 +1768,6 @@ namespace ICSharpCode.NRefactory.TypeSystem this.ApplyInterningProvider(loader.InterningProvider); } this.Freeze(); - loader.RegisterCecilObject(this, typeDefinition); } public override string Namespace { @@ -1772,7 +1797,8 @@ namespace ICSharpCode.NRefactory.TypeSystem var result = LazyInit.VolatileRead(ref this.baseTypes); if (result != null) { return result; - } else { + } + lock (loader.currentModule) { result = new List(); loader.InitBaseTypes(cecilTypeDef, result); return LazyInit.GetOrSet(ref this.baseTypes, FreezableHelper.FreezeList(result)); @@ -1785,7 +1811,10 @@ namespace ICSharpCode.NRefactory.TypeSystem var result = LazyInit.VolatileRead(ref this.nestedTypes); if (result != null) { return result; - } else { + } + lock (loader.currentModule) { + if (this.nestedTypes != null) + return this.nestedTypes; result = new List(); loader.InitNestedTypes(cecilTypeDef, this, result); return LazyInit.GetOrSet(ref this.nestedTypes, FreezableHelper.FreezeList(result)); @@ -1798,7 +1827,10 @@ namespace ICSharpCode.NRefactory.TypeSystem var result = LazyInit.VolatileRead(ref this.members); if (result != null) { return result; - } else { + } + lock (loader.currentModule) { + if (this.members != null) + return this.members; result = new List(); loader.InitMembers(cecilTypeDef, this, result); return LazyInit.GetOrSet(ref this.members, FreezableHelper.FreezeList(result)); @@ -2160,7 +2192,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } #endregion - void FinishReadMember(AbstractUnresolvedMember member, object cecilDefinition) + void FinishReadMember(AbstractUnresolvedMember member, MemberReference cecilDefinition) { if (this.InterningProvider != null) member.ApplyInterningProvider(this.InterningProvider); @@ -2171,7 +2203,15 @@ namespace ICSharpCode.NRefactory.TypeSystem #region Type system translation table readonly Dictionary typeSystemTranslationTable; - void RegisterCecilObject(object typeSystemObject, object cecilObject) + void RegisterCecilObject(IUnresolvedEntity typeSystemObject, MemberReference cecilObject) + { + if (OnEntityLoaded != null) + OnEntityLoaded(typeSystemObject, cecilObject); + + AddToTypeSystemTranslationTable(typeSystemObject, cecilObject); + } + + void AddToTypeSystemTranslationTable(object typeSystemObject, object cecilObject) { if (typeSystemTranslationTable != null) { // When lazy-loading, the dictionary might be shared between multiple cecil-loaders that are used concurrently