Skip to content

Commit 18c7592

Browse files
committed
Add frontend option "-warn-long-function-bodies=<N>".
This is a /slightly/ more user-friendly option than -debug-time-function-bodies; pass it a limit in milliseconds and the compiler will warn whenever a function or multi-statement closure takes longer than that to type-check. Since it's a frontend option (and thus usually passed with -Xfrontend), I went with the "joined" syntax as the common case. The usual "separate" syntax of "-warn-long-function-bodies <N>" is also available. As a frontend option, this is UNSUPPORTED and may be removed without notice at any future date. Additional caveats: - Other parts of type-checking not measured by this may also be slow. - May include first-use penalties (i.e. "this is slow because it's the first function that references an imported type, which causes many things to be imported") - Does not report anything whatsoever about other phases of compilation (SILGen, optimization, IRGen, assembly emission, whatever). - Does not catch anything accidentally being type-checked multiple times (a known issue for initial value expressions on properties).
1 parent ecd8821 commit 18c7592

File tree

9 files changed

+107
-25
lines changed

9 files changed

+107
-25
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,17 @@ ERROR(circular_reference, none,
29112911
NOTE(circular_reference_through, none,
29122912
"through reference here", ())
29132913

2914+
//------------------------------------------------------------------------------
2915+
// Debug diagnostics
2916+
//------------------------------------------------------------------------------
2917+
2918+
WARNING(debug_long_function_body, none,
2919+
"%0 %1 took %2ms to type-check (limit: %3ms)",
2920+
(DescriptiveDeclKind, DeclName, unsigned, unsigned))
2921+
WARNING(debug_long_closure_body, none,
2922+
"closure took %0ms to type-check (limit: %1ms)",
2923+
(unsigned, unsigned))
2924+
29142925
#ifndef DIAG_NO_UNDEF
29152926
# if defined(DIAG)
29162927
# undef DIAG

include/swift/Frontend/FrontendOptions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ class FrontendOptions {
128128
/// The path to collect the group information for the compiled source files.
129129
std::string GroupInfoPath;
130130

131+
/// If non-zero, warn when a function body takes longer than this many
132+
/// milliseconds to type-check.
133+
///
134+
/// Intended for debugging purposes only.
135+
unsigned WarnLongFunctionBodies = 0;
136+
131137
enum ActionType {
132138
NoneAction, ///< No specific action
133139
Parse, ///< Parse and type-check only

include/swift/Option/FrontendOptions.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ def swift3_migration :
247247
Flag<["-"], "swift3-migration">,
248248
HelpText<"Enable Fix-It based migration aids for Swift 3">;
249249

250+
def warn_long_function_bodies : Separate<["-"], "warn-long-function-bodies">,
251+
MetaVarName<"<n>">,
252+
HelpText<"Warns when type-checking a function takes longer than <n> ms">;
253+
def warn_long_function_bodies_EQ : Joined<["-"], "warn-long-function-bodies=">,
254+
Alias<warn_long_function_bodies>;
255+
250256
def warn_omit_needless_words :
251257
Flag<["-"], "Womit-needless-words">,
252258
HelpText<"Warn about needless words in names">;

include/swift/Subsystems.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,14 @@ namespace swift {
160160
/// types and diagnose problems therein.
161161
///
162162
/// \param StartElem Where to start for incremental type-checking in the main
163-
/// source file.
163+
/// source file.
164+
///
165+
/// \param WarnLongFunctionBodies If non-zero, warn when a function body takes
166+
/// longer than this many milliseconds to type-check
164167
void performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
165168
OptionSet<TypeCheckingFlags> Options,
166-
unsigned StartElem = 0);
169+
unsigned StartElem = 0,
170+
unsigned WarnLongFunctionBodies = 0);
167171

168172
/// Once type checking is complete, this walks protocol requirements
169173
/// to resolve default witnesses.

lib/Frontend/CompilerInvocation.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
175175
Opts.DebugTimeFunctionBodies |= Args.hasArg(OPT_debug_time_function_bodies);
176176
Opts.DebugTimeCompilation |= Args.hasArg(OPT_debug_time_compilation);
177177

178+
if (const Arg *A = Args.getLastArg(OPT_warn_long_function_bodies)) {
179+
unsigned attempt;
180+
if (StringRef(A->getValue()).getAsInteger(10, attempt)) {
181+
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
182+
A->getAsString(Args), A->getValue());
183+
} else {
184+
Opts.WarnLongFunctionBodies = attempt;
185+
}
186+
}
187+
178188
Opts.PlaygroundTransform |= Args.hasArg(OPT_playground);
179189
if (Args.hasArg(OPT_disable_playground_transform))
180190
Opts.PlaygroundTransform = false;

lib/Frontend/Frontend.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,10 @@ void CompilerInstance::performSema() {
465465
if (PrimaryBufferID == NO_SUCH_BUFFER) {
466466
TypeCheckOptions |= TypeCheckingFlags::DelayWholeModuleChecking;
467467
}
468-
if (Invocation.getFrontendOptions().DebugTimeFunctionBodies) {
468+
if (options.DebugTimeFunctionBodies) {
469469
TypeCheckOptions |= TypeCheckingFlags::DebugTimeFunctionBodies;
470470
}
471-
if (Invocation.getFrontendOptions().actionIsImmediate()) {
471+
if (options.actionIsImmediate()) {
472472
TypeCheckOptions |= TypeCheckingFlags::ForImmediateMode;
473473
}
474474

@@ -497,7 +497,8 @@ void CompilerInstance::performSema() {
497497
&PersistentState, DelayedCB.get());
498498
if (mainIsPrimary) {
499499
performTypeChecking(MainFile, PersistentState.getTopLevelContext(),
500-
TypeCheckOptions, CurTUElem);
500+
TypeCheckOptions, CurTUElem,
501+
options.WarnLongFunctionBodies);
501502
}
502503
CurTUElem = MainFile.Decls.size();
503504
} while (!Done);
@@ -516,7 +517,8 @@ void CompilerInstance::performSema() {
516517
if (auto SF = dyn_cast<SourceFile>(File))
517518
if (PrimaryBufferID == NO_SUCH_BUFFER || SF == PrimarySourceFile)
518519
performTypeChecking(*SF, PersistentState.getTopLevelContext(),
519-
TypeCheckOptions);
520+
TypeCheckOptions, /*curElem*/0,
521+
options.WarnLongFunctionBodies);
520522

521523
// Even if there were no source files, we should still record known
522524
// protocols.

lib/Sema/TypeCheckStmt.cpp

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -134,29 +134,56 @@ namespace {
134134
}
135135
};
136136

137+
/// Used for debugging which parts of the code are taking a long time to
138+
/// compile.
137139
class FunctionBodyTimer {
138-
PointerUnion<const AbstractFunctionDecl *,
139-
const AbstractClosureExpr *> Function;
140+
AnyFunctionRef Function;
140141
llvm::TimeRecord StartTime = llvm::TimeRecord::getCurrentTime();
142+
unsigned WarnLimit;
143+
bool ShouldDump;
141144

142145
public:
143-
FunctionBodyTimer(decltype(Function) Fn) : Function(Fn) {}
146+
FunctionBodyTimer(AnyFunctionRef Fn, bool shouldDump,
147+
unsigned warnLimit)
148+
: Function(Fn), WarnLimit(warnLimit), ShouldDump(shouldDump) {}
149+
144150
~FunctionBodyTimer() {
145151
llvm::TimeRecord endTime = llvm::TimeRecord::getCurrentTime(false);
146152

147153
auto elapsed = endTime.getProcessTime() - StartTime.getProcessTime();
148-
llvm::errs() << llvm::format("%0.1f", elapsed * 1000) << "ms\t";
149-
150-
if (auto *AFD = Function.dyn_cast<const AbstractFunctionDecl *>()) {
151-
AFD->getLoc().print(llvm::errs(), AFD->getASTContext().SourceMgr);
152-
llvm::errs() << "\t";
153-
AFD->print(llvm::errs(), PrintOptions());
154-
} else {
155-
auto *ACE = Function.get<const AbstractClosureExpr *>();
156-
ACE->getLoc().print(llvm::errs(), ACE->getASTContext().SourceMgr);
157-
llvm::errs() << "\t(closure)";
154+
unsigned elapsedMS = static_cast<unsigned>(elapsed * 1000);
155+
156+
ASTContext &ctx = Function.getAsDeclContext()->getASTContext();
157+
158+
if (ShouldDump) {
159+
llvm::errs() << llvm::format("%0.1f", elapsed * 1000) << "ms\t";
160+
Function.getLoc().print(llvm::errs(), ctx.SourceMgr);
161+
162+
if (auto *AFD = Function.getAbstractFunctionDecl()) {
163+
llvm::errs() << "\t";
164+
AFD->print(llvm::errs(), PrintOptions());
165+
} else {
166+
llvm::errs() << "\t(closure)";
167+
}
168+
llvm::errs() << "\n";
169+
}
170+
171+
if (WarnLimit != 0 && elapsedMS >= WarnLimit) {
172+
if (auto *AFD = Function.getAbstractFunctionDecl()) {
173+
DeclName name = AFD->getFullName();
174+
if (!name) {
175+
if (auto *method = dyn_cast<FuncDecl>(AFD)) {
176+
name = method->getAccessorStorageDecl()->getFullName();
177+
}
178+
}
179+
ctx.Diags.diagnose(AFD, diag::debug_long_function_body,
180+
AFD->getDescriptiveKind(), name,
181+
elapsedMS, WarnLimit);
182+
} else {
183+
ctx.Diags.diagnose(Function.getLoc(), diag::debug_long_closure_body,
184+
elapsedMS, WarnLimit);
185+
}
158186
}
159-
llvm::errs() << "\n";
160187
}
161188
};
162189
}
@@ -1191,8 +1218,8 @@ bool TypeChecker::typeCheckAbstractFunctionBody(AbstractFunctionDecl *AFD) {
11911218
return false;
11921219

11931220
Optional<FunctionBodyTimer> timer;
1194-
if (DebugTimeFunctionBodies)
1195-
timer.emplace(AFD);
1221+
if (DebugTimeFunctionBodies || WarnLongFunctionBodies)
1222+
timer.emplace(AFD, DebugTimeFunctionBodies, WarnLongFunctionBodies);
11961223

11971224
if (typeCheckAbstractFunctionBodyUntil(AFD, SourceLoc()))
11981225
return true;
@@ -1433,8 +1460,8 @@ void TypeChecker::typeCheckClosureBody(ClosureExpr *closure) {
14331460
BraceStmt *body = closure->getBody();
14341461

14351462
Optional<FunctionBodyTimer> timer;
1436-
if (DebugTimeFunctionBodies)
1437-
timer.emplace(closure);
1463+
if (DebugTimeFunctionBodies || WarnLongFunctionBodies)
1464+
timer.emplace(closure, DebugTimeFunctionBodies, WarnLongFunctionBodies);
14381465

14391466
StmtChecker(*this, closure).typeCheckBody(body);
14401467
if (body) {

lib/Sema/TypeChecker.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,8 @@ void swift::typeCheckExternalDefinitions(SourceFile &SF) {
498498

499499
void swift::performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
500500
OptionSet<TypeCheckingFlags> Options,
501-
unsigned StartElem) {
501+
unsigned StartElem,
502+
unsigned WarnLongFunctionBodies) {
502503
if (SF.ASTStage == SourceFile::TypeChecked)
503504
return;
504505

@@ -516,6 +517,7 @@ void swift::performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
516517
TypeChecker TC(Ctx);
517518
SharedTimer timer("Type checking / Semantic analysis");
518519

520+
TC.setWarnLongFunctionBodies(WarnLongFunctionBodies);
519521
if (Options.contains(TypeCheckingFlags::DebugTimeFunctionBodies))
520522
TC.enableDebugTimeFunctionBodies();
521523

lib/Sema/TypeChecker.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,12 @@ class TypeChecker final : public LazyResolver {
545545
/// The index of the next response metavariable to bind to a REPL result.
546546
unsigned NextResponseVariableIndex = 0;
547547

548+
/// If non-zero, warn when a function body takes longer than this many
549+
/// milliseconds to type-check.
550+
///
551+
/// Intended for debugging purposes only.
552+
unsigned WarnLongFunctionBodies = 0;
553+
548554
/// If true, the time it takes to type-check each function will be dumped
549555
/// to llvm::errs().
550556
bool DebugTimeFunctionBodies = false;
@@ -571,6 +577,14 @@ class TypeChecker final : public LazyResolver {
571577
DebugTimeFunctionBodies = true;
572578
}
573579

580+
/// If \p timeInMS is non-zero, warn when a function body takes longer than
581+
/// this many milliseconds to type-check.
582+
///
583+
/// Intended for debugging purposes only.
584+
void setWarnLongFunctionBodies(unsigned timeInMS) {
585+
WarnLongFunctionBodies = timeInMS;
586+
}
587+
574588
bool getInImmediateMode() {
575589
return InImmediateMode;
576590
}

0 commit comments

Comments
 (0)