mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
introduce a common superclass, SILNode. This is in preparation for allowing instructions to have multiple results. It is also a somewhat more elegant representation for instructions that have zero results. Instructions that are known to have exactly one result inherit from a class, SingleValueInstruction, that subclasses both ValueBase and SILInstruction. Some care must be taken when working with SILNode pointers and testing for equality; please see the comment on SILNode for more information. A number of SIL passes needed to be updated in order to handle this new distinction between SIL values and SIL instructions. Note that the SIL parser is now stricter about not trying to assign a result value from an instruction (like 'return' or 'strong_retain') that does not produce any.
982 lines
34 KiB
C++
982 lines
34 KiB
C++
//===-- OptimizerStatsUtils.cpp - Utils for collecting stats -*- C++ ---*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
///
|
|
/// This file implements collection and dumping of statistics about SILModules,
|
|
/// SILFunctions and memory consumption during the execution of SIL
|
|
/// optimization pipelines.
|
|
///
|
|
/// The following statistics are collected:
|
|
/// - For SILFunctions: the number of SIL basic blocks, the number of SIL
|
|
/// instructions
|
|
///
|
|
/// - For SILModules: the number of SIL basic blocks, the number of SIL
|
|
/// instructions, the number of SILFunctions, the amount of memory used by the
|
|
/// compiler.
|
|
///
|
|
/// By default, any collection of statistics is disabled to avoid affecting
|
|
/// compile times.
|
|
///
|
|
/// One can enable the collection of statistics and dumping of these statistics
|
|
/// for the whole SILModule and/or for SILFunctions.
|
|
///
|
|
/// To reduce the amount of produced data, one can set thresholds in such a way
|
|
/// that changes in the statistics are only reported if the delta between the
|
|
/// old and the new values are at least X%. The deltas are computed using the
|
|
/// following formula:
|
|
/// Delta = (NewValue - OldValue) / OldValue
|
|
///
|
|
/// Thresholds provide a simple way to perform a simple filtering of the
|
|
/// collected statistics during the compilation. But if there is a need for a
|
|
/// more complex analysis of collected data (e.g. aggregation by a pipeline
|
|
/// stage or by the type of a transformation), it is often better to dump
|
|
/// as much data as possible into a file using e.g. `-sil-stats-dump-all
|
|
/// -sil-stats-modules -sil-stats-functions` and then e.g. use the helper scripts
|
|
/// to store the collected data into a database and then perform complex queries
|
|
/// on it. Many kinds of analysis can be then formulated pretty easily as SQL
|
|
/// queries.
|
|
///
|
|
/// The output format is a set of CSV (comma separated values) lines. Each lines
|
|
/// represents one counter or one counter change.
|
|
///
|
|
/// For counter updates it looks like this:
|
|
/// Kind, CounterName, StageName, TransformName, TransformPassNumber,
|
|
/// DeltaValue, OldCounterValue, NewCounterValue, Duration, Symbol
|
|
///
|
|
/// For counter updates it looks like this:
|
|
/// Kind, CounterName, StageName, TransformName, TransformPassNumber,
|
|
/// CounterValue, Duration, Symbol
|
|
///
|
|
/// where Kind is one of "function", "module", "function_history",
|
|
/// CounterName is one of "block", "inst", "function", "memory",
|
|
/// Symbol is e.g. the name of a function.
|
|
/// StageName and TransformName are the names of the current optimizer
|
|
/// pipeline stage and current transform.
|
|
/// Duration is the duration of the transformation.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Process.h"
|
|
#include "swift/SIL/SILValue.h"
|
|
#include "swift/SIL/SILVisitor.h"
|
|
#include "swift/SILOptimizer/Analysis/Analysis.h"
|
|
#include "swift/SILOptimizer/PassManager/PassManager.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/OptimizerStatsUtils.h"
|
|
using namespace swift;
|
|
|
|
namespace {
|
|
|
|
/// The total number of different kinds of SILInstructions.
|
|
constexpr unsigned NumSILInstructions =
|
|
unsigned(SILNodeKind::Last_SILInstruction)
|
|
- unsigned(SILNodeKind::First_SILInstruction)
|
|
+ 1;
|
|
|
|
static unsigned getIndexForKind(SILInstructionKind kind) {
|
|
return unsigned(kind) - unsigned(SILNodeKind::First_SILInstruction);
|
|
}
|
|
|
|
/// A set of counters, one per SILInstruction kind.
|
|
class InstructionCounts {
|
|
unsigned Counts[NumSILInstructions] = {};
|
|
|
|
public:
|
|
constexpr InstructionCounts() {}
|
|
|
|
unsigned &operator[](SILInstructionKind kind) {
|
|
return Counts[getIndexForKind(kind)];
|
|
}
|
|
|
|
void addAll(const InstructionCounts &other) {
|
|
for (unsigned i = 0; i != NumSILInstructions; ++i) {
|
|
Counts[i] += other.Counts[i];
|
|
}
|
|
}
|
|
|
|
void subAll(const InstructionCounts &other) {
|
|
for (unsigned i = 0; i != NumSILInstructions; ++i) {
|
|
Counts[i] -= other.Counts[i];
|
|
}
|
|
}
|
|
};
|
|
|
|
/// A helper type to parse a comma separated list of SIL instruction names
|
|
/// provided as argument of the -sil-stats-only-instructions options.
|
|
class StatsOnlyInstructionsOpt {
|
|
bool ShouldComputeInstCounts[NumSILInstructions] = {};
|
|
|
|
/// The number of different kinds of SILInstructions to be tracked.
|
|
unsigned NumInstCounts = 0;
|
|
|
|
public:
|
|
constexpr StatsOnlyInstructionsOpt() {}
|
|
|
|
void operator=(StringRef val) {
|
|
if (val.empty())
|
|
return;
|
|
if (val == "all") {
|
|
for (auto &inst : ShouldComputeInstCounts) {
|
|
inst = true;
|
|
}
|
|
NumInstCounts = NumSILInstructions;
|
|
return;
|
|
}
|
|
SmallVector<StringRef, 8> statsInstNames;
|
|
val.split(statsInstNames, ',', -1, false);
|
|
for (auto instName : statsInstNames) {
|
|
// Check if it is a known instruction.
|
|
auto kind = getSILInstructionKind(instName);
|
|
unsigned index = getIndexForKind(kind);
|
|
if (!ShouldComputeInstCounts[index]) {
|
|
ShouldComputeInstCounts[index] = true;
|
|
NumInstCounts++;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool shouldComputeInstCount(SILInstructionKind kind) const {
|
|
return ShouldComputeInstCounts[getIndexForKind(kind)];
|
|
}
|
|
|
|
int getInstCountsNum() const {
|
|
return NumInstCounts;
|
|
}
|
|
};
|
|
|
|
StatsOnlyInstructionsOpt StatsOnlyInstructionsOptLoc;
|
|
|
|
/// Use this option like -Xllvm -sil-stats-only-instructions=strong_retain,alloc_stack
|
|
/// If you need to track all kinds of SILInstructions, you can use
|
|
/// -sil-stats-only-instructions=all
|
|
llvm::cl::opt<StatsOnlyInstructionsOpt, true, llvm::cl::parser<std::string>>
|
|
StatsOnlyInstructions(
|
|
"sil-stats-only-instructions",
|
|
llvm::cl::desc(
|
|
"Comma separated list of SIL insruction names, whose stats "
|
|
"should be collected"),
|
|
llvm::cl::Hidden, llvm::cl::ZeroOrMore,
|
|
llvm::cl::value_desc("instruction name"),
|
|
llvm::cl::location(StatsOnlyInstructionsOptLoc),
|
|
llvm::cl::ValueRequired);
|
|
|
|
/// Dump as much statistics as possible, ignore any thresholds.
|
|
llvm::cl::opt<bool> SILStatsDumpAll(
|
|
"sil-stats-dump-all", llvm::cl::init(false),
|
|
llvm::cl::desc("Dump all SIL module and SIL function stats"));
|
|
|
|
/// Dump statistics about the SILModule.
|
|
llvm::cl::opt<bool> SILStatsModules(
|
|
"sil-stats-modules", llvm::cl::init(false),
|
|
llvm::cl::desc("Enable computation of statistics for SIL modules"));
|
|
|
|
/// Dump statistics about SILFunctions.
|
|
llvm::cl::opt<bool> SILStatsFunctions(
|
|
"sil-stats-functions", llvm::cl::init(false),
|
|
llvm::cl::desc("Enable computation of statistics for SIL functions"));
|
|
|
|
/// The name of the output file for optimizer counters.
|
|
llvm::cl::opt<std::string> SILStatsOutputFile(
|
|
"sil-stats-output-file", llvm::cl::init(""),
|
|
llvm::cl::desc("The name of the output file for optimizer counters"));
|
|
|
|
/// A threshold in percents for the SIL basic block counters.
|
|
llvm::cl::opt<double> BlockCountDeltaThreshold(
|
|
"sil-stats-block-count-delta-threshold", llvm::cl::init(1),
|
|
llvm::cl::desc(
|
|
"Threshold for reporting changed basic block count numbers"));
|
|
|
|
/// A threshold in percents for the SIL instructions counters.
|
|
llvm::cl::opt<double> InstCountDeltaThreshold(
|
|
"sil-stats-inst-count-delta-threshold", llvm::cl::init(1),
|
|
llvm::cl::desc(
|
|
"Threshold for reporting changed instruction count numbers"));
|
|
|
|
/// A threshold in percents for the SIL functions counters.
|
|
llvm::cl::opt<double> FunctionCountDeltaThreshold(
|
|
"sil-stats-function-count-delta-threshold", llvm::cl::init(1),
|
|
llvm::cl::desc("Threshold for reporting changed function count numbers"));
|
|
|
|
/// A threshold in percents for the counters of memory used by the compiler.
|
|
llvm::cl::opt<double> UsedMemoryDeltaThreshold(
|
|
"sil-stats-used-memory-delta-threshold", llvm::cl::init(5),
|
|
llvm::cl::desc("Threshold for reporting changed memory usage numbers"));
|
|
|
|
llvm::cl::opt<double> UsedMemoryMinDeltaThreshold(
|
|
"sil-stats-used-memory-min-threshold", llvm::cl::init(1),
|
|
llvm::cl::desc("Min hreshold for reporting changed memory usage numbers"));
|
|
|
|
/// A threshold in percents for the basic blocks counter inside a SILFunction.
|
|
/// Has effect only if it is used together with -sil-stats-functions.
|
|
llvm::cl::opt<double> FuncBlockCountDeltaThreshold(
|
|
"sil-stats-func-block-count-delta-threshold", llvm::cl::init(200),
|
|
llvm::cl::desc("Threshold for reporting changed basic block count numbers "
|
|
"for a function"));
|
|
|
|
/// A minimal threshold (in number of basic blocks) for the basic blocks counter
|
|
/// inside a SILFunction. Only functions with more basic blocks than this
|
|
/// threshold are reported. Has effect only if it is used together with
|
|
/// -sil-stats-functions.
|
|
llvm::cl::opt<int> FuncBlockCountMinThreshold(
|
|
"sil-stats-func-block-count-min-threshold", llvm::cl::init(50),
|
|
llvm::cl::desc(
|
|
"Min threshold for reporting changed basic block count numbers "
|
|
"for a function"));
|
|
|
|
/// A threshold in percents for the SIL instructions counter inside a
|
|
/// SILFunction. Has effect only if it is used together with
|
|
/// -sil-stats-functions.
|
|
llvm::cl::opt<double> FuncInstCountDeltaThreshold(
|
|
"sil-stats-func-inst-count-delta-threshold", llvm::cl::init(200),
|
|
llvm::cl::desc("Threshold for reporting changed instruction count numbers "
|
|
"for a function"));
|
|
|
|
/// A minimal threshold (in number of instructions) for the SIL instructions
|
|
/// counter inside a SILFunction. Only functions with more instructions than
|
|
/// this threshold are reported. Has effect only if it is used together with
|
|
/// -sil-stats-functions.
|
|
llvm::cl::opt<int> FuncInstCountMinThreshold(
|
|
"sil-stats-func-inst-count-min-threshold", llvm::cl::init(300),
|
|
llvm::cl::desc(
|
|
"Min threshold for reporting changed instruction count numbers "
|
|
"for a function"));
|
|
|
|
/// Track only statistics for a function with a given name.
|
|
/// Has effect only if it is used together with -sil-stats-functions.
|
|
llvm::cl::opt<std::string> StatsOnlyFunctionName(
|
|
"sil-stats-only-function", llvm::cl::init(""),
|
|
llvm::cl::desc("Function name, whose stats should be tracked"));
|
|
|
|
/// Track only statistics for a function whose name contains a given substring.
|
|
/// Has effect only if it is used together with -sil-stats-functions.
|
|
llvm::cl::opt<std::string> StatsOnlyFunctionsNamePattern(
|
|
"sil-stats-only-functions", llvm::cl::init(""),
|
|
llvm::cl::desc(
|
|
"Pattern of a function name, whose stats should be tracked"));
|
|
|
|
/// Stats for a SIL function.
|
|
struct FunctionStat {
|
|
int BlockCount = 0;
|
|
int InstCount = 0;
|
|
/// Instruction counts per SILInstruction kind.
|
|
InstructionCounts InstCounts;
|
|
|
|
FunctionStat(SILFunction *F);
|
|
FunctionStat() {}
|
|
|
|
void print(llvm::raw_ostream &stream) const {
|
|
stream << "FunctionStat("
|
|
<< "blocks = " << BlockCount << ", Inst = " << InstCount << ")\n";
|
|
}
|
|
|
|
bool operator==(const FunctionStat &rhs) const {
|
|
return BlockCount == rhs.BlockCount && InstCount == rhs.InstCount;
|
|
}
|
|
|
|
bool operator!=(const FunctionStat &rhs) const { return !(*this == rhs); }
|
|
|
|
void dump() { print(llvm::errs()); }
|
|
};
|
|
|
|
/// Stats a single SIL module.
|
|
struct ModuleStat {
|
|
/// Total number of SILFunctions in the current SILModule.
|
|
int FunctionCount = 0;
|
|
/// Total number of SILBasicBlocks in the current SILModule.
|
|
int BlockCount = 0;
|
|
/// Total number of SILInstructions in the current SILModule.
|
|
int InstCount = 0;
|
|
/// Total amount of memory used by the compiler.
|
|
int UsedMemory = 0;
|
|
/// Total number of SILInstructions created since the beginning of the current
|
|
/// compilation.
|
|
int CreatedInstCount = 0;
|
|
/// Total number of SILInstructions deleted since the beginning of the current
|
|
/// compilation.
|
|
int DeletedInstCount = 0;
|
|
/// Instruction counts per SILInstruction kind.
|
|
InstructionCounts InstCounts;
|
|
|
|
ModuleStat() {}
|
|
|
|
/// Add the stats for a given function to the total module stats.
|
|
void addFunctionStat(FunctionStat &Stat) {
|
|
BlockCount += Stat.BlockCount;
|
|
InstCount += Stat.InstCount;
|
|
++FunctionCount;
|
|
if (!StatsOnlyInstructionsOptLoc.getInstCountsNum())
|
|
return;
|
|
InstCounts.addAll(Stat.InstCounts);
|
|
}
|
|
|
|
/// Subtract the stats for a given function from total module stats.
|
|
void subFunctionStat(FunctionStat &Stat) {
|
|
BlockCount -= Stat.BlockCount;
|
|
InstCount -= Stat.InstCount;
|
|
--FunctionCount;
|
|
if (!StatsOnlyInstructionsOptLoc.getInstCountsNum())
|
|
return;
|
|
InstCounts.subAll(Stat.InstCounts);
|
|
}
|
|
|
|
/// Add the stats about current memory usage.
|
|
void addMemoryStat() {
|
|
UsedMemory = llvm::sys::Process::GetMallocUsage();
|
|
}
|
|
|
|
/// Add the stats about created and deleted instructions.
|
|
void addCreatedAndDeletedInstructionsStat() {
|
|
CreatedInstCount = SILInstruction::getNumCreatedInstructions();
|
|
DeletedInstCount = SILInstruction::getNumDeletedInstructions();
|
|
}
|
|
|
|
void print(llvm::raw_ostream &stream) const {
|
|
stream << "ModuleStat(functions = " << FunctionCount
|
|
<< ", blocks = " << BlockCount << ", Inst = " << InstCount
|
|
<< ", UsedMemory = " << UsedMemory / (1024 * 1024)
|
|
<< ", CreatedInst = " << CreatedInstCount
|
|
<< ", DeletedInst = " << DeletedInstCount
|
|
<< ")\n";
|
|
}
|
|
|
|
void dump() { print(llvm::errs()); }
|
|
|
|
bool operator==(const ModuleStat &rhs) const {
|
|
return FunctionCount == rhs.FunctionCount && BlockCount == rhs.BlockCount &&
|
|
InstCount == rhs.InstCount && UsedMemory == rhs.UsedMemory;
|
|
}
|
|
|
|
bool operator!=(const ModuleStat &rhs) const { return !(*this == rhs); }
|
|
};
|
|
|
|
// A helper type to collect the stats about the number of instructions and basic
|
|
// blocks.
|
|
struct InstCountVisitor : SILInstructionVisitor<InstCountVisitor> {
|
|
int BlockCount = 0;
|
|
int InstCount = 0;
|
|
InstructionCounts &InstCounts;
|
|
|
|
InstCountVisitor(InstructionCounts &InstCounts) : InstCounts(InstCounts) {}
|
|
|
|
int getBlockCount() const {
|
|
return BlockCount;
|
|
}
|
|
|
|
int getInstCount() const {
|
|
return InstCount;
|
|
}
|
|
|
|
void visitSILBasicBlock(SILBasicBlock *BB) {
|
|
++BlockCount;
|
|
SILInstructionVisitor<InstCountVisitor>::visitSILBasicBlock(BB);
|
|
}
|
|
|
|
void visit(SILInstruction *I) {
|
|
++InstCount;
|
|
++InstCounts[I->getKind()];
|
|
}
|
|
};
|
|
|
|
/// A helper type to store different parameters related to the current transform.
|
|
class TransformationContext {
|
|
/// SILModule being processed.
|
|
SILModule &M;
|
|
/// The pass manager being used.
|
|
SILPassManager &PM;
|
|
/// The transformation that was/will be performed.
|
|
SILTransform *Transform;
|
|
/// The time it took to perform the transformation.
|
|
int Duration;
|
|
/// The pass number in the optimizer pipeline.
|
|
int PassNumber;
|
|
|
|
public:
|
|
TransformationContext(SILModule &M, SILPassManager &PM,
|
|
SILTransform *Transform, int PassNumber, int Duration)
|
|
: M(M), PM(PM), Transform(Transform), Duration(Duration),
|
|
PassNumber(PassNumber) {}
|
|
|
|
int getPassNumber() const {
|
|
return PassNumber;
|
|
}
|
|
|
|
int getDuration() const {
|
|
return Duration;
|
|
}
|
|
|
|
StringRef getTransformId() const {
|
|
return Transform->getID();
|
|
}
|
|
|
|
StringRef getStageName() const {
|
|
return PM.getStageName();
|
|
}
|
|
|
|
SILModule &getModule() {
|
|
return M;
|
|
}
|
|
|
|
SILPassManager &getPassManager() {
|
|
return PM;
|
|
}
|
|
};
|
|
|
|
/// Aggregated statistics for the whole SILModule and all SILFunctions belonging
|
|
/// to it.
|
|
class AccumulatedOptimizerStats {
|
|
using FunctionStats = llvm::DenseMap<const SILFunction *, FunctionStat>;
|
|
|
|
/// Current stats for each function.
|
|
FunctionStats FuncStats;
|
|
/// Current stats for the module.
|
|
ModuleStat ModStat;
|
|
|
|
public:
|
|
FunctionStat &getFunctionStat(const SILFunction *F) {
|
|
return FuncStats[F];
|
|
}
|
|
|
|
ModuleStat &getModuleStat() {
|
|
return ModStat;
|
|
}
|
|
|
|
void deleteFunctionStat(SILFunction *F) {
|
|
FuncStats.erase(F);
|
|
}
|
|
};
|
|
|
|
/// A helper class to repesent the module stats as an analysis,
|
|
/// so that it is preserved across multiple passes.
|
|
class OptimizerStatsAnalysis : public SILAnalysis {
|
|
SILModule &M;
|
|
/// The actual cache holding all the statistics.
|
|
std::unique_ptr<AccumulatedOptimizerStats> Cache;
|
|
|
|
/// Sets of functions changed, deleted or added since the last
|
|
/// computation of statistics. These sets are used to avoid complete
|
|
/// re-computation of the stats for the whole module. Instead only
|
|
/// re-compute the stats for the changed functions, which is much
|
|
/// faster.
|
|
SmallVector<SILFunction *, 16> InvalidatedFuncs;
|
|
SmallVector<SILFunction *, 16> DeletedFuncs;
|
|
SmallVector<SILFunction *, 16> AddedFuncs;
|
|
|
|
public:
|
|
OptimizerStatsAnalysis(SILModule *M)
|
|
: SILAnalysis(AnalysisKind::OptimizerStats), M(*M), Cache(nullptr) {}
|
|
|
|
static bool classof(const SILAnalysis *S) {
|
|
return S->getKind() == AnalysisKind::OptimizerStats;
|
|
}
|
|
|
|
/// Invalidate all information in this analysis.
|
|
virtual void invalidate() override {
|
|
// This analysis is never invalidated, because it just used
|
|
// to store the statistics.
|
|
}
|
|
|
|
/// Invalidate all of the information for a specific function.
|
|
virtual void invalidate(SILFunction *F, InvalidationKind K) override {
|
|
InvalidatedFuncs.push_back(F);
|
|
}
|
|
|
|
/// Notify the analysis about a newly created function.
|
|
virtual void notifyAddFunction(SILFunction *F) override {
|
|
AddedFuncs.push_back(F);
|
|
}
|
|
|
|
/// Notify the analysis about a function which will be deleted from the
|
|
/// module.
|
|
virtual void notifyDeleteFunction(SILFunction *F) override {
|
|
DeletedFuncs.push_back(F);
|
|
};
|
|
|
|
/// Notify the analysis about changed witness or vtables.
|
|
virtual void invalidateFunctionTables() override {
|
|
}
|
|
|
|
|
|
SILModule &getModule() const {
|
|
return M;
|
|
}
|
|
|
|
/// Get the collected statistics for a function.
|
|
FunctionStat &getFunctionStat(const SILFunction *F) {
|
|
if (!Cache)
|
|
Cache = llvm::make_unique<AccumulatedOptimizerStats>();
|
|
|
|
return Cache->getFunctionStat(F);
|
|
}
|
|
|
|
/// Get the collected statistics for a module.
|
|
ModuleStat &getModuleStat() {
|
|
if (!Cache)
|
|
Cache = llvm::make_unique<AccumulatedOptimizerStats>();
|
|
|
|
return Cache->getModuleStat();
|
|
}
|
|
|
|
/// Update module stats after running the Transform.
|
|
void updateModuleStats(TransformationContext &Ctx);
|
|
};
|
|
|
|
class NewLineInserter {
|
|
bool isNewline = true;
|
|
|
|
public:
|
|
NewLineInserter() {}
|
|
StringRef get() {
|
|
StringRef result = isNewline ? "\n" : "";
|
|
isNewline = false;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
/// The output stream to be used for writing the collected statistics.
|
|
/// Use the unique_ptr to ensure that the file is properly closed upon
|
|
/// exit.
|
|
std::unique_ptr<llvm::raw_ostream, std::function<void(llvm::raw_ostream *)>>
|
|
stats_output_stream;
|
|
|
|
/// Return the output streamm to be used for logging the collected statistics.
|
|
llvm::raw_ostream &stats_os() {
|
|
// Initialize the stream if it is not initialized yet.
|
|
if (!stats_output_stream) {
|
|
// If there is user-defined output file name, use it.
|
|
if (!SILStatsOutputFile.empty()) {
|
|
// Try to open the file.
|
|
std::error_code EC;
|
|
llvm::raw_fd_ostream *fd_stream = new llvm::raw_fd_ostream(
|
|
SILStatsOutputFile, EC, llvm::sys::fs::OpenFlags::F_Text);
|
|
if (!fd_stream->has_error() && !EC) {
|
|
stats_output_stream = {fd_stream,
|
|
[](llvm::raw_ostream *d) { delete d; }};
|
|
return *stats_output_stream.get();
|
|
}
|
|
fd_stream->clear_error();
|
|
llvm::errs() << SILStatsOutputFile << " : " << EC.message() << "\n";
|
|
delete fd_stream;
|
|
}
|
|
// Otherwise use llvm::errs() as output. No need to destroy it at the end.
|
|
stats_output_stream = {&llvm::errs(), [](llvm::raw_ostream *d) {}};
|
|
}
|
|
return *stats_output_stream.get();
|
|
}
|
|
|
|
/// A helper function to dump the counter value.
|
|
void printCounterValue(StringRef Kind, StringRef CounterName, int CounterValue,
|
|
StringRef Symbol, TransformationContext &Ctx) {
|
|
stats_os() << Kind;
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << CounterName;
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getStageName();
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getTransformId();
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getPassNumber();
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << CounterValue;
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getDuration();
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Symbol;
|
|
stats_os() << "\n";
|
|
}
|
|
|
|
/// A helper function to dump the change of the counter value.
|
|
void printCounterChange(StringRef Kind, StringRef CounterName, double Delta,
|
|
int OldValue, int NewValue, TransformationContext &Ctx,
|
|
StringRef Symbol = "") {
|
|
stats_os() << Kind;
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << CounterName;
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getStageName();
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getTransformId();
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getPassNumber();
|
|
stats_os() << ", ";
|
|
|
|
llvm::format_provider<double>::format(Delta, stats_os(), "f8");
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << OldValue;
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << NewValue;
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Ctx.getDuration();
|
|
stats_os() << ", ";
|
|
|
|
stats_os() << Symbol;
|
|
stats_os() << "\n";
|
|
}
|
|
|
|
/// Check if a function name matches the pattern provided by the user.
|
|
bool isMatchingFunction(SILFunction *F, bool shouldHaveNamePattern = false) {
|
|
auto FuncName = F->getName();
|
|
// Is it an exact string match?
|
|
if (!StatsOnlyFunctionName.empty())
|
|
return F->getName() == StatsOnlyFunctionName;
|
|
|
|
// Does the name contain a user-defined substring?
|
|
if (!StatsOnlyFunctionsNamePattern.empty()) {
|
|
return FuncName.contains(StatsOnlyFunctionsNamePattern);
|
|
}
|
|
|
|
return shouldHaveNamePattern ? true : false;
|
|
}
|
|
|
|
/// Compute the delta between the old and new values.
|
|
/// Return it as a percent value.
|
|
double computeDelta(int Old, int New) {
|
|
return 100.0 * (Old ? ((New - Old) * 1.0 / Old) : 0.0);
|
|
}
|
|
|
|
/// Returns true if it is a first time we collect data for a given counter.
|
|
/// This is the case if the old value is 0 and the new one is something
|
|
/// different, i.e. we didn't have any statistics about it.
|
|
bool isFirstTimeData(int Old, int New) {
|
|
return Old == 0 && New != Old;
|
|
}
|
|
|
|
/// Dump statistics for a SILFunction. It is only used if a user asked to
|
|
/// produce detailed stats about transformations of SILFunctions. This
|
|
/// information is dumped unconditionally, for each transformation that changed
|
|
/// the function in any form. No thresholds are taken into account.
|
|
///
|
|
/// \param Stat statistics computed now, after the run of the transformation
|
|
/// \param Ctx transformation context to be used
|
|
void processFuncStatHistory(SILFunction *F, FunctionStat &Stat,
|
|
TransformationContext &Ctx) {
|
|
if (!SILStatsFunctions)
|
|
return;
|
|
|
|
if (!SILStatsDumpAll && !isMatchingFunction(F, /*shouldHaveNamePattern*/ true))
|
|
return;
|
|
|
|
printCounterValue("function_history", "block", Stat.BlockCount, F->getName(),
|
|
Ctx);
|
|
printCounterValue("function_history", "inst", Stat.InstCount, F->getName(),
|
|
Ctx);
|
|
|
|
/// Dump collected instruction counts.
|
|
if (!StatsOnlyInstructionsOptLoc.getInstCountsNum())
|
|
return;
|
|
|
|
for (auto kind : allSILInstructionKinds()) {
|
|
if (!Stat.InstCounts[kind])
|
|
continue;
|
|
if (!StatsOnlyInstructionsOptLoc.shouldComputeInstCount(kind))
|
|
continue;
|
|
std::string CounterName = "inst_";
|
|
CounterName += getSILInstructionName(kind);
|
|
printCounterValue("function_history", CounterName, Stat.InstCounts[kind],
|
|
F->getName(), Ctx);
|
|
}
|
|
}
|
|
|
|
/// Process SILFunction's statistics changes.
|
|
///
|
|
/// \param OldStat statistics computed last time
|
|
/// \param NewStat statistics computed now, after the run of the transformation
|
|
/// \param Ctx transformation context to be used
|
|
void processFuncStatsChanges(SILFunction *F, FunctionStat &OldStat,
|
|
FunctionStat &NewStat,
|
|
TransformationContext &Ctx) {
|
|
processFuncStatHistory(F, NewStat, Ctx);
|
|
|
|
if (!SILStatsFunctions && !SILStatsDumpAll)
|
|
return;
|
|
|
|
if (OldStat == NewStat)
|
|
return;
|
|
|
|
if ((!StatsOnlyFunctionsNamePattern.empty() ||
|
|
!StatsOnlyFunctionName.empty()) &&
|
|
!isMatchingFunction(F))
|
|
return;
|
|
|
|
// Compute deltas.
|
|
double DeltaBlockCount = computeDelta(OldStat.BlockCount, NewStat.BlockCount);
|
|
double DeltaInstCount = computeDelta(OldStat.InstCount, NewStat.InstCount);
|
|
|
|
NewLineInserter nl;
|
|
|
|
// TODO: handle cases where a function got smaller.
|
|
if ((SILStatsDumpAll &&
|
|
(DeltaBlockCount != 0.0 || OldStat.BlockCount == 0)) ||
|
|
(std::abs(DeltaBlockCount) > FuncBlockCountDeltaThreshold &&
|
|
OldStat.BlockCount > FuncBlockCountMinThreshold)) {
|
|
stats_os() << nl.get();
|
|
printCounterChange("function", "block", DeltaBlockCount, OldStat.BlockCount,
|
|
NewStat.BlockCount, Ctx, F->getName());
|
|
}
|
|
|
|
if ((SILStatsDumpAll && (DeltaInstCount != 0.0 || OldStat.InstCount == 0)) ||
|
|
(std::abs(DeltaInstCount) > FuncInstCountDeltaThreshold &&
|
|
OldStat.InstCount > FuncInstCountMinThreshold)) {
|
|
stats_os() << nl.get();
|
|
printCounterChange("function", "inst", DeltaInstCount, OldStat.InstCount,
|
|
NewStat.InstCount, Ctx, F->getName());
|
|
}
|
|
}
|
|
|
|
/// Process SILModule's statistics changes.
|
|
///
|
|
/// \param OldStat statistics computed last time
|
|
/// \param NewStat statistics computed now, after the run of the transformation
|
|
/// \param Ctx transformation context to be used
|
|
void processModuleStatsChanges(ModuleStat &OldStat, ModuleStat &NewStat,
|
|
TransformationContext &Ctx) {
|
|
if (!SILStatsModules && !SILStatsDumpAll)
|
|
return;
|
|
|
|
// Bail if no changes.
|
|
if (OldStat == NewStat)
|
|
return;
|
|
|
|
// Compute deltas.
|
|
double DeltaBlockCount = computeDelta(OldStat.BlockCount, NewStat.BlockCount);
|
|
double DeltaInstCount = computeDelta(OldStat.InstCount, NewStat.InstCount);
|
|
double DeltaFunctionCount =
|
|
computeDelta(OldStat.FunctionCount, NewStat.FunctionCount);
|
|
double DeltaUsedMemory = computeDelta(OldStat.UsedMemory, NewStat.UsedMemory);
|
|
|
|
NewLineInserter nl;
|
|
|
|
// Print delta for blocks only if it is above a threshold or we are asked to
|
|
// dump all changes.
|
|
if ((SILStatsDumpAll &&
|
|
(DeltaBlockCount != 0.0 ||
|
|
isFirstTimeData(OldStat.BlockCount, NewStat.BlockCount))) ||
|
|
(std::abs(DeltaBlockCount) > BlockCountDeltaThreshold)) {
|
|
stats_os() << nl.get();
|
|
printCounterChange("module", "block", DeltaBlockCount, OldStat.BlockCount,
|
|
NewStat.BlockCount, Ctx);
|
|
}
|
|
|
|
// Print delta for instructions only if it is above a threshold or we are
|
|
// asked to dump all changes.
|
|
if ((SILStatsDumpAll &&
|
|
(DeltaInstCount != 0.0 ||
|
|
isFirstTimeData(OldStat.InstCount, NewStat.InstCount))) ||
|
|
(std::abs(DeltaInstCount) > InstCountDeltaThreshold)) {
|
|
stats_os() << nl.get();
|
|
printCounterChange("module", "inst", DeltaInstCount, OldStat.InstCount,
|
|
NewStat.InstCount, Ctx);
|
|
}
|
|
|
|
// Print delta for functions only if it is above a threshold or we are
|
|
// asked to dump all changes.
|
|
if ((SILStatsDumpAll &&
|
|
(DeltaFunctionCount != 0.0 ||
|
|
isFirstTimeData(OldStat.FunctionCount, NewStat.FunctionCount))) ||
|
|
(std::abs(DeltaFunctionCount) > FunctionCountDeltaThreshold)) {
|
|
stats_os() << nl.get();
|
|
printCounterChange("module", "functions", DeltaFunctionCount,
|
|
OldStat.FunctionCount, NewStat.FunctionCount, Ctx);
|
|
}
|
|
|
|
// Print delta for the used memory only if it is above a threshold or we are
|
|
// asked to dump all changes.
|
|
if ((SILStatsDumpAll &&
|
|
(std::abs(DeltaUsedMemory) > UsedMemoryMinDeltaThreshold ||
|
|
isFirstTimeData(OldStat.UsedMemory, NewStat.UsedMemory))) ||
|
|
(std::abs(DeltaUsedMemory) > UsedMemoryDeltaThreshold)) {
|
|
stats_os() << nl.get();
|
|
printCounterChange("module", "memory", DeltaUsedMemory, OldStat.UsedMemory,
|
|
NewStat.UsedMemory, Ctx);
|
|
}
|
|
|
|
|
|
if (SILStatsDumpAll) {
|
|
// Dump stats about the number of created and deleted instructions.
|
|
auto DeltaCreatedInstCount =
|
|
computeDelta(OldStat.CreatedInstCount, NewStat.CreatedInstCount);
|
|
auto DeltaDeletedInstCount =
|
|
computeDelta(OldStat.DeletedInstCount, NewStat.DeletedInstCount);
|
|
if (DeltaCreatedInstCount != 0.0 ||
|
|
isFirstTimeData(OldStat.CreatedInstCount, NewStat.CreatedInstCount))
|
|
printCounterChange("module", "created_inst", DeltaCreatedInstCount,
|
|
OldStat.CreatedInstCount, NewStat.CreatedInstCount,
|
|
Ctx);
|
|
if (DeltaDeletedInstCount != 0.0 ||
|
|
isFirstTimeData(OldStat.DeletedInstCount, NewStat.DeletedInstCount))
|
|
printCounterChange("module", "deleted_inst", DeltaDeletedInstCount,
|
|
OldStat.DeletedInstCount, NewStat.DeletedInstCount,
|
|
Ctx);
|
|
}
|
|
|
|
/// Dump collected instruction counts.
|
|
if (!StatsOnlyInstructionsOptLoc.getInstCountsNum())
|
|
return;
|
|
|
|
for (auto kind : allSILInstructionKinds()) {
|
|
// Do not print anything, if there is no change.
|
|
if (OldStat.InstCounts[kind] == NewStat.InstCounts[kind])
|
|
continue;
|
|
if (!StatsOnlyInstructionsOptLoc.shouldComputeInstCount(kind))
|
|
continue;
|
|
SmallString<64> CounterName("inst_");
|
|
CounterName += getSILInstructionName(kind);
|
|
auto DeltaCounterKindCount =
|
|
computeDelta(OldStat.InstCounts[kind], NewStat.InstCounts[kind]);
|
|
printCounterChange("module", CounterName, DeltaCounterKindCount,
|
|
OldStat.InstCounts[kind], NewStat.InstCounts[kind], Ctx);
|
|
}
|
|
}
|
|
|
|
/// Update the stats for a module after a SIL transform has been performed.
|
|
void OptimizerStatsAnalysis::updateModuleStats(TransformationContext &Ctx) {
|
|
assert(&getModule() == &Ctx.getModule());
|
|
|
|
auto &ModStat = getModuleStat();
|
|
auto OldModStat = ModStat;
|
|
ModuleStat NewModStat;
|
|
|
|
// First, collect statistics that require scanning SILFunctions.
|
|
if (OldModStat.FunctionCount == 0) {
|
|
// This is the first time the stats are computed for the module.
|
|
// Iterate over all functions in the module and compute the stats.
|
|
for (auto &F : M) {
|
|
auto &FuncStat = getFunctionStat(&F);
|
|
FunctionStat NewFuncStat(&F);
|
|
processFuncStatHistory(&F, NewFuncStat, Ctx);
|
|
// Update function stats.
|
|
FuncStat = NewFuncStat;
|
|
// Update module stats.
|
|
NewModStat.addFunctionStat(NewFuncStat);
|
|
}
|
|
} else {
|
|
// Go only over functions that were changed since the last computation.
|
|
// These are the functions that got invalidated, removed or added.
|
|
//
|
|
// If no functions were changed by the last executed transformation, then
|
|
// the sets of invalidated, deleted and added functions will be empty and
|
|
// no FunctionStats will be updated.
|
|
//
|
|
// FIXME: As a result, the current implementation does not record the fact
|
|
// about performing a given transformation on a given function, if the
|
|
// function was not changed during the transformation. This reduced the
|
|
// amount of recorded information, but makes the history of transformations
|
|
// on a given function incomplete. If this ever becomes an issue, we can
|
|
// record all transformations, even if they do not change anything.
|
|
NewModStat = OldModStat;
|
|
|
|
// Process modified functions.
|
|
while (!InvalidatedFuncs.empty()) {
|
|
auto *F = InvalidatedFuncs.back();
|
|
InvalidatedFuncs.pop_back();
|
|
auto &FuncStat = getFunctionStat(F);
|
|
auto OldFuncStat = FuncStat;
|
|
FunctionStat NewFuncStat(F);
|
|
processFuncStatsChanges(F, OldFuncStat, NewFuncStat, Ctx);
|
|
NewModStat.subFunctionStat(OldFuncStat);
|
|
NewModStat.addFunctionStat(NewFuncStat);
|
|
FuncStat = NewFuncStat;
|
|
}
|
|
|
|
// Process deleted functions.
|
|
while (!DeletedFuncs.empty()) {
|
|
auto *F = DeletedFuncs.back();
|
|
DeletedFuncs.pop_back();
|
|
auto &FuncStat = getFunctionStat(F);
|
|
auto OldFuncStat = FuncStat;
|
|
FunctionStat NewFuncStat;
|
|
processFuncStatsChanges(F, OldFuncStat, NewFuncStat, Ctx);
|
|
NewModStat.subFunctionStat(OldFuncStat);
|
|
Cache->deleteFunctionStat(F);
|
|
}
|
|
|
|
// Process added functions.
|
|
while (!AddedFuncs.empty()) {
|
|
auto *F = AddedFuncs.back();
|
|
AddedFuncs.pop_back();
|
|
auto &FuncStat = getFunctionStat(F);
|
|
FunctionStat OldFuncStat;
|
|
FunctionStat NewFuncStat(F);
|
|
processFuncStatsChanges(F, OldFuncStat, NewFuncStat, Ctx);
|
|
NewModStat.addFunctionStat(NewFuncStat);
|
|
FuncStat = NewFuncStat;
|
|
}
|
|
}
|
|
|
|
// Then collect some more general statistics, which do not require
|
|
// any scanning of SILFunctions or the like.
|
|
NewModStat.addMemoryStat();
|
|
NewModStat.addCreatedAndDeletedInstructionsStat();
|
|
|
|
// Process updates.
|
|
processModuleStatsChanges(OldModStat, NewModStat, Ctx);
|
|
|
|
// Remember the new state of the collected stats.
|
|
ModStat = NewModStat;
|
|
}
|
|
|
|
FunctionStat::FunctionStat(SILFunction *F) {
|
|
InstCountVisitor V(InstCounts);
|
|
V.visitSILFunction(F);
|
|
BlockCount = V.getBlockCount();
|
|
InstCount = V.getInstCount();
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
/// Updates SILModule stats after finishing executing the
|
|
/// transform \p Transform.
|
|
///
|
|
/// \param M SILModule to be processed
|
|
/// \param Transform the SIL transformation that was just executed
|
|
/// \param PM the PassManager being used
|
|
void swift::updateSILModuleStatsAfterTransform(SILModule &M,
|
|
SILTransform *Transform,
|
|
SILPassManager &PM,
|
|
int PassNumber, int Duration) {
|
|
if (!SILStatsModules && !SILStatsFunctions && !SILStatsDumpAll)
|
|
return;
|
|
TransformationContext Ctx(M, PM, Transform, PassNumber, Duration);
|
|
OptimizerStatsAnalysis *Stats = PM.getAnalysis<OptimizerStatsAnalysis>();
|
|
Stats->updateModuleStats(Ctx);
|
|
}
|
|
|
|
// This is just a hook for possible extensions in the future.
|
|
// It could be used e.g. to detect sequences of consecutive executions
|
|
// of the same transform.
|
|
void swift::updateSILModuleStatsBeforeTransform(SILModule &M,
|
|
SILTransform *Transform,
|
|
SILPassManager &PM,
|
|
int PassNumber) {
|
|
if (!SILStatsModules && !SILStatsFunctions)
|
|
return;
|
|
}
|
|
|
|
SILAnalysis *swift::createOptimizerStatsAnalysis(SILModule *M) {
|
|
return new OptimizerStatsAnalysis(M);
|
|
}
|