diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index a5c324a33fada..42dcfabd3a8d6 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3919,11 +3919,6 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes) return (uint)sizeof(CORJIT_FLAGS); } - private PgoSchemaElem[] getPgoInstrumentationResults(MethodDesc method) - { - return _compilation.ProfileData[method]?.SchemaData; - } - private MemoryStream _cachedMemoryStream = new MemoryStream(); public static void ComputeJitPgoInstrumentationSchema(Func objectToHandle, PgoSchemaElem[] pgoResultsSchemas, out PgoInstrumentationSchema[] nativeSchemas, MemoryStream instrumentationData, Func typeFilter = null) @@ -4001,7 +3996,11 @@ private HRESULT getPgoInstrumentationResults(CORINFO_METHOD_STRUCT_* ftnHnd, ref if (!_pgoResults.TryGetValue(methodDesc, out PgoInstrumentationResults pgoResults)) { - var pgoResultsSchemas = getPgoInstrumentationResults(methodDesc); +#if READYTORUN + PgoSchemaElem[] pgoResultsSchemas = _compilation.ProfileData.GetAllowSynthesis(_compilation, methodDesc)?.SchemaData; +#else + PgoSchemaElem[] pgoResultsSchemas = _compilation.ProfileData[methodDesc]?.SchemaData; +#endif if (pgoResultsSchemas == null) { pgoResults.hr = HRESULT.E_NOTIMPL; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs index acc55fb5aa601..54d0c1968ea8f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs @@ -62,11 +62,8 @@ public abstract class ProfileData public abstract IEnumerable GetAllMethodProfileData(); public abstract byte[] GetMethodBlockCount(MethodDesc m); - public static void MergeProfileData(ref bool partialNgen, Dictionary mergedProfileData, ProfileData profileData) + public static void MergeProfileData(Dictionary mergedProfileData, ProfileData profileData) { - if (profileData.PartialNGen) - partialNgen = true; - PgoSchemaElem[][] schemaElemMergerArray = new PgoSchemaElem[2][]; foreach (MethodProfileData data in profileData.GetAllMethodProfileData()) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileDataManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileDataManager.cs index c7560253c38d1..9c1f5e5076fe2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileDataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileDataManager.cs @@ -3,11 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection.PortableExecutable; - using ILCompiler.IBC; - +using Internal.IL; using Internal.Pgo; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -18,12 +18,11 @@ public class ProfileDataManager { private readonly IBCProfileParser _ibcParser; private readonly List _inputData = new List(); - private readonly Dictionary _mergedProfileData = new Dictionary(); - private readonly Dictionary> _placedProfileMethods = new Dictionary>(); - private readonly HashSet _placedProfileMethodsAll = new HashSet(); - private readonly bool _partialNGen; + private readonly ProfileDataMap _inputProfileData; + private readonly ProfileDataMap _synthesizedProfileData; private readonly ReadyToRunCompilationModuleGroupBase _compilationGroup; private readonly CallChainProfile _callChainProfile; + private readonly GdvEntityFinder _gdvEntityFinder; public ProfileDataManager(Logger logger, IEnumerable possibleReferenceModules, @@ -38,7 +37,8 @@ public ProfileDataManager(Logger logger, ReadyToRunCompilationModuleGroupBase compilationGroup, bool embedPgoDataInR2RImage, bool parseIbcData, - Func canBeIncludedInCurrentCompilation) + Func canBeIncludedInCurrentCompilation, + bool synthesizeRandomPgoData) { EmbedPgoDataInR2RImage = embedPgoDataInR2RImage; _ibcParser = new IBCProfileParser(logger, possibleReferenceModules); @@ -84,86 +84,318 @@ public ProfileDataManager(Logger logger, } } - // Ensure each module has a hashset of methods available - foreach (var module in inputModules) + _inputProfileData = new ProfileDataMap(nonLocalGenericsHome, _compilationGroup); + _inputProfileData.LoadByMerging(_inputData); + + if (synthesizeRandomPgoData) + { + _gdvEntityFinder = new GdvEntityFinder(versionBubbleModules); + _synthesizedProfileData = new ProfileDataMap(nonLocalGenericsHome, _compilationGroup); + } + } + + public IEnumerable GetMethodsForModuleDesc(ModuleDesc moduleDesc) + { + return _inputProfileData.GetPlacedMethodsForModuleDesc(moduleDesc); + } + + public bool IsMethodInInputProfileData(MethodDesc method) + { + return _inputProfileData.Contains(method); + } + + public MethodProfileData this[MethodDesc method] + { + get { - _placedProfileMethods.Add(module, new HashSet()); + MethodProfileData mpd = _inputProfileData[method]; + if (mpd == null && _synthesizedProfileData != null) + { + lock (_synthesizedProfileData) + { + mpd = _synthesizedProfileData[method]; + } + } + + return mpd; } + } + + public bool EmbedPgoDataInR2RImage { get; } + public CallChainProfile CallChainProfile => _callChainProfile; - // Merge all data together - foreach (ProfileData profileData in _inputData) + public MethodProfileData GetAllowSynthesis(Compilation comp, MethodDesc method) + { + MethodProfileData existingProfileData = this[method]; + if (_synthesizedProfileData == null || existingProfileData != null) { - ProfileData.MergeProfileData(ref _partialNGen, _mergedProfileData, profileData); + return existingProfileData; } - // With the merged data find the set of methods to be placed within this module - foreach (var profileData in _mergedProfileData) + MethodProfileData profileData = null; + // We only support synthesizing PGO data for normal methods. + if (method is EcmaMethod or MethodForInstantiatedType or InstantiatedMethod) { - // If the method is not excluded from processing - if (!profileData.Value.Flags.HasFlag(MethodProfilingDataFlags.ExcludeHotMethodCode) && - !profileData.Value.Flags.HasFlag(MethodProfilingDataFlags.ExcludeColdMethodCode)) + profileData = new MethodProfileData(method, MethodProfilingDataFlags.ReadMethodCode, 0, null, 0, SynthesizeSchema(comp, method)); + + lock (_synthesizedProfileData) { - // Check for methods which are defined within the version bubble, and only rely on other modules within the bubble - if (!_compilationGroup.VersionsWithMethodBody(profileData.Key) && !_compilationGroup.CrossModuleCompileable(profileData.Key)) - continue; // Method not contained within version bubble and not cross module compileable + // We may race here but SynthesizeSchema is deterministic. + // Still, ensure that all threads end up seeing the same + // profile data instance. + existingProfileData = _synthesizedProfileData[method]; + if (existingProfileData != null) + profileData = existingProfileData; + else + _synthesizedProfileData.Add(profileData); + } + } - if (_compilationGroup.ContainsType(profileData.Key.OwningType) && - (profileData.Key.OwningType is MetadataType declaringType)) + return profileData; + } + + private PgoSchemaElem[] SynthesizeSchema(Compilation comp, MethodDesc method) + { + Random rand = new Random(method.GetHashCode()); + // We may be asked to synthesize PGO data for methods not in the + // compilation group due to cross-version bubble inlining (e.g. + // System.Object..ctor). + if (!_compilationGroup.ContainsMethodBody(method, false)) + { + return null; + } + + List elems = new(); + MethodIL il = comp.GetMethodIL(method); + ILReader reader = new ILReader(il.GetILBytes()); + while (reader.HasNext) + { + int instructionOffset = reader.Offset; + ILOpcode opcode = reader.ReadILOpcode(); + if (opcode is ILOpcode.call or ILOpcode.callvirt) + { + int token = reader.ReadILToken(); + object obj = il.GetObject(token); + MethodDesc targetMeth = obj as MethodDesc; + if (targetMeth == null) + continue; + + if (targetMeth.Signature.IsStatic) + continue; + + bool isDelegateInvoke = targetMeth.OwningType.IsDelegate && targetMeth.Name == "Invoke"; + + if (opcode != ILOpcode.callvirt && !isDelegateInvoke) + continue; + + List entities; + PgoInstrumentationKind kind; + + if (isDelegateInvoke) { - // In this case the method is placed in its natural home (which is the defining module of the method) - _placedProfileMethods[declaringType.Module].Add(profileData.Key); - _placedProfileMethodsAll.Add(profileData.Key); + entities = SampleMethodsCompatibleWithDelegateInvocation(targetMeth.Signature, rand); + kind = PgoInstrumentationKind.HandleHistogramMethods; } else { - // If the defining module is not within the input set, if the nonLocalGenericsHome is provided, place it there - if ((nonLocalGenericsHome != null) && (profileData.Key.GetTypicalMethodDefinition() != profileData.Key)) - { - _placedProfileMethods[nonLocalGenericsHome].Add(profileData.Key); - _placedProfileMethodsAll.Add(profileData.Key); - } + entities = new List(0); + kind = PgoInstrumentationKind.HandleHistogramTypes; } + + elems.Add( + new PgoSchemaElem + { + InstrumentationKind = PgoInstrumentationKind.HandleHistogramIntCount, + Count = 1, + ILOffset = instructionOffset, + DataLong = 104729, + }); + + elems.Add( + new PgoSchemaElem + { + InstrumentationKind = kind, + Count = entities.Count, + ILOffset = instructionOffset, + DataObject = entities.ToArray() + }); + } + else + { + reader.Skip(opcode); } } + + if (elems.Count > 0) + { + // TODO: JIT should be able to handle PGO data that only contains histograms. + elems.Insert(0, new PgoSchemaElem { InstrumentationKind = PgoInstrumentationKind.BasicBlockIntCount, ILOffset = 0, Count = 1, DataLong = 5 }); + return elems.ToArray(); + } + else + { + return null; + } } - /// - /// Get the defining module for a method which is entirely defined within the version bubble - /// If a module is a generic which has interaction modules outside of the version bubble, return null. - /// - private ModuleDesc GetDefiningModuleForMethodWithinVersionBubble(MethodDesc method, HashSet versionBubble) + private List SampleMethodsCompatibleWithDelegateInvocation(MethodSignature delegateSignature, Random rand) { - if (_compilationGroup.VersionsWithMethodBody(method) && (method.OwningType is MetadataType metadataType)) + Debug.Assert(_gdvEntityFinder != null); + + const int sampleSize = 3; + List result = new(sampleSize); + + IList compatible = _gdvEntityFinder.GetCompatibleWithDelegateInvoke(delegateSignature); + if (compatible.Count <= sampleSize) { - return metadataType.Module; + foreach (MethodDesc md in compatible) + result.Add(new TypeSystemEntityOrUnknown(md)); + } + else + { + while (result.Count < sampleSize) + { + int index = rand.Next(compatible.Count); + MethodDesc md = compatible[index]; + bool contains = false; + foreach (TypeSystemEntityOrUnknown existing in result) + { + if (existing.AsMethod == md) + { + contains = true; + break; + } + } + + if (!contains) + result.Add(new TypeSystemEntityOrUnknown(md)); + } } - return null; + return result; } - public IEnumerable GetMethodsForModuleDesc(ModuleDesc moduleDesc) + private class ProfileDataMap { - if (_placedProfileMethods.TryGetValue(moduleDesc, out var precomputedProfileData)) - return precomputedProfileData.ToArray(); + private readonly ModuleDesc _nonLocalGenericsHome; + private readonly ReadyToRunCompilationModuleGroupBase _compilationGroup; + private readonly Dictionary _profileData = new(); + private readonly Dictionary> _placedProfileMethods = new(); + private readonly HashSet _placedProfileMethodsAll = new(); - return Array.Empty(); - } + public ProfileDataMap(ModuleDesc nonLocalGenericsHome, ReadyToRunCompilationModuleGroupBase compilationGroup) + { + _nonLocalGenericsHome = nonLocalGenericsHome; + _compilationGroup = compilationGroup; + } - public bool IsMethodInProfileData(MethodDesc method) - { - return _placedProfileMethodsAll.Contains(method); + public MethodProfileData this[MethodDesc method] + => _profileData.GetValueOrDefault(method); + + public void LoadByMerging(IEnumerable data) + { + // Merge all data together + foreach (ProfileData profileData in data) + { + ProfileData.MergeProfileData(_profileData, profileData); + } + + // With the merged data find the set of methods to be placed within this module + foreach ((MethodDesc method, MethodProfileData profileData) in _profileData) + { + AssociateMethodProfileDataWithModule(method, profileData); + } + } + + public bool Contains(MethodDesc md) => _profileData.ContainsKey(md); + + private void AssociateMethodProfileDataWithModule(MethodDesc method, MethodProfileData profileData) + { + // If the method is not excluded from processing + if (profileData.Flags.HasFlag(MethodProfilingDataFlags.ExcludeHotMethodCode) || + profileData.Flags.HasFlag(MethodProfilingDataFlags.ExcludeColdMethodCode)) + { + return; + } + + // Check for methods which are defined within the version bubble, and only rely on other modules within the bubble + if (!_compilationGroup.VersionsWithMethodBody(method) && !_compilationGroup.CrossModuleCompileable(method)) + return; // Method not contained within version bubble and not cross module compileable + + ModuleDesc home = null; + if (_compilationGroup.ContainsType(method.OwningType) && + (method.OwningType is MetadataType declaringType)) + { + // In this case the method is placed in its natural home (which is the defining module of the method) + home = declaringType.Module; + } + else + { + // If the defining module is not within the input set, if the nonLocalGenericsHome is provided, place it there + if ((_nonLocalGenericsHome != null) && (method.GetTypicalMethodDefinition() != method)) + { + home = _nonLocalGenericsHome; + } + } + + if (home != null) + { + if (!_placedProfileMethods.TryGetValue(home, out HashSet set)) + _placedProfileMethods.Add(home, set = new HashSet()); + + set.Add(method); + _placedProfileMethodsAll.Add(method); + } + } + + public IEnumerable GetPlacedMethodsForModuleDesc(ModuleDesc moduleDesc) + { + if (_placedProfileMethods.TryGetValue(moduleDesc, out var precomputedProfileData)) + return precomputedProfileData.ToArray(); + + return Array.Empty(); + } + + + public void Add(MethodProfileData profileData) + { + _profileData.Add(profileData.Method, profileData); + AssociateMethodProfileDataWithModule(profileData.Method, profileData); + } } - public MethodProfileData this[MethodDesc method] + private class GdvEntityFinder { - get + // Currently, we just use direct signature equality between the + // delegate's Invoke method and the target method. This does not + // take covariance into account in addition to other more + // restrictive MethodSignature checks. + private readonly Dictionary> _delegateTargets = new(); + + public GdvEntityFinder(IEnumerable modules) { - _mergedProfileData.TryGetValue(method, out var profileData); - return profileData; + foreach (ModuleDesc module in modules) + { + foreach (MetadataType type in module.GetAllTypes()) + { + foreach (MethodDesc method in type.GetMethods()) + { + if (method.Signature.IsStatic) + continue; + + if (!_delegateTargets.TryGetValue(method.Signature, out List list)) + _delegateTargets.Add(method.Signature, list = new List()); + + list.Add(method); + } + } + } } - } - public bool EmbedPgoDataInR2RImage { get; } - public CallChainProfile CallChainProfile => _callChainProfile; + public IList GetCompatibleWithDelegateInvoke(MethodSignature delegateInvokeSignature) + { + return _delegateTargets.TryGetValue(delegateInvokeSignature, out List methods) ? methods : Array.Empty(); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs index cb4532ac16705..94115c782acfb 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs @@ -509,7 +509,7 @@ private bool CrossModuleInlineableInternal(MethodDesc method) return _crossModuleInlineableCache.GetOrAdd(method, _crossModuleInlineableCacheUncached); } - private bool CrossModuleInlineableUncached(MethodDesc method) + private bool CrossModuleInlineableUncached(MethodDesc method) { // Defined in corelib MetadataType owningMetadataType = method.OwningType.GetTypeDefinition() as MetadataType; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs index 11eaac885050b..db114cba999b9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs @@ -29,7 +29,7 @@ public sealed override bool ContainsMethodBody(MethodDesc method, bool unboxingS if (_profileGuidedCompileRestriction != null) { - if (!_profileGuidedCompileRestriction.IsMethodInProfileData(method)) + if (!_profileGuidedCompileRestriction.IsMethodInInputProfileData(method)) return false; } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/ProfileDataManager.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/ProfileDataManager.cs index 7e2ac2051b5c0..22cf86291fd10 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/ProfileDataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/ProfileDataManager.cs @@ -26,12 +26,10 @@ public ProfileDataManager(IEnumerable mibcFiles, } } - bool dummy = false; - // Merge all data together foreach (ProfileData profileData in _inputData) { - ProfileData.MergeProfileData(ref dummy, _mergedProfileData, profileData); + ProfileData.MergeProfileData(_mergedProfileData, profileData); } } diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index d823ea26ca26d..54014fabf999f 100644 --- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs +++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs @@ -178,6 +178,8 @@ internal class Crossgen2RootCommand : RootCommand new(new[] { "--make-repro-path" }, "Path where to place a repro package"); public Option HotColdSplitting { get; } = new(new[] { "--hot-cold-splitting" }, SR.HotColdSplittingOption); + public Option SynthesizeRandomMibc { get; } = + new(new[] { "--synthesize-random-mibc" }); public bool CompositeOrInputBubble { get; private set; } public OptimizationMode OptimizationMode { get; private set; } @@ -243,6 +245,7 @@ public Crossgen2RootCommand(string[] args) : base(SR.Crossgen2BannerText) AddOption(CallChainProfileFile); AddOption(MakeReproPath); AddOption(HotColdSplitting); + AddOption(SynthesizeRandomMibc); this.SetHandler(context => { diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index c09d42c7afd1a..76724216063d6 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -503,7 +503,8 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru compilationGroup, Get(_command.EmbedPgoData), Get(_command.SupportIbc), - crossModuleInlineableCode.Count == 0 ? compilationGroup.VersionsWithMethodBody : compilationGroup.CrossModuleInlineable); + crossModuleInlineableCode.Count == 0 ? compilationGroup.VersionsWithMethodBody : compilationGroup.CrossModuleInlineable, + Get(_command.SynthesizeRandomMibc)); bool partial = Get(_command.Partial); compilationGroup.ApplyProfileGuidedOptimizationData(profileDataManager, partial); diff --git a/src/coreclr/tools/dotnet-pgo/Program.cs b/src/coreclr/tools/dotnet-pgo/Program.cs index f7ba495f5acb5..1cd2c0f74084a 100644 --- a/src/coreclr/tools/dotnet-pgo/Program.cs +++ b/src/coreclr/tools/dotnet-pgo/Program.cs @@ -428,13 +428,12 @@ private int InnerMergeMain() { var tsc = new TypeRefTypeSystem.TypeRefTypeSystemContext(mibcReaders); - bool partialNgen = false; Dictionary mergedProfileData = new Dictionary(); for (int i = 0; i < mibcReaders.Length; i++) { var peReader = mibcReaders[i]; PrintDetailedMessage($"Merging {paths[i]}"); - ProfileData.MergeProfileData(ref partialNgen, mergedProfileData, MIbcProfileParser.ParseMIbcFile(tsc, peReader, assemblyNamesInBubble, onlyDefinedInAssembly: null)); + ProfileData.MergeProfileData(mergedProfileData, MIbcProfileParser.ParseMIbcFile(tsc, peReader, assemblyNamesInBubble, onlyDefinedInAssembly: null)); } MibcConfig mergedConfig = ParseMibcConfigsAndMerge(tsc, mibcReaders);