Skip to content

Commit d8cf185

Browse files
committed
Sema: Fix source location bookkeeping for 'reasonable time' diagnostic
We already had bookkeeping to track which statement in a multi-statement closure we were looking at, but this was only used for the 'reasonable time' diagnostic in the case that we hit the expression timer, which was almost never hit, and is now off by default. The scope, memory, and trial limits couldn't use this information, so they would always diagnose the entire target being type checked. Move it up from ExpressionTimer to ConstraintSystem, so that we get the right source location there too. Also, factor out some code duplication in BuilderTransform to ensure we get the same benefit for result builders applied to function bodies too.
1 parent a99aebb commit d8cf185

File tree

9 files changed

+180
-132
lines changed

9 files changed

+180
-132
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 19 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -236,36 +236,23 @@ struct RestrictionOrFix {
236236

237237

238238
class ExpressionTimer {
239-
public:
240-
using AnchorType = llvm::PointerUnion<Expr *, ConstraintLocator *>;
241-
242-
private:
243-
AnchorType Anchor;
244-
ASTContext &Context;
239+
ConstraintSystem &CS;
245240
llvm::TimeRecord StartTime;
246241

247242
/// The number of seconds from creation until
248243
/// this timer is considered expired.
249244
unsigned ThresholdInSecs;
250245

251-
bool PrintDebugTiming;
252246
bool PrintWarning;
253247

254248
public:
255249
const static unsigned NoLimit = (unsigned) -1;
256250

257-
ExpressionTimer(AnchorType Anchor, ConstraintSystem &CS,
258-
unsigned thresholdInSecs);
251+
ExpressionTimer(ConstraintSystem &CS, unsigned thresholdInSecs);
259252

260253
~ExpressionTimer();
261254

262-
AnchorType getAnchor() const { return Anchor; }
263-
264-
SourceRange getAffectedRange() const;
265-
266-
unsigned getWarnLimit() const {
267-
return Context.TypeCheckerOpts.WarnLongExpressionTypeChecking;
268-
}
255+
unsigned getWarnLimit() const;
269256
llvm::TimeRecord startedAt() const { return StartTime; }
270257

271258
/// Return the elapsed process time (including fractional seconds)
@@ -2159,7 +2146,9 @@ struct ClosureIsolatedByPreconcurrency {
21592146
/// solution of which assigns concrete types to each of the type variables.
21602147
/// Constraint systems are typically generated given an (untyped) expression.
21612148
class ConstraintSystem {
2149+
private:
21622150
ASTContext &Context;
2151+
SourceRange CurrentRange;
21632152

21642153
public:
21652154
DeclContext *DC;
@@ -5384,6 +5373,9 @@ class ConstraintSystem {
53845373
/// \returns The selected conjunction.
53855374
Constraint *selectConjunction();
53865375

5376+
void diagnoseTooComplex(SourceLoc fallbackLoc,
5377+
SolutionResult &result);
5378+
53875379
/// Solve the system of constraints generated from provided expression.
53885380
///
53895381
/// \param target The target to generate constraints from.
@@ -5481,6 +5473,8 @@ class ConstraintSystem {
54815473
compareSolutions(ConstraintSystem &cs, ArrayRef<Solution> solutions,
54825474
const SolutionDiff &diff, unsigned idx1, unsigned idx2);
54835475

5476+
void startExpressionTimer();
5477+
54845478
public:
54855479
/// Increase the score of the given kind for the current (partial) solution
54865480
/// along the current solver path.
@@ -5518,7 +5512,6 @@ class ConstraintSystem {
55185512
std::optional<unsigned> findBestSolution(SmallVectorImpl<Solution> &solutions,
55195513
bool minimize);
55205514

5521-
public:
55225515
/// Apply a given solution to the target, producing a fully
55235516
/// type-checked target or \c None if an error occurred.
55245517
///
@@ -5571,7 +5564,14 @@ class ConstraintSystem {
55715564
/// resolved before any others.
55725565
void optimizeConstraints(Expr *e);
55735566

5574-
void startExpressionTimer(ExpressionTimer::AnchorType anchor);
5567+
/// Set the current sub-expression (of a multi-statement closure, etc) for
5568+
/// the purposes of diagnosing "reasonable time" errors.
5569+
void startExpression(ASTNode node);
5570+
5571+
/// The source range of the target being type checked.
5572+
SourceRange getCurrentSourceRange() const {
5573+
return CurrentRange;
5574+
}
55755575

55765576
/// Determine if we've already explored too many paths in an
55775577
/// attempt to solve this expression.
@@ -5584,53 +5584,7 @@ class ConstraintSystem {
55845584
return range.isValid() ? range : std::optional<SourceRange>();
55855585
}
55865586

5587-
bool isTooComplex(size_t solutionMemory) {
5588-
if (isAlreadyTooComplex.first)
5589-
return true;
5590-
5591-
auto CancellationFlag = getASTContext().CancellationFlag;
5592-
if (CancellationFlag && CancellationFlag->load(std::memory_order_relaxed))
5593-
return true;
5594-
5595-
auto markTooComplex = [&](SourceRange range, StringRef reason) {
5596-
if (isDebugMode()) {
5597-
if (solverState)
5598-
llvm::errs().indent(solverState->getCurrentIndent());
5599-
llvm::errs() << "(too complex: " << reason << ")\n";
5600-
}
5601-
isAlreadyTooComplex = {true, range};
5602-
return true;
5603-
};
5604-
5605-
auto used = getASTContext().getSolverMemory() + solutionMemory;
5606-
MaxMemory = std::max(used, MaxMemory);
5607-
auto threshold = getASTContext().TypeCheckerOpts.SolverMemoryThreshold;
5608-
if (MaxMemory > threshold) {
5609-
// No particular location for OoM problems.
5610-
return markTooComplex(SourceRange(), "exceeded memory limit");
5611-
}
5612-
5613-
if (Timer && Timer->isExpired()) {
5614-
// Disable warnings about expressions that go over the warning
5615-
// threshold since we're arbitrarily ending evaluation and
5616-
// emitting an error.
5617-
Timer->disableWarning();
5618-
5619-
return markTooComplex(Timer->getAffectedRange(), "exceeded time limit");
5620-
}
5621-
5622-
auto &opts = getASTContext().TypeCheckerOpts;
5623-
5624-
// Bail out once we've looked at a really large number of choices.
5625-
if (opts.SolverScopeThreshold && NumSolverScopes > opts.SolverScopeThreshold)
5626-
return markTooComplex(SourceRange(), "exceeded scope limit");
5627-
5628-
// Bail out once we've taken a really large number of steps.
5629-
if (opts.SolverTrailThreshold && NumTrailSteps > opts.SolverTrailThreshold)
5630-
return markTooComplex(SourceRange(), "exceeded trail limit");
5631-
5632-
return false;
5633-
}
5587+
bool isTooComplex(size_t solutionMemory);
56345588

56355589
bool isTooComplex(ArrayRef<Solution> solutions) {
56365590
if (isAlreadyTooComplex.first)

lib/Sema/BuilderTransform.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,9 +1053,7 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
10531053

10541054
case SolutionResult::Kind::TooComplex:
10551055
reportSolutionsToSolutionCallback(salvagedResult);
1056-
func->diagnose(diag::expression_too_complex)
1057-
.highlight(func->getBodySourceRange());
1058-
salvagedResult.markAsDiagnosed();
1056+
cs.diagnoseTooComplex(func->getLoc(), salvagedResult);
10591057
return nullptr;
10601058
}
10611059

lib/Sema/CSSolver.cpp

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -829,9 +829,7 @@ bool ConstraintSystem::Candidate::solve(
829829

830830
// Allocate new constraint system for sub-expression.
831831
ConstraintSystem cs(DC, std::nullopt);
832-
833-
// Set up expression type checker timer for the candidate.
834-
cs.startExpressionTimer(E);
832+
cs.startExpression(E);
835833

836834
// Generate constraints for the new system.
837835
if (auto generatedExpr = cs.generateConstraints(E, DC)) {
@@ -1455,18 +1453,7 @@ ConstraintSystem::solve(SyntacticElementTarget &target,
14551453
return std::nullopt;
14561454

14571455
case SolutionResult::TooComplex: {
1458-
auto affectedRange = solution.getTooComplexAt();
1459-
1460-
// If affected range is unknown, let's use whole
1461-
// target.
1462-
if (!affectedRange)
1463-
affectedRange = target.getSourceRange();
1464-
1465-
getASTContext()
1466-
.Diags.diagnose(affectedRange->Start, diag::expression_too_complex)
1467-
.highlight(*affectedRange);
1468-
1469-
solution.markAsDiagnosed();
1456+
diagnoseTooComplex(target.getLoc(), solution);
14701457
return std::nullopt;
14711458
}
14721459

@@ -1501,6 +1488,19 @@ ConstraintSystem::solve(SyntacticElementTarget &target,
15011488
llvm_unreachable("Loop always returns");
15021489
}
15031490

1491+
void ConstraintSystem::diagnoseTooComplex(SourceLoc fallbackLoc,
1492+
SolutionResult &result) {
1493+
auto affectedRange = result.getTooComplexAt();
1494+
1495+
SourceLoc loc = (affectedRange ? affectedRange->Start : fallbackLoc);
1496+
auto diag = getASTContext().Diags.diagnose(loc, diag::expression_too_complex);
1497+
1498+
if (affectedRange)
1499+
diag.highlight(*affectedRange);
1500+
1501+
result.markAsDiagnosed();
1502+
}
1503+
15041504
SolutionResult
15051505
ConstraintSystem::solveImpl(SyntacticElementTarget &target,
15061506
FreeTypeVariableBinding allowFreeTypeVariables) {
@@ -1518,9 +1518,8 @@ ConstraintSystem::solveImpl(SyntacticElementTarget &target,
15181518

15191519
assert(!solverState && "cannot be used directly");
15201520

1521-
// Set up the expression type checker timer.
15221521
if (Expr *expr = target.getAsExpr())
1523-
startExpressionTimer(expr);
1522+
startExpression(expr);
15241523

15251524
if (generateConstraints(target, allowFreeTypeVariables))
15261525
return SolutionResult::forError();
@@ -1701,8 +1700,7 @@ bool ConstraintSystem::solveForCodeCompletion(
17011700
// Tell the constraint system what the contextual type is.
17021701
setContextualInfo(expr, target.getExprContextualTypeInfo());
17031702

1704-
// Set up the expression type checker timer.
1705-
startExpressionTimer(expr);
1703+
startExpression(expr);
17061704

17071705
shrink(expr);
17081706
}

lib/Sema/CSStep.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,13 @@ bool ConjunctionStep::attempt(const ConjunctionElement &element) {
823823
// (expression) gets a fresh time slice to get solved. This
824824
// is important for closures with large number of statements
825825
// in them.
826-
if (CS.Timer) {
826+
if (CS.Timer)
827827
CS.Timer.reset();
828-
CS.startExpressionTimer(element.getLocator());
828+
829+
{
830+
auto *locator = element.getLocator();
831+
auto anchor = simplifyLocatorToAnchor(locator);
832+
CS.startExpression(anchor ? anchor : locator->getAnchor());
829833
}
830834

831835
auto success = element.attempt(CS);

lib/Sema/CSStep.h

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -867,8 +867,7 @@ class ConjunctionStep : public BindingStep<ConjunctionElementProducer> {
867867

868868
/// The number of milliseconds until outer constraint system
869869
/// is considered "too complex" if timer is enabled.
870-
std::optional<std::pair<ExpressionTimer::AnchorType, unsigned>>
871-
OuterTimeRemaining = std::nullopt;
870+
std::optional<unsigned> OuterTimeRemaining = std::nullopt;
872871

873872
/// Conjunction constraint associated with this step.
874873
Constraint *Conjunction;
@@ -910,7 +909,7 @@ class ConjunctionStep : public BindingStep<ConjunctionElementProducer> {
910909

911910
if (cs.Timer) {
912911
auto remainingTime = cs.Timer->getRemainingProcessTimeInSeconds();
913-
OuterTimeRemaining.emplace(cs.Timer->getAnchor(), remainingTime);
912+
OuterTimeRemaining.emplace(remainingTime);
914913
}
915914
}
916915

@@ -925,11 +924,8 @@ class ConjunctionStep : public BindingStep<ConjunctionElementProducer> {
925924
if (HadFailure)
926925
restoreBestScore();
927926

928-
if (OuterTimeRemaining) {
929-
auto anchor = OuterTimeRemaining->first;
930-
auto remainingTime = OuterTimeRemaining->second;
931-
CS.Timer.emplace(anchor, CS, remainingTime);
932-
}
927+
if (OuterTimeRemaining)
928+
CS.Timer.emplace(CS, *OuterTimeRemaining);
933929
}
934930

935931
StepResult resume(bool prevFailed) override;

0 commit comments

Comments
 (0)