diff --git a/src/coreclr/inc/eventtracebase.h b/src/coreclr/inc/eventtracebase.h index 012af669ac67a9..b6a9ec2e2ccfa5 100644 --- a/src/coreclr/inc/eventtracebase.h +++ b/src/coreclr/inc/eventtracebase.h @@ -1169,7 +1169,8 @@ namespace ETW typedef enum _Sku { DesktopCLR=0x1, - CoreCLR=0x2 + CoreCLR=0x2, + Mono=0x4 }Sku; typedef enum _EtwMode diff --git a/src/coreclr/nativeaot/Runtime/eventtracebase.h b/src/coreclr/nativeaot/Runtime/eventtracebase.h index 11112593a9e4e5..ebac2358593138 100644 --- a/src/coreclr/nativeaot/Runtime/eventtracebase.h +++ b/src/coreclr/nativeaot/Runtime/eventtracebase.h @@ -692,7 +692,8 @@ namespace ETW typedef enum _Sku { DesktopCLR=0x1, - CoreCLR=0x2 + CoreCLR=0x2, + Mono=0x4 }Sku; typedef enum _EtwMode diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs index dc5641a83366d6..cec289f04bf234 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs @@ -59,6 +59,7 @@ public MethodProfileData(MethodDesc method, MethodProfilingDataFlags flags, doub public abstract class ProfileData { + public abstract MibcConfig Config { get; } public abstract bool PartialNGen { get; } public abstract MethodProfileData GetMethodProfileData(MethodDesc m); public abstract IEnumerable GetAllMethodProfileData(); @@ -131,6 +132,8 @@ private EmptyProfileData() { } + public override MibcConfig Config { get; } = new (); + public override bool PartialNGen => false; public static EmptyProfileData Singleton => s_singleton; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileData.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileData.cs index e028dbdcd993d4..266be8710eb0b8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileData.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileData.cs @@ -8,10 +8,45 @@ namespace ILCompiler.IBC { + public class MibcConfig + { + public string FormatVersion = "1.0"; + public string Os; + public string Arch; + public string Runtime; + + public override string ToString() + { + return + $""" + FormatVersion: {FormatVersion} + Runtime: {Runtime} + Os: {Os} + Arch: {Arch} + + """; + } + + public static MibcConfig FromKeyValueMap(Dictionary kvMap) + { + MibcConfig config = new(); + foreach (var kvPair in kvMap) + { + switch (kvPair.Key) + { + case nameof(FormatVersion): config.FormatVersion = kvPair.Value; break; + case nameof(Os): config.Os = kvPair.Value; break; + case nameof(Arch): config.Arch = kvPair.Value; break; + case nameof(Runtime): config.Runtime = kvPair.Value; break; + } + } + return config; + } + } public class IBCProfileData : ProfileData { - public IBCProfileData(bool partialNGen, IEnumerable methodData) + public IBCProfileData(MibcConfig config, bool partialNGen, IEnumerable methodData) { MethodProfileData[] dataArray = methodData.ToArray(); foreach (MethodProfileData data in dataArray) @@ -22,12 +57,16 @@ public IBCProfileData(bool partialNGen, IEnumerable methodDat } } _partialNGen = partialNGen; + _config = config; } private readonly Dictionary _methodData = new Dictionary(); private readonly bool _partialNGen; + private readonly MibcConfig _config; + + public override MibcConfig Config => _config; - public override bool PartialNGen { get { return _partialNGen; } } + public override bool PartialNGen => _partialNGen; public override MethodProfileData GetMethodProfileData(MethodDesc m) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileParser.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileParser.cs index 245c800a7cc4ee..b119f3bce5e4f7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileParser.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/IBCProfileParser.cs @@ -159,7 +159,7 @@ public ProfileData ParseIBCDataFromModule(EcmaModule ecmaModule) } } - return new IBCProfileData(parsedData.PartialNGen, methodProfileData); + return new IBCProfileData(null, parsedData.PartialNGen, methodProfileData); } public struct IBCBlobKey : IEquatable diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/MIbcProfileParser.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/MIbcProfileParser.cs index f0d7c5afc4100f..66dd82173e99dc 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/MIbcProfileParser.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IBC/MIbcProfileParser.cs @@ -219,7 +219,62 @@ public static ProfileData ParseMIbcFile(TypeSystemContext tsc, PEReader peReader } } - return new IBCProfileData(false, loadedMethodProfileData); + return new IBCProfileData(ParseMibcConfig(tsc, peReader), false, loadedMethodProfileData); + } + + public static MibcConfig ParseMibcConfig(TypeSystemContext tsc, PEReader pEReader) + { + EcmaModule mibcModule = EcmaModule.Create(tsc, pEReader, null); + EcmaMethod mibcConfigMth = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod(nameof(MibcConfig), null); + + if (mibcConfigMth == null) + return null; + + var ilBody = EcmaMethodIL.Create(mibcConfigMth); + var ilReader = new ILReader(ilBody.GetILBytes()); + + // Parse: + // + // ldstr "key1" + // ldstr "value1" + // pop + // pop + // ldstr "key2" + // ldstr "value2" + // pop + // pop + // ... + // ret + string fieldName = null; + Dictionary keyValue = new(); + while (ilReader.HasNext) + { + ILOpcode opcode = ilReader.ReadILOpcode(); + switch (opcode) + { + case ILOpcode.ldstr: + var ldStrValue = (string)ilBody.GetObject(ilReader.ReadILToken()); + if (fieldName != null) + { + keyValue[fieldName] = ldStrValue; + } + else + { + fieldName = ldStrValue; + } + break; + + case ILOpcode.ret: + case ILOpcode.pop: + fieldName = null; + break; + + default: + throw new InvalidOperationException($"Unexpected opcode: {opcode}"); + } + } + + return MibcConfig.FromKeyValueMap(keyValue); } enum MibcGroupParseState diff --git a/src/coreclr/tools/dotnet-pgo/MibcEmitter.cs b/src/coreclr/tools/dotnet-pgo/MibcEmitter.cs index e99fc610d86ca6..8491a25991c0ac 100644 --- a/src/coreclr/tools/dotnet-pgo/MibcEmitter.cs +++ b/src/coreclr/tools/dotnet-pgo/MibcEmitter.cs @@ -212,12 +212,45 @@ private static void AddAssembliesAssociatedWithMethod(MethodDesc method, HashSet } } - public static int GenerateMibcFile(TypeSystemContext tsc, FileInfo outputFileName, IEnumerable methodsToAttemptToPlaceIntoProfileData, bool validate, bool uncompressed) + // ... + /// + /// Emit a global method "MibcConfig" that will contain key-value settings in the following format: + /// ldstr "key1" + /// ldstr "value1" + /// pop + /// pop + /// ldstr "key2" + /// ldstr "value2" + /// pop + /// pop + /// ... + /// + public static void GenerateConfigData(MibcConfig config, TypeSystemMetadataEmitter emitter) + { + var buffer = new BlobBuilder(); + var il = new InstructionEncoder(buffer); + + foreach (FieldInfo mibcCfgField in typeof(MibcConfig).GetFields()) + { + Debug.Assert(!mibcCfgField.IsStatic && mibcCfgField.FieldType == typeof(string)); + il.LoadString(emitter.GetUserStringHandle(mibcCfgField.Name)); + il.LoadString(emitter.GetUserStringHandle((string)mibcCfgField.GetValue(config) ?? "")); + il.OpCode(ILOpCode.Pop); + il.OpCode(ILOpCode.Pop); + } + il.OpCode(ILOpCode.Ret); + emitter.AddGlobalMethod(nameof(MibcConfig), il, 8); + } + + public static int GenerateMibcFile(MibcConfig config, TypeSystemContext tsc, FileInfo outputFileName, IEnumerable methodsToAttemptToPlaceIntoProfileData, bool validate, bool uncompressed) { TypeSystemMetadataEmitter emitter = new TypeSystemMetadataEmitter(new AssemblyName(outputFileName.Name), tsc); emitter.InjectSystemPrivateCanon(); emitter.AllowUseOfAddGlobalMethod(); + if (config != null) + GenerateConfigData(config, emitter); + SortedDictionary groups = new SortedDictionary(); StringBuilder mibcGroupNameBuilder = new StringBuilder(); HashSet assembliesAssociatedWithMethod = new HashSet(); diff --git a/src/coreclr/tools/dotnet-pgo/Program.cs b/src/coreclr/tools/dotnet-pgo/Program.cs index c09e5406d88878..40e2572c9e316c 100644 --- a/src/coreclr/tools/dotnet-pgo/Program.cs +++ b/src/coreclr/tools/dotnet-pgo/Program.cs @@ -354,6 +354,38 @@ static int InnerDumpMain(CommandLineOptions commandLineOptions) return 0; } + static MibcConfig ParseMibcConfigsAndMerge(TypeSystemContext tsc, params PEReader[] pEReader) + { + MibcConfig firstCfg = null; + foreach (PEReader peReader in pEReader) + { + MibcConfig config = MIbcProfileParser.ParseMibcConfig(tsc, peReader); + if (firstCfg == null) + { + firstCfg = config; + } + else + { + if (firstCfg.Runtime != config.Runtime) + { + PrintMessage( + $"Warning: Attempting to merge MIBCs collected on different runtimes: {firstCfg.Runtime} != {config.Runtime}"); + } + if (firstCfg.FormatVersion != config.FormatVersion) + { + PrintMessage( + $"Warning: Attempting to merge MIBCs with different format versions: {firstCfg.FormatVersion} != {config.FormatVersion}"); + } + if (firstCfg.Os != config.Os || + firstCfg.Arch != config.Arch) + { + PrintMessage( + $"Warning: Attempting to merge MIBCs collected on different RIDs: {firstCfg.Os}-{firstCfg.Arch} != {config.Os}-{config.Arch}"); + } + } + } + return firstCfg; + } static int InnerMergeMain(CommandLineOptions commandLineOptions) { @@ -399,7 +431,8 @@ static int InnerMergeMain(CommandLineOptions commandLineOptions) ProfileData.MergeProfileData(ref partialNgen, mergedProfileData, MIbcProfileParser.ParseMIbcFile(tsc, peReader, assemblyNamesInBubble, onlyDefinedInAssembly: null)); } - int result = MibcEmitter.GenerateMibcFile(tsc, commandLineOptions.OutputFileName, mergedProfileData.Values, commandLineOptions.ValidateOutputFile, commandLineOptions.Uncompressed); + MibcConfig mergedConfig = ParseMibcConfigsAndMerge(tsc, mibcReaders); + int result = MibcEmitter.GenerateMibcFile(mergedConfig, tsc, commandLineOptions.OutputFileName, mergedProfileData.Values, commandLineOptions.ValidateOutputFile, commandLineOptions.Uncompressed); if (result == 0 && commandLineOptions.InheritTimestamp) { commandLineOptions.OutputFileName.CreationTimeUtc = commandLineOptions.InputFilesToMerge.Max(fi => fi.CreationTimeUtc); @@ -794,7 +827,8 @@ static void PrintLikelihoodHistogram(double[] likelihoods) static void PrintMibcStats(ProfileData data) { - List methods = data.GetAllMethodProfileData().ToList(); + PrintOutput(data.Config?.ToString()); + List methods = data.GetAllMethodProfileData().ToList(); List profiledMethods = methods.Where(spd => spd.SchemaData != null).ToList(); PrintOutput($"# Methods: {methods.Count}"); PrintOutput($"# Methods with any profile data: {profiledMethods.Count(spd => spd.SchemaData.Length > 0)}"); @@ -1731,13 +1765,24 @@ void AddToInstrumentationData(int eventClrInstanceId, long methodID, int methodF GenerateJittraceFile(commandLineOptions.OutputFileName, methodsUsedInProcess, commandLineOptions.JitTraceOptions); else if (commandLineOptions.FileType.Value == PgoFileType.mibc) { + var config = new MibcConfig(); + + // Look for OS and Arch, e.g. "Windows" and "x64" + TraceEvent processInfo = p.EventsInProcess.Filter(t => t.EventName == "ProcessInfo").FirstOrDefault(); + config.Os = processInfo?.PayloadByName("OSInformation")?.ToString(); + config.Arch = processInfo?.PayloadByName("ArchInformation")?.ToString(); + + // Look for Sku, e.g. "CoreClr" + TraceEvent runtimeStart = p.EventsInProcess.Filter(t => t.EventName == "Runtime/Start").FirstOrDefault(); + config.Runtime = runtimeStart?.PayloadByName("Sku")?.ToString(); + ILCompiler.MethodProfileData[] methodProfileData = new ILCompiler.MethodProfileData[methodsUsedInProcess.Count]; for (int i = 0; i < methodProfileData.Length; i++) { ProcessedMethodData processedData = methodsUsedInProcess[i]; methodProfileData[i] = new ILCompiler.MethodProfileData(processedData.Method, ILCompiler.MethodProfilingDataFlags.ReadMethodCode, processedData.ExclusiveWeight, processedData.WeightedCallData, 0xFFFFFFFF, processedData.InstrumentationData); } - return MibcEmitter.GenerateMibcFile(tsc, commandLineOptions.OutputFileName, methodProfileData, commandLineOptions.ValidateOutputFile, commandLineOptions.Uncompressed); + return MibcEmitter.GenerateMibcFile(config, tsc, commandLineOptions.OutputFileName, methodProfileData, commandLineOptions.ValidateOutputFile, commandLineOptions.Uncompressed); } } return 0; diff --git a/src/mono/mono/eventpipe/ep-rt-mono.c b/src/mono/mono/eventpipe/ep-rt-mono.c index 109ecb9e1ea926..9e9cd70dece9d3 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono.c +++ b/src/mono/mono/eventpipe/ep-rt-mono.c @@ -150,7 +150,7 @@ typedef struct _EventPipeSampleProfileStackWalkData { } EventPipeSampleProfileStackWalkData; // Rundown flags. -#define RUNTIME_SKU_CORECLR 0x2 +#define RUNTIME_SKU_MONO 0x4 #define METHOD_FLAGS_DYNAMIC_METHOD 0x1 #define METHOD_FLAGS_GENERIC_METHOD 0x2 #define METHOD_FLAGS_SHARED_GENERIC_METHOD 0x4 @@ -2801,7 +2801,7 @@ ep_rt_mono_execute_rundown (ep_rt_execution_checkpoint_array_t *execution_checkp FireEtwRuntimeInformationDCStart ( clr_instance_get_id (), - RUNTIME_SKU_CORECLR, + RUNTIME_SKU_MONO, RuntimeProductMajorVersion, RuntimeProductMinorVersion, RuntimeProductPatchVersion,