diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs
index 47976ee402a723..c0d107c821b902 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs
@@ -65,6 +65,11 @@ internal class ReadyToRunObjectWriter
///
private readonly SymbolFileBuilder _symbolFileBuilder;
+ ///
+ /// Set to non-null when generating callchain profile info.
+ ///
+ private readonly ProfileFileBuilder _profileFileBuilder;
+
///
/// True when the map file builder should emit a textual map file
///
@@ -130,6 +135,8 @@ public ReadyToRunObjectWriter(
string pdbPath,
bool generatePerfMapFile,
string perfMapPath,
+ bool generateProfileFile,
+ CallChainProfile callChainProfile,
int customPESectionAlignment)
{
_objectFilePath = objectFilePath;
@@ -147,7 +154,7 @@ public ReadyToRunObjectWriter(
bool generateMap = (generateMapFile || generateMapCsvFile);
bool generateSymbols = (generatePdbFile || generatePerfMapFile);
- if (generateMap || generateSymbols)
+ if (generateMap || generateSymbols || generateProfileFile)
{
_outputInfoBuilder = new OutputInfoBuilder();
@@ -160,6 +167,11 @@ public ReadyToRunObjectWriter(
{
_symbolFileBuilder = new SymbolFileBuilder(_outputInfoBuilder);
}
+
+ if (generateProfileFile)
+ {
+ _profileFileBuilder = new ProfileFileBuilder(_outputInfoBuilder, callChainProfile, _nodeFactory.Target);
+ }
}
}
@@ -348,6 +360,12 @@ public void EmitPortableExecutable()
}
_symbolFileBuilder.SavePerfMap(path, _objectFilePath);
}
+
+ if (_profileFileBuilder != null)
+ {
+ string path = Path.ChangeExtension(_objectFilePath, ".profile");
+ _profileFileBuilder.SaveProfile(path);
+ }
}
succeeded = true;
@@ -417,6 +435,8 @@ public static void EmitObject(
string pdbPath,
bool generatePerfMapFile,
string perfMapPath,
+ bool generateProfileFile,
+ CallChainProfile callChainProfile,
int customPESectionAlignment)
{
Console.WriteLine($@"Emitting R2R PE file: {objectFilePath}");
@@ -431,6 +451,8 @@ public static void EmitObject(
pdbPath: pdbPath,
generatePerfMapFile: generatePerfMapFile,
perfMapPath: perfMapPath,
+ generateProfileFile: generateProfileFile,
+ callChainProfile,
customPESectionAlignment);
objectWriter.EmitPortableExecutable();
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs
index eaf66f945dc673..97ae3665b2baa2 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs
@@ -323,7 +323,7 @@ private void WriteProfileParseStats()
Console.WriteLine($"Successfully resolved {_methodsSuccessfullyResolved} methods ({(double)_methodsSuccessfullyResolved / (double)_methodResolvesAttempted:P})");
}
#endif
- }
+ public IReadOnlyDictionary> ResolvedProfileData => _resolvedProfileData;
+ }
}
-
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs
index b8b6135432fa07..77cf87911bd356 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs
@@ -100,6 +100,7 @@ public class ProfileDataManager
private readonly HashSet _placedProfileMethodsAll = new HashSet();
private readonly bool _partialNGen;
private readonly ReadyToRunCompilationModuleGroupBase _compilationGroup;
+ private readonly CallChainProfile _callChainProfile;
public ProfileDataManager(Logger logger,
IEnumerable possibleReferenceModules,
@@ -107,11 +108,13 @@ public ProfileDataManager(Logger logger,
IEnumerable versionBubbleModules,
ModuleDesc nonLocalGenericsHome,
IReadOnlyList mibcFiles,
+ CallChainProfile callChainProfile,
CompilerTypeSystemContext context,
ReadyToRunCompilationModuleGroupBase compilationGroup)
{
_ibcParser = new IBCProfileParser(logger, possibleReferenceModules);
_compilationGroup = compilationGroup;
+ _callChainProfile = callChainProfile;
HashSet versionBubble = new HashSet(versionBubbleModules);
{
@@ -265,5 +268,7 @@ public MethodProfileData this[MethodDesc method]
return profileData;
}
}
+
+ public CallChainProfile CallChainProfile => _callChainProfile;
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
index ee73ed8fd611a3..7f42ede1c3cb7c 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
@@ -225,20 +225,21 @@ public sealed class ReadyToRunCodegenCompilation : Compilation
private readonly string _compositeRootPath;
- private bool _resilient;
+ private readonly bool _resilient;
- private int _parallelism;
+ private readonly int _parallelism;
- private bool _generateMapFile;
- private bool _generateMapCsvFile;
- private bool _generatePdbFile;
- private Func _printReproInstructions;
- private string _pdbPath;
- private bool _generatePerfMapFile;
- private string _perfMapPath;
+ private readonly bool _generateMapFile;
+ private readonly bool _generateMapCsvFile;
+ private readonly bool _generatePdbFile;
+ private readonly string _pdbPath;
+ private readonly bool _generatePerfMapFile;
+ private readonly string _perfMapPath;
+ private readonly bool _generateProfileFile;
+ private readonly Func _printReproInstructions;
- private ProfileDataManager _profileData;
- private ReadyToRunFileLayoutOptimizer _fileLayoutOptimizer;
+ private readonly ProfileDataManager _profileData;
+ private readonly ReadyToRunFileLayoutOptimizer _fileLayoutOptimizer;
public ProfileDataManager ProfileData => _profileData;
@@ -269,6 +270,7 @@ internal ReadyToRunCodegenCompilation(
string pdbPath,
bool generatePerfMapFile,
string perfMapPath,
+ bool generateProfileFile,
int parallelism,
ProfileDataManager profileData,
ReadyToRunMethodLayoutAlgorithm methodLayoutAlgorithm,
@@ -293,6 +295,7 @@ internal ReadyToRunCodegenCompilation(
_pdbPath = pdbPath;
_generatePerfMapFile = generatePerfMapFile;
_perfMapPath = perfMapPath;
+ _generateProfileFile = generateProfileFile;
_customPESectionAlignment = customPESectionAlignment;
SymbolNodeFactory = new ReadyToRunSymbolNodeFactory(nodeFactory, verifyTypeAndFieldLayout);
_corInfoImpls = new ConditionalWeakTable();
@@ -335,6 +338,8 @@ public override void Compile(string outputFile)
pdbPath: _pdbPath,
generatePerfMapFile: _generatePerfMapFile,
perfMapPath: _perfMapPath,
+ generateProfileFile: _generateProfileFile,
+ callChainProfile: _profileData.CallChainProfile,
_customPESectionAlignment);
CompilationModuleGroup moduleGroup = _nodeFactory.CompilationModuleGroup;
@@ -411,6 +416,8 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow
pdbPath: _pdbPath,
generatePerfMapFile: false,
perfMapPath: _perfMapPath,
+ generateProfileFile: _generateProfileFile,
+ _profileData.CallChainProfile,
customPESectionAlignment: 0);
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
index ba92a692c7d521..c01ef6e4863c36 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
@@ -29,6 +29,7 @@ public sealed class ReadyToRunCodegenCompilationBuilder : CompilationBuilder
private string _pdbPath;
private bool _generatePerfMapFile;
private string _perfMapPath;
+ private bool _generateProfileFile;
private int _parallelism;
Func _printReproInstructions;
private InstructionSetSupport _instructionSetSupport;
@@ -153,6 +154,12 @@ public ReadyToRunCodegenCompilationBuilder UsePerfMapFile(bool generatePerfMapFi
return this;
}
+ public ReadyToRunCodegenCompilationBuilder UseProfileFile(bool generateProfileFile)
+ {
+ _generateProfileFile = generateProfileFile;
+ return this;
+ }
+
public ReadyToRunCodegenCompilationBuilder UseParallelism(int parallelism)
{
_parallelism = parallelism;
@@ -282,6 +289,7 @@ public override ICompilation ToCompilation()
pdbPath: _pdbPath,
generatePerfMapFile: _generatePerfMapFile,
perfMapPath: _perfMapPath,
+ generateProfileFile: _generateProfileFile,
_parallelism,
_profileData,
_r2rMethodLayoutAlgorithm,
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
index 08d921f3ee2d79..2cbb68077b1a48 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
@@ -203,6 +203,7 @@
+
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs
index 00a73721531756..82145e37999dad 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs
@@ -225,6 +225,9 @@ private string FormatMethodName(MethodDesc method, TypeNameFormatter typeNameFor
public IReadOnlyList Sections => _sections;
public IReadOnlyList Symbols => _symbols;
+ public IReadOnlyDictionary NodeSymbolMap => _nodeSymbolMap;
+ public IReadOnlyDictionary MethodSymbolMap => _methodSymbolMap;
+
public IReadOnlyDictionary RelocCounts => _relocCounts;
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs
new file mode 100644
index 00000000000000..c35aac16c0c785
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs
@@ -0,0 +1,205 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Internal.TypeSystem;
+
+using ILCompiler.DependencyAnalysis;
+using ILCompiler.DependencyAnalysis.ReadyToRun;
+
+namespace ILCompiler.PEWriter
+{
+ ///
+ /// Helper class used to calculate code layout quality heuristics w.r.t. given call chain profile.
+ ///
+ public class ProfileFileBuilder
+ {
+ private enum CrossPageCall : byte
+ {
+ No,
+ Yes,
+ Unresolved,
+ }
+
+ private class CallInfo
+ {
+ public readonly MethodDesc Caller;
+ public readonly OutputNode CallerNode;
+ public readonly int CallerRVA;
+ public readonly MethodDesc Callee;
+ public readonly OutputNode CalleeNode;
+ public readonly int CalleeRVA;
+ public readonly int CallCount;
+ public readonly CrossPageCall CallType;
+
+ public CallInfo(MethodDesc caller, OutputNode callerNode, int callerRVA, MethodDesc callee, OutputNode calleeNode, int calleeRVA, int callCount, CrossPageCall callType)
+ {
+ Caller = caller;
+ CallerNode = callerNode;
+ CallerRVA = callerRVA;
+ Callee = callee;
+ CalleeNode = calleeNode;
+ CalleeRVA = calleeRVA;
+ CallCount = callCount;
+ CallType = callType;
+ }
+ }
+
+ private readonly OutputInfoBuilder _outputInfoBuilder;
+ private readonly CallChainProfile _callChainProfile;
+ private readonly TargetDetails _targetDetails;
+ private readonly int _pageSize;
+
+ private Dictionary _symbolMethodMap;
+ private List _callInfo;
+
+ public ProfileFileBuilder(OutputInfoBuilder outputInfoBuilder, CallChainProfile callChainProfile, TargetDetails targetDetails)
+ {
+ _outputInfoBuilder = outputInfoBuilder;
+ _callChainProfile = callChainProfile;
+ _targetDetails = targetDetails;
+ _pageSize = _targetDetails.Architecture switch
+ {
+ TargetArchitecture.X86 => 0x00001000,
+ TargetArchitecture.X64 => 0x00010000,
+ TargetArchitecture.ARM => 0x00001000,
+ TargetArchitecture.ARM64 => 0x00010000,
+ _ => throw new NotImplementedException(_targetDetails.Architecture.ToString())
+ };
+ }
+
+ public void SaveProfile(string profileFileName)
+ {
+ Console.WriteLine("Emitting profile file: {0}", profileFileName);
+
+ CalculateCallInfo();
+ using (StreamWriter writer = new StreamWriter(profileFileName))
+ {
+ writer.WriteLine("CHARACTERISTIC | PAIR COUNT | CALL COUNT | PERCENTAGE");
+ writer.WriteLine("----------------------------------------------------------------");
+ int callCount = _callInfo.Sum(info => info.CallCount);
+ double percentFactor = 100.0 / Math.Max(callCount, 1);
+ writer.WriteLine("ENTRIES TOTAL | {0,10} | {1,10} | {2,10:F2}", _callInfo.Count, callCount, callCount * percentFactor);
+ int resolvedPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Unresolved ? 0 : 1);
+ int resolvedCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Unresolved ? 0 : info.CallCount);
+ writer.WriteLine("RESOLVED ENTRIES | {0,10} | {1,10} | {2,10:F2}", resolvedPairCount, resolvedCallCount, resolvedCallCount * percentFactor);
+ int unresolvedPairCount = _callInfo.Count - resolvedPairCount;
+ int unresolvedCallCount = callCount - resolvedCallCount;
+ writer.WriteLine("UNRESOLVED ENTRIES | {0,10} | {1,10} | {2,10:F2}", unresolvedPairCount, unresolvedCallCount, unresolvedCallCount * percentFactor);
+ int nearPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.No ? 1 : 0);
+ int nearCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.No ? info.CallCount : 0);
+ writer.WriteLine("NEAR (INTRA-PAGE) CALLS | {0,10} | {1,10} | {2,10:F2}", nearPairCount, nearCallCount, nearCallCount * percentFactor);
+ int farPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Yes ? 1 : 0);
+ int farCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Yes ? info.CallCount : 0);
+ writer.WriteLine("FAR (CROSS-PAGE) CALLS | {0,10} | {1,10} | {2,10:F2}", farPairCount, farCallCount, farCallCount * percentFactor);
+
+ writer.WriteLine();
+ writer.WriteLine("CALLER RVA | CALLER LEN | CALLEE RVA | CALLEE LEN | COUNT | FAR (CROSS-PAGE) CALLS (CALLER -> CALLEE)");
+ writer.WriteLine("----------------------------------------------------------------------------------------------------------");
+ DumpCallInfo(writer, _callInfo.Where(info => info.CallType == CrossPageCall.Yes).OrderByDescending(info => info.CallCount));
+
+ writer.WriteLine();
+ writer.WriteLine("CALLER RVA | CALLER LEN | CALLEE RVA | CALLEE LEN | COUNT | NEAR (INTRA-PAGE) CALLS (CALLER -> CALLEE)");
+ writer.WriteLine("-----------------------------------------------------------------------------------------------------------");
+ DumpCallInfo(writer, _callInfo.Where(info => info.CallType == CrossPageCall.No).OrderByDescending(info => info.CallCount));
+ }
+ }
+
+ private void DumpCallInfo(StreamWriter writer, IEnumerable callInfos)
+ {
+ foreach (CallInfo callInfo in callInfos)
+ {
+ writer.Write($@"{callInfo.CallerRVA,10:X8} | ");
+ writer.Write($@"{callInfo.CallerNode.Length,10:X8} | ");
+ writer.Write($@"{callInfo.CalleeRVA,10:X8} | ");
+ writer.Write($@"{callInfo.CalleeNode.Length,10:X8} | ");
+ writer.Write($@"{callInfo.CallCount,10} | ");
+ writer.WriteLine($@"{callInfo.Caller.ToString()} -> {callInfo.Callee.ToString()}");
+ }
+ }
+
+ private void CalculateSymbolMethodMap()
+ {
+ if (_symbolMethodMap != null)
+ {
+ // Already calculated
+ return;
+ }
+ _symbolMethodMap = new Dictionary();
+ foreach (KeyValuePair kvpSymbolMethod in _outputInfoBuilder.MethodSymbolMap)
+ {
+ _symbolMethodMap.Add(kvpSymbolMethod.Value.Method, kvpSymbolMethod.Key);
+ }
+ }
+
+ private void CalculateCallInfo()
+ {
+ if (_callInfo != null)
+ {
+ // Already calculated
+ return;
+ }
+
+ CalculateSymbolMethodMap();
+
+ _callInfo = new List();
+ foreach (KeyValuePair> kvpCallerCalleeCount in _callChainProfile.ResolvedProfileData)
+ {
+ OutputNode callerNode = null;
+ int callerRVA = 0;
+ if (_symbolMethodMap.TryGetValue(kvpCallerCalleeCount.Key, out ISymbolDefinitionNode callerSymbol) &&
+ _outputInfoBuilder.NodeSymbolMap.TryGetValue(callerSymbol, out callerNode))
+ {
+ callerRVA = _outputInfoBuilder.Sections[callerNode.SectionIndex].RVAWhenPlaced + callerNode.Offset;
+ }
+
+ foreach (KeyValuePair kvpCalleeCount in kvpCallerCalleeCount.Value)
+ {
+ OutputNode calleeNode = null;
+ int calleeRVA = 0;
+ if (_symbolMethodMap.TryGetValue(kvpCalleeCount.Key, out ISymbolDefinitionNode calleeSymbol) &&
+ _outputInfoBuilder.NodeSymbolMap.TryGetValue(calleeSymbol, out calleeNode))
+ {
+ calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].RVAWhenPlaced + calleeNode.Offset;
+ }
+
+ _callInfo.Add(new CallInfo(
+ caller: kvpCallerCalleeCount.Key,
+ callerNode: callerNode,
+ callerRVA: callerRVA,
+ callee: kvpCalleeCount.Key,
+ calleeNode: calleeNode,
+ calleeRVA: calleeRVA,
+ callCount: kvpCalleeCount.Value,
+ callType: GetCallType(callerNode, callerRVA, calleeNode, calleeRVA)));
+ }
+ }
+ }
+
+ private CrossPageCall GetCallType(OutputNode caller, int callerRVA, OutputNode callee, int calleeRVA)
+ {
+ if (caller == null || callee == null)
+ {
+ return CrossPageCall.Unresolved;
+ }
+ int callerStartPage = callerRVA / _pageSize;
+ int callerEndPage = (callerRVA + caller.Length - 1) / _pageSize;
+ int calleePage = calleeRVA / _pageSize;
+
+ if (callerStartPage == calleePage && callerEndPage == calleePage)
+ {
+ // The entire caller and the callee entrypoint are on the same page, no cross-page call penalty
+ return CrossPageCall.No;
+ }
+
+ // Pessimistic estimate - we don't know where exactly the call is, we just know that it might cross a page.
+ return CrossPageCall.Yes;
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/crossgen2.sln b/src/coreclr/tools/aot/crossgen2.sln
index cb78b75b95945d..ac541c0a78d16a 100644
--- a/src/coreclr/tools/aot/crossgen2.sln
+++ b/src/coreclr/tools/aot/crossgen2.sln
@@ -112,8 +112,8 @@ Global
{3EACD929-4725-4173-A845-734936BBDF87}.Checked|x86.Build.0 = Debug|x86
{3EACD929-4725-4173-A845-734936BBDF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3EACD929-4725-4173-A845-734936BBDF87}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.ActiveCfg = Debug|Any CPU
- {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.Build.0 = Debug|Any CPU
+ {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.ActiveCfg = Debug|x64
+ {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.Build.0 = Debug|x64
{3EACD929-4725-4173-A845-734936BBDF87}.Debug|x86.ActiveCfg = Debug|Any CPU
{3EACD929-4725-4173-A845-734936BBDF87}.Debug|x86.Build.0 = Debug|Any CPU
{3EACD929-4725-4173-A845-734936BBDF87}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs
index 0a1a462a447807..3b51b9b3cb1250 100644
--- a/src/coreclr/tools/aot/crossgen2/Program.cs
+++ b/src/coreclr/tools/aot/crossgen2/Program.cs
@@ -540,6 +540,7 @@ private int Run(string[] args)
versionBubbleModules,
_commandLineOptions.CompileBubbleGenerics ? inputModules[0] : null,
mibcFiles,
+ jsonProfile,
_typeSystemContext,
compilationGroup);
@@ -593,6 +594,7 @@ private int Run(string[] args)
.UseMapCsvFile(_commandLineOptions.MapCsv)
.UsePdbFile(_commandLineOptions.Pdb, _commandLineOptions.PdbPath)
.UsePerfMapFile(_commandLineOptions.PerfMap, _commandLineOptions.PerfMapPath)
+ .UseProfileFile(jsonProfile != null)
.UseParallelism(_commandLineOptions.Parallelism)
.UseProfileData(profileDataManager)
.FileLayoutAlgorithms(_methodLayout, _fileLayout)