Skip to content

Commit d26c859

Browse files
committed
Add -warn-long-expression-type-checking=<limit> frontend option.
Generates a warning for any expression that takes longer than <limit> milliseconds to type check. This compliments the existing -warn-long-function-body=<limit> option.
1 parent 5af0266 commit d26c859

File tree

10 files changed

+80
-13
lines changed

10 files changed

+80
-13
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3709,6 +3709,9 @@ WARNING(debug_long_function_body, none,
37093709
WARNING(debug_long_closure_body, none,
37103710
"closure took %0ms to type-check (limit: %1ms)",
37113711
(unsigned, unsigned))
3712+
WARNING(debug_long_expression, none,
3713+
"expression took %0ms to type-check (limit: %1ms)",
3714+
(unsigned, unsigned))
37123715

37133716
//------------------------------------------------------------------------------
37143717
// Pattern match diagnostics

include/swift/Frontend/FrontendOptions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ class FrontendOptions {
138138
/// Intended for debugging purposes only.
139139
unsigned WarnLongFunctionBodies = 0;
140140

141+
/// If non-zero, warn when type-checking an expression takes longer
142+
/// than this many milliseconds.
143+
///
144+
/// Intended for debugging purposes only.
145+
unsigned WarnLongExpressionTypeChecking = 0;
146+
141147
enum ActionType {
142148
NoneAction, ///< No specific action
143149
Parse, ///< Parse only

include/swift/Option/FrontendOptions.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ def warn_long_function_bodies : Separate<["-"], "warn-long-function-bodies">,
320320
def warn_long_function_bodies_EQ : Joined<["-"], "warn-long-function-bodies=">,
321321
Alias<warn_long_function_bodies>;
322322

323+
def warn_long_expression_type_checking : Separate<["-"], "warn-long-expression-type-checking">,
324+
MetaVarName<"<n>">,
325+
HelpText<"Warns when type-checking a function takes longer than <n> ms">;
326+
def warn_long_expression_type_checking_EQ : Joined<["-"], "warn-long-expression-type-checking=">,
327+
Alias<warn_long_expression_type_checking>;
328+
323329
def enable_source_import : Flag<["-"], "enable-source-import">,
324330
HelpText<"Enable importing of Swift source files">;
325331

include/swift/Subsystems.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ namespace swift {
191191
void performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
192192
OptionSet<TypeCheckingFlags> Options,
193193
unsigned StartElem = 0,
194-
unsigned WarnLongFunctionBodies = 0);
194+
unsigned WarnLongFunctionBodies = 0,
195+
unsigned WarnLongExpressionTypeChecking = 0);
195196

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

lib/Frontend/CompilerInvocation.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
216216
}
217217
}
218218

219+
if (const Arg *A = Args.getLastArg(OPT_warn_long_expression_type_checking)) {
220+
unsigned attempt;
221+
if (StringRef(A->getValue()).getAsInteger(10, attempt)) {
222+
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
223+
A->getAsString(Args), A->getValue());
224+
} else {
225+
Opts.WarnLongExpressionTypeChecking = attempt;
226+
}
227+
}
228+
219229
Opts.PlaygroundTransform |= Args.hasArg(OPT_playground);
220230
if (Args.hasArg(OPT_disable_playground_transform))
221231
Opts.PlaygroundTransform = false;

lib/Frontend/Frontend.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,8 @@ void CompilerInstance::performSema() {
519519
if (mainIsPrimary) {
520520
performTypeChecking(MainFile, PersistentState.getTopLevelContext(),
521521
TypeCheckOptions, CurTUElem,
522-
options.WarnLongFunctionBodies);
522+
options.WarnLongFunctionBodies,
523+
options.WarnLongExpressionTypeChecking);
523524
}
524525
CurTUElem = MainFile.Decls.size();
525526
} while (!Done);
@@ -546,8 +547,9 @@ void CompilerInstance::performSema() {
546547
if (auto SF = dyn_cast<SourceFile>(File))
547548
if (PrimaryBufferID == NO_SUCH_BUFFER || SF == PrimarySourceFile)
548549
performTypeChecking(*SF, PersistentState.getTopLevelContext(),
549-
TypeCheckOptions, /*curElem*/0,
550-
options.WarnLongFunctionBodies);
550+
TypeCheckOptions, /*curElem*/ 0,
551+
options.WarnLongFunctionBodies,
552+
options.WarnLongExpressionTypeChecking);
551553

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

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,22 +1773,35 @@ namespace {
17731773

17741774
class ExpressionTimer {
17751775
Expr* E;
1776+
unsigned WarnLimit;
1777+
bool ShouldDump;
17761778
ASTContext &Context;
17771779
llvm::TimeRecord StartTime = llvm::TimeRecord::getCurrentTime();
17781780

17791781
public:
1780-
ExpressionTimer(Expr *E, ASTContext &Context) : E(E), Context(Context) {}
1782+
ExpressionTimer(Expr *E, bool shouldDump, unsigned warnLimit,
1783+
ASTContext &Context)
1784+
: E(E), WarnLimit(warnLimit), ShouldDump(shouldDump), Context(Context) {
1785+
}
17811786

17821787
~ExpressionTimer() {
17831788
llvm::TimeRecord endTime = llvm::TimeRecord::getCurrentTime(false);
17841789

17851790
auto elapsed = endTime.getProcessTime() - StartTime.getProcessTime();
1791+
unsigned elapsedMS = static_cast<unsigned>(elapsed * 1000);
1792+
1793+
if (ShouldDump) {
1794+
// Round up to the nearest 100th of a millisecond.
1795+
llvm::errs() << llvm::format("%0.2f", ceil(elapsed * 100000) / 100)
1796+
<< "ms\t";
1797+
E->getLoc().print(llvm::errs(), Context.SourceMgr);
1798+
llvm::errs() << "\n";
1799+
}
17861800

1787-
// Round up to the nearest 100th of a millisecond.
1788-
llvm::errs() << llvm::format("%0.2f", ceil(elapsed * 100000) / 100)
1789-
<< "ms\t";
1790-
E->getLoc().print(llvm::errs(), Context.SourceMgr);
1791-
llvm::errs() << "\n";
1801+
if (WarnLimit != 0 && elapsedMS >= WarnLimit && E->getLoc().isValid())
1802+
Context.Diags.diagnose(E->getLoc(), diag::debug_long_expression,
1803+
elapsedMS, WarnLimit)
1804+
.highlight(E->getSourceRange());
17921805
}
17931806
};
17941807

@@ -1802,8 +1815,9 @@ bool TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
18021815
ExprTypeCheckListener *listener,
18031816
ConstraintSystem *baseCS) {
18041817
Optional<ExpressionTimer> timer;
1805-
if (DebugTimeExpressions)
1806-
timer.emplace(expr, Context);
1818+
if (DebugTimeExpressions || WarnLongExpressionTypeChecking)
1819+
timer.emplace(expr, DebugTimeExpressions, WarnLongExpressionTypeChecking,
1820+
Context);
18071821

18081822
PrettyStackTraceExpr stackTrace(Context, "type-checking", expr);
18091823

lib/Sema/TypeChecker.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,8 @@ void swift::typeCheckExternalDefinitions(SourceFile &SF) {
621621
void swift::performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
622622
OptionSet<TypeCheckingFlags> Options,
623623
unsigned StartElem,
624-
unsigned WarnLongFunctionBodies) {
624+
unsigned WarnLongFunctionBodies,
625+
unsigned WarnLongExpressionTypeChecking) {
625626
if (SF.ASTStage == SourceFile::TypeChecked)
626627
return;
627628

@@ -646,6 +647,7 @@ void swift::performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
646647

647648
if (MyTC) {
648649
MyTC->setWarnLongFunctionBodies(WarnLongFunctionBodies);
650+
MyTC->setWarnLongExpressionTypeChecking(WarnLongExpressionTypeChecking);
649651
if (Options.contains(TypeCheckingFlags::DebugTimeFunctionBodies))
650652
MyTC->enableDebugTimeFunctionBodies();
651653

lib/Sema/TypeChecker.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,12 @@ class TypeChecker final : public LazyResolver {
829829
/// Intended for debugging purposes only.
830830
unsigned WarnLongFunctionBodies = 0;
831831

832+
/// If \p timeInMS is non-zero, warn when type-chcking an expression
833+
/// takes longer than this many milliseconds.
834+
///
835+
/// Intended for debugging purposes only.
836+
unsigned WarnLongExpressionTypeChecking = 0;
837+
832838
/// If true, the time it takes to type-check each function will be dumped
833839
/// to llvm::errs().
834840
bool DebugTimeFunctionBodies = false;
@@ -872,6 +878,14 @@ class TypeChecker final : public LazyResolver {
872878
WarnLongFunctionBodies = timeInMS;
873879
}
874880

881+
/// If \p timeInMS is non-zero, warn when type-chcking an expression
882+
/// takes longer than this many milliseconds.
883+
///
884+
/// Intended for debugging purposes only.
885+
void setWarnLongExpressionTypeChecking(unsigned timeInMS) {
886+
WarnLongExpressionTypeChecking = timeInMS;
887+
}
888+
875889
bool getInImmediateMode() {
876890
return InImmediateMode;
877891
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %target-typecheck-verify-swift -warn-long-expression-type-checking=1 -warn-long-function-bodies=1 %s
2+
func foo<T>(_ x: T) -> T { return x }
3+
func foo(_ x: Int) -> Int { return x }
4+
5+
func test(m: Double) -> Int {
6+
// expected-warning@-1 {{global function 'test(m:)' took}}
7+
return Int(foo(Float(m) / 20) * 20 - 2) + 10
8+
// expected-warning@-1 {{expression took}}
9+
}

0 commit comments

Comments
 (0)