[Profiler] Rework profiling of top-level code

Instead of creating and destroying a SILProfiler
per TopLevelCodeDecl, setup a single profiler
for the top-level entry point function, and visit
all the TopLevelCodeDecls when mapping regions.
This commit is contained in:
Hamish Knight
2022-10-14 17:45:14 +01:00
parent 337cbeffa3
commit 350e28b4b7
9 changed files with 168 additions and 81 deletions

View File

@@ -576,9 +576,7 @@ public:
Profiler = InheritedProfiler;
}
void createProfiler(ASTNode Root, SILDeclRef Ref);
void discardProfiler() { Profiler = nullptr; }
void createProfiler(SILDeclRef Ref);
ProfileCounter getEntryCount() const { return EntryCount; }

View File

@@ -83,8 +83,6 @@ class SILProfiler : public SILAllocated<SILProfiler> {
private:
SILModule &M;
ASTNode Root;
SILDeclRef forDecl;
bool EmitCoverageMapping;
@@ -107,13 +105,11 @@ private:
std::vector<std::tuple<std::string, uint64_t, std::string>> CoverageData;
SILProfiler(SILModule &M, ASTNode Root, SILDeclRef forDecl,
bool EmitCoverageMapping)
: M(M), Root(Root), forDecl(forDecl),
EmitCoverageMapping(EmitCoverageMapping) {}
SILProfiler(SILModule &M, SILDeclRef forDecl, bool EmitCoverageMapping)
: M(M), forDecl(forDecl), EmitCoverageMapping(EmitCoverageMapping) {}
public:
static SILProfiler *create(SILModule &M, ASTNode N, SILDeclRef Ref);
static SILProfiler *create(SILModule &M, SILDeclRef Ref);
/// Check if the function is set up for profiling.
bool hasRegionCounters() const { return NumRegionCounters != 0; }

View File

@@ -332,12 +332,11 @@ void SILFunction::deleteSnapshot(int ID) {
} while ((f = f->snapshots) != nullptr);
}
void SILFunction::createProfiler(ASTNode Root, SILDeclRef Ref) {
void SILFunction::createProfiler(SILDeclRef Ref) {
assert(!Profiler && "Function already has a profiler");
assert(Root && "Cannot profile a null ASTNode");
assert(Ref && "Must have non-null SILDeclRef");
Profiler = SILProfiler::create(Module, Root, Ref);
Profiler = SILProfiler::create(Module, Ref);
if (!Profiler)
return;

View File

@@ -25,13 +25,75 @@
using namespace swift;
/// Check whether a root AST node should be profiled.
static bool shouldProfile(ASTNode N, SILDeclRef Constant) {
/// Unfortunately this is needed as ASTNode can't currently represent a
/// SourceFile.
class NodeToProfile final {
/// For a direct ASTNode, this stores the node itself. For a main SourceFile,
/// it stores the corresponding ModuleDecl.
ASTNode Storage;
explicit NodeToProfile(ASTNode Node) : Storage(Node) {}
public:
static NodeToProfile node(ASTNode Node) {
assert(!isa_and_nonnull<ModuleDecl>(Node.dyn_cast<Decl *>()));
return NodeToProfile(Node);
}
static NodeToProfile mainSourceFile(SourceFile *SF) {
assert(SF->isScriptMode());
auto N = NodeToProfile(SF->getParentModule());
assert(N.getAsSourceFile() == SF);
return N;
}
/// If an ASTNode is being stored, returns it, otherwise \c nullptr.
ASTNode getAsNode() const {
return isSourceFile() ? nullptr : Storage;
}
/// Whether this is storing a main SourceFile.
bool isSourceFile() const {
return getAsSourceFile();
}
/// If a main SourceFile is being stored, returns it, otherwise \c nullptr.
SourceFile *getAsSourceFile() const {
auto *M = dyn_cast_or_null<ModuleDecl>(Storage.dyn_cast<Decl *>());
return M ? &M->getMainSourceFile() : nullptr;
}
};
static NodeToProfile getNodeToProfile(SILDeclRef Constant) {
// If we have an initialization expression, walk that instead of the variable.
if (auto *E = Constant.getInitializationExpr())
return NodeToProfile::node(E);
// Otherwise, we walk the SILDeclRef's node directly.
using LocKind = SILDeclRef::LocKind;
switch (Constant.getLocKind()) {
case LocKind::Decl:
return NodeToProfile::node(Constant.getDecl());
case LocKind::Closure:
return NodeToProfile::node(Constant.getAbstractClosureExpr());
case LocKind::File: {
auto *SF = cast<SourceFile>(Constant.getFileUnit());
return NodeToProfile::mainSourceFile(SF);
}
}
llvm_unreachable("Unhandled case in switch!");
}
/// Check whether we should profile a given SILDeclRef.
static bool shouldProfile(SILDeclRef Constant) {
auto Root = getNodeToProfile(Constant);
// Do not profile AST nodes with invalid source locations.
if (N.getStartLoc().isInvalid() || N.getEndLoc().isInvalid()) {
LLVM_DEBUG(llvm::dbgs()
<< "Skipping ASTNode: invalid start/end locations\n");
return false;
if (auto N = Root.getAsNode()) {
if (N.getStartLoc().isInvalid() || N.getEndLoc().isInvalid()) {
LLVM_DEBUG(llvm::dbgs()
<< "Skipping ASTNode: invalid start/end locations\n");
return false;
}
}
// Do not profile AST nodes in unavailable contexts.
@@ -62,18 +124,17 @@ static Stmt *getProfilerStmtForCase(CaseStmt *caseStmt) {
llvm_unreachable("invalid parent kind");
}
SILProfiler *SILProfiler::create(SILModule &M, ASTNode N, SILDeclRef Ref) {
SILProfiler *SILProfiler::create(SILModule &M, SILDeclRef Ref) {
// If profiling isn't enabled, don't profile anything.
const auto &Opts = M.getOptions();
if (!Opts.GenerateProfile && Opts.UseProfile.empty())
return nullptr;
if (!shouldProfile(N, Ref))
if (!shouldProfile(Ref))
return nullptr;
auto *Buf = M.allocate<SILProfiler>(1);
auto *SP =
::new (Buf) SILProfiler(M, N, Ref, Opts.EmitProfileCoverageMapping);
auto *SP = ::new (Buf) SILProfiler(M, Ref, Opts.EmitProfileCoverageMapping);
SP->assignRegionCounters();
return SP;
}
@@ -1277,6 +1338,16 @@ static StringRef getCurrentFileName(SILDeclRef forDecl) {
return {};
}
static void walkNode(NodeToProfile Node, ASTWalker &Walker) {
if (auto N = Node.getAsNode()) {
N.walk(Walker);
} else {
// We want to walk the SourceFile for a top-level entry point. We will only
// assign regions to TopLevelCodeDecls.
Node.getAsSourceFile()->walk(Walker);
}
}
void SILProfiler::assignRegionCounters() {
const auto &SM = M.getASTContext().SourceMgr;
@@ -1284,22 +1355,16 @@ void SILProfiler::assignRegionCounters() {
MapRegionCounters Mapper(forDecl, RegionCounterMap);
std::string CurrentFuncName;
FormalLinkage CurrentFuncLinkage;
if (auto *D = Root.dyn_cast<Decl *>()) {
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
CurrentFuncName = forDecl.mangle();
CurrentFuncLinkage = getDeclLinkage(AFD);
} else {
auto *TLCD = cast<TopLevelCodeDecl>(D);
llvm::raw_string_ostream OS{CurrentFuncName};
OS << "__tlcd_";
TLCD->getStartLoc().printLineAndColumn(OS, SM);
CurrentFuncLinkage = FormalLinkage::HiddenUnique;
auto Root = getNodeToProfile(forDecl);
auto CurrentFuncName = forDecl.mangle();
auto CurrentFuncLinkage = FormalLinkage::HiddenUnique;
if (auto N = Root.getAsNode()) {
if (auto *D = N.dyn_cast<Decl *>()) {
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D))
CurrentFuncLinkage = getDeclLinkage(AFD);
}
} else {
CurrentFuncName = forDecl.mangle();
CurrentFuncLinkage = FormalLinkage::HiddenUnique;
}
PGOFuncName = llvm::getPGOFuncName(
@@ -1311,7 +1376,7 @@ void SILProfiler::assignRegionCounters() {
LLVM_DEBUG(llvm::dbgs() << "Assigning counters to: " << CurrentFuncName
<< "\n");
Root.walk(Mapper);
walkNode(Root, Mapper);
NumRegionCounters = Mapper.NextCounter;
// TODO: Mapper needs to calculate a function hash as it goes.
@@ -1319,7 +1384,7 @@ void SILProfiler::assignRegionCounters() {
if (EmitCoverageMapping) {
CoverageMapping Coverage(SM, forDecl);
Root.walk(Coverage);
walkNode(Root, Coverage);
CovMap =
Coverage.emitSourceRegions(M, CurrentFuncName, PGOFuncName, PGOFuncHash,
RegionCounterMap, CurrentFileName);
@@ -1337,7 +1402,7 @@ void SILProfiler::assignRegionCounters() {
}
PGOMapping pgoMapper(forDecl, RegionCounterMap, LoadedCounts.get(),
RegionLoadedCounterMap, RegionCondToParentMap);
Root.walk(pgoMapper);
walkNode(Root, pgoMapper);
}
}

View File

@@ -856,7 +856,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
if (auto *ce = constant.getAbstractClosureExpr()) {
preEmitFunction(constant, f, ce);
PrettyStackTraceSILFunction X("silgen closureexpr", f);
f->createProfiler(ce, constant);
f->createProfiler(constant);
SILGenFunction(*this, *f, ce).emitClosure(ce);
postEmitFunction(constant, f);
break;
@@ -866,7 +866,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
preEmitFunction(constant, f, fd);
PrettyStackTraceSILFunction X("silgen emitFunction", f);
f->createProfiler(fd, constant);
f->createProfiler(constant);
SILGenFunction(*this, *f, fd).emitFunction(fd);
postEmitFunction(constant, f);
break;
@@ -885,7 +885,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
} else {
preEmitFunction(constant, f, decl);
PrettyStackTraceSILFunction X("silgen emitConstructor", f);
f->createProfiler(decl, constant);
f->createProfiler(constant);
SILGenFunction(*this, *f, decl).emitValueConstructor(decl);
postEmitFunction(constant, f);
}
@@ -898,7 +898,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
preEmitFunction(constant, f, decl);
PrettyStackTraceSILFunction X("silgen constructor initializer", f);
f->createProfiler(decl, constant);
f->createProfiler(constant);
SILGenFunction(*this, *f, decl).emitClassConstructorInitializer(decl);
postEmitFunction(constant, f);
break;
@@ -952,7 +952,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
auto loc = RegularLocation::getAutoGeneratedLocation(init);
preEmitFunction(constant, f, loc);
PrettyStackTraceSILFunction X("silgen emitStoredPropertyInitialization", f);
f->createProfiler(init, constant);
f->createProfiler(constant);
SILGenFunction SGF(*this, *f, initDC);
// If this is a stored property initializer inside a type at global scope,
@@ -981,7 +981,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
auto *init = constant.getInitializationExpr();
assert(init);
f->createProfiler(init, constant);
f->createProfiler(constant);
auto varDC = var->getInnermostDeclContext();
SILGenFunction SGF(*this, *f, varDC);
SGF.emitGeneratorFunction(constant, init, /*EmitProfilerIncrement*/ true);
@@ -1039,7 +1039,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
auto *dd = cast<DestructorDecl>(constant.getDecl());
preEmitFunction(constant, f, dd);
PrettyStackTraceSILFunction X("silgen emitDestroyingDestructor", f);
f->createProfiler(dd, constant);
f->createProfiler(constant);
SILGenFunction(*this, *f, dd).emitDestroyingDestructor(dd);
postEmitFunction(constant, f);
return;
@@ -1053,7 +1053,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
if (usesObjCAllocator(cd)) {
preEmitFunction(constant, f, dd);
PrettyStackTraceSILFunction X("silgen emitDestructor -dealloc", f);
f->createProfiler(dd, constant);
f->createProfiler(constant);
SILGenFunction(*this, *f, dd).emitObjCDestructor(constant);
postEmitFunction(constant, f);
return;
@@ -1936,23 +1936,8 @@ void SILGenModule::visitTopLevelCodeDecl(TopLevelCodeDecl *td) {
if (!TopLevelSGF->B.hasValidInsertionPoint())
return;
// Retrieve the entry point constant we're emitting for.
// FIXME: This won't be necessary once we unify emission of the entry point
// such that we walk all of the TopLevelCodeDecls in one shot. This will be
// needed in order to requestify entry point emission.
auto *SF = td->getParentSourceFile();
assert(SF && "TopLevelDecl outside of a SourceFile?");
auto entryPoint = TopLevelSGF->F.isAsync()
? SILDeclRef::getAsyncMainFileEntryPoint(SF)
: SILDeclRef::getMainFileEntryPoint(SF);
// A single SILFunction may be used to lower multiple top-level decls. When
// this happens, fresh profile counters must be assigned to the new decl.
TopLevelSGF->F.discardProfiler();
TopLevelSGF->F.createProfiler(td, entryPoint);
TopLevelSGF->emitProfilerIncrement(td->getBody());
DebugScope DS(*TopLevelSGF, CleanupLocation(td));
for (auto &ESD : td->getBody()->getElements()) {
@@ -1985,11 +1970,15 @@ namespace {
/// An RAII class to scope source file codegen.
class SourceFileScope {
SILGenModule &sgm;
SILDeclRef EntryRef;
Optional<Scope> scope;
bool isAsyncTopLevel = false;
public:
SourceFileScope(SILGenModule &sgm, SourceFile *sf) : sgm(sgm) {
// If this is the script-mode file for the module, create a toplevel.
// TODO: We need to unify emission of the entry point such that we walk
// all of the TopLevelCodeDecls in one shot. This will be needed in order
// to requestify entry point emission.
if (sf->isScriptMode()) {
assert(!sgm.TopLevelSGF && "already emitted toplevel?!");
assert(!sgm.M.lookUpFunction(
@@ -1999,15 +1988,19 @@ public:
auto mainEntryRef = SILDeclRef::getMainFileEntryPoint(sf);
SILFunction * toplevel = sgm.getFunction(mainEntryRef, ForDefinition);
toplevel->setBare(IsBare);
EntryRef = mainEntryRef;
if (sf->isAsyncContext()) {
isAsyncTopLevel = true;
auto asyncEntryRef = SILDeclRef::getAsyncMainFileEntryPoint(sf);
SILFunction * asyncTopLevel = sgm.getFunction(asyncEntryRef, ForDefinition);
auto *asyncTopLevel = sgm.getFunction(asyncEntryRef, ForDefinition);
SILGenFunction(sgm, *toplevel, sf).emitAsyncMainThreadStart(asyncEntryRef);
toplevel = asyncTopLevel;
EntryRef = asyncEntryRef;
}
toplevel->createProfiler(EntryRef);
sgm.TopLevelSGF = new SILGenFunction(sgm, *toplevel, sf);
sgm.TopLevelSGF->MagicFunctionName = sgm.SwiftModule->getName();
auto moduleCleanupLoc = CleanupLocation::getModuleCleanupLocation();

View File

@@ -0,0 +1,22 @@
// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sil -emit-sorted-sil -module-name coverage_maindecl -parse-as-library %s | %FileCheck %s
// RUN: %target-swift-frontend -profile-generate -profile-coverage-mapping -emit-ir -parse-as-library %s
// CHECK-NOT: sil_coverage_map {{.*}} "main"
@main
struct S {
// CHECK: sil_coverage_map {{.*}} "{{.*}}s17coverage_maindecl1SV4mainyyFZ"
// CHECK-NEXT: [[@LINE+1]]:22 -> [[@LINE+10]]:4 : 0
static func main() {
var i : Int32 = 0
// CHECK-NEXT: [[@LINE+3]]:11 -> [[@LINE+3]]:19 : (0 + 1)
// CHECK-NEXT: [[@LINE+2]]:20 -> [[@LINE+4]]:6 : 1
// CHECK-NEXT: [[@LINE+3]]:6 -> [[@LINE+4]]:4 : 0
while (i < 10) {
i += 1
}
} // CHECK-NEXT: }
}
// CHECK-NOT: sil_coverage_map {{.*}} "main"

View File

@@ -46,6 +46,6 @@ func baz() {
baz()
// IRGEN-LABEL: define {{.*}} @main
// IRGEN: call void @llvm.instrprof.increment({{.*}} @"__profn_{{.*}}__tlcd_line
// IRGEN: call void @llvm.instrprof.increment({{.*}} @"__profn_{{.*}}main
// IRGEN: call void @llvm.instrprof.increment({{.*}} @"__profn_{{.*}}$s18coverage_optimized3bazyyF"
// IRGEN: call void @llvm.instrprof.increment({{.*}} @"__profn_{{.*}}$s18coverage_optimized3barSbyF"

View File

@@ -1,31 +1,39 @@
// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sil -module-name coverage_toplevel %s | %FileCheck %s
// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sil -emit-sorted-sil -module-name coverage_toplevel %s | %FileCheck %s
// RUN: %target-swift-frontend -profile-generate -profile-coverage-mapping -emit-ir %s
// CHECK: sil_coverage_map{{.*}}__tlcd_line:[[@LINE+2]]:1
// CHECK: [[@LINE+1]]:1 -> [[@LINE+1]]:11
// CHECK: sil_coverage_map{{.*}}// coverage_toplevel.f1
// CHECK: sil_coverage_map {{.*}} "main"
// CHECK: [[@LINE+1]]:1 -> [[@LINE+1]]:11 : 0
print("a")
// CHECK: sil_coverage_map{{.*}}// coverage_toplevel.f1
// Make sure we don't emit regions for these.
class C {
var k = 0
func foo() {}
}
func f1() {}
// CHECK-NEXT: [[@LINE+1]]:1 -> [[@LINE+1]]:18 : 1
var i : Int32 = 0
// CHECK: sil_coverage_map{{.*}}__tlcd_line:[[@LINE+2]]:1
// CHECK: [[@LINE+1]]:7 -> [[@LINE+1]]:15 : (0 + 1)
// CHECK-NEXT: [[@LINE+4]]:1 -> [[@LINE+6]]:2 : 2
// CHECK-NEXT: [[@LINE+3]]:7 -> [[@LINE+3]]:15 : (2 + 3)
// CHECK-NEXT: [[@LINE+2]]:16 -> [[@LINE+4]]:2 : 3
// CHECK-NEXT: [[@LINE+3]]:2 -> [[@LINE+3]]:2 : 2
while (i < 10) {
i += 1
}
// CHECK: sil_coverage_map{{.*}}__tlcd_line:[[@LINE+3]]:1
// CHECK-NEXT: [[@LINE+2]]:17 -> [[@LINE+2]]:18 : 1
// CHECK-NEXT: [[@LINE+1]]:21 -> [[@LINE+1]]:22 : (0 - 1)
// CHECK-NEXT: [[@LINE+3]]:1 -> [[@LINE+3]]:22 : 4
// CHECK-NEXT: [[@LINE+2]]:17 -> [[@LINE+2]]:18 : 5
// CHECK-NEXT: [[@LINE+1]]:21 -> [[@LINE+1]]:22 : (4 - 5)
var i2 = true ? 1 : 0;
// CHECK: sil_coverage_map{{.*}}__tlcd_line:[[@LINE+5]]:1
// CHECK-NEXT: [[@LINE+4]]:4 -> [[@LINE+4]]:10 : 0
// CHECK-NEXT: [[@LINE+3]]:11 -> [[@LINE+5]]:2 : 1
// CHECK-NEXT: [[@LINE+2]]:1 -> [[@LINE+4]]:2 : 0
// CHECK-NEXT: [[@LINE+3]]:2 -> [[@LINE+3]]:2 : 0
// CHECK-NEXT: [[@LINE+4]]:1 -> [[@LINE+6]]:2 : 6
// CHECK-NEXT: [[@LINE+3]]:4 -> [[@LINE+3]]:10 : 6
// CHECK-NEXT: [[@LINE+2]]:11 -> [[@LINE+4]]:2 : 7
// CHECK-NEXT: [[@LINE+3]]:2 -> [[@LINE+3]]:2 : 6
if (true) {
i2 = 2
}

View File

@@ -0,0 +1,6 @@
// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sil -emit-sorted-sil -module-name coverage_toplevel_empty %s | %FileCheck %s
// RUN: %target-swift-frontend -profile-generate -profile-coverage-mapping -emit-ir %s
// Make sure we don't emit a coverage map for an empty main file with no
// top-level code.
// CHECK-NOT: sil_coverage_map