Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exponential compile time with recursive opaque type #87450

Closed
Skepfyr opened this issue Jul 25, 2021 · 11 comments · Fixed by #87546
Closed

Exponential compile time with recursive opaque type #87450

Skepfyr opened this issue Jul 25, 2021 · 11 comments · Fixed by #87546
Labels
A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. C-bug Category: This is a bug. I-compiletime Issue: Problems and improvements with respect to compile times. P-high High priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Skepfyr
Copy link
Contributor

Skepfyr commented Jul 25, 2021

Code

I tried this code:

fn bar() -> impl Fn() {
    wrap(wrap(wrap(wrap(foo()))))
}

fn foo() -> impl Fn() {
    wrap(wrap(wrap(wrap(wrap(wrap(wrap(foo())))))))
}

fn wrap(f: impl Fn()) -> impl Fn() {
    move || f()
}

I expected running cargo check to produce an error like:

error[E0720]: cannot resolve opaque type
 --> src/lib.rs:5:13
  |
5 | fn foo() -> impl Fn() {
  |             ^^^^^^^^^ recursive opaque type
6 |     ((wrap(wrap(wrap(wrap(wrap(foo())))))))
  |     --------------------------------------- returning here with type `impl Fn<()>`
...
9 | fn wrap(f: impl Fn()) -> impl Fn() {
  |                          --------- returning this opaque type `impl Fn<()>`

It does, but cargo check takes time exponential in the number of wrap calls, although doesn't appear to use any extra memory.
In foo the time take looks like:

Num wrapss Time taken (s)
4 0.5
5 2.1
6 28
7 436

I believe it's also exponential in the number of wrap calls in bar.

The reason I had code like this was because I was using nom's parser combinators which results in lots of nested closures. I spotted it because this caused rust-analyzer to hang because cargo clippy hung (it looks like check was the issue), I had to kill clippy to fix it.

Bisection results

I bisected this (with cargo-bisect-rustc) to find the regression in nightly-2019-10-16, which contained the following bors commits:

  1. Auto merge of Rollup of 4 pull requests #65433 - Centril:rollup-rzvry15, r=Centril
  2. Auto merge of Rollup of 14 pull requests #65454 - tmandry:rollup-0k6jiik, r=tmandry
  3. Auto merge of Update clippy #65450 - Manishearth:clippyup, r=Manishearth
  4. Auto merge of use precalculated dominators in explain_borrow #65172 - tanriol:explain_borrow-use-context-dominators, r=nagisa
  5. Auto merge of Update cargo, books #65445 - ehuss:update-cargo-books, r=alexcrichton

@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged

@Skepfyr Skepfyr added C-bug Category: This is a bug. regression-untriaged Untriaged performance or correctness regression. labels Jul 25, 2021
@rustbot rustbot added I-prioritize Issue: Indicates that prioritization has been requested for this issue. regression-from-stable-to-stable Performance or correctness regression from one stable version to another. and removed regression-untriaged Untriaged performance or correctness regression. labels Jul 25, 2021
@jyn514
Copy link
Member

jyn514 commented Jul 25, 2021

@Skepfyr what version of rust are you using? Is this also present on nightly?

@Skepfyr
Copy link
Contributor Author

Skepfyr commented Jul 25, 2021

Good question! This repros on the latest nightly (nightly-2021-07-24) and on stable (1.53.0 2021-06-17).

@klensy
Copy link
Contributor

klensy commented Jul 25, 2021

I bisected this (with cargo-bisect-rustc) to find the regression in nightly-2019-10-16, which contained the following bors commits:

Regression means that it was linear, or exponential, but smaller?

@jonas-schievink jonas-schievink added A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. I-compiletime Issue: Problems and improvements with respect to compile times. labels Jul 25, 2021
@Skepfyr
Copy link
Contributor Author

Skepfyr commented Jul 25, 2021

I didn't check....it looks to still be exponential just much quicker, and with a smaller base (~2 vs ~14):

nightly-2019-10-16

Num calls Time (s)
15 0.68
16 1.3
17 2.7
18 5.6
19 12
20 25

@ibraheemdev
Copy link
Member

#65293 looks related

@hkratz
Copy link
Contributor

hkratz commented Jul 26, 2021

Bisected to innocent-looking commit fe09bb5. Removing the caching from OpaqueTypeExpander in master does indeed fix the issue. I haven't looked at the how or why or where that causes (other) problems though.

cc @tmandry

hkratz added a commit to rusticstuff/rust that referenced this issue Jul 26, 2021
@jyn514
Copy link
Member

jyn514 commented Jul 26, 2021

Hmm, the only thing that looks expensive there is the hashing - I wonder if it's hashing substs over and over again without ever getting a cache hit? Hashing DefId has a fixed cost, but SubstsRef can be an arbitrary length: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/subst/type.InternalSubsts.html

@hkratz
Copy link
Contributor

hkratz commented Jul 29, 2021

#87546 cuts down on time for this testcase and others with invalid recursive opaque types by bailing out early if a recursion is found.

This fixes the regression but the check time is still exponential in the recursion:

#wraps time (s)
23 9.2
24 18.3
25 36.6

Not sure if that matters in practice though.

@apiraino
Copy link
Contributor

Assigning priority as discussed in the Zulip thread of the Prioritization Working Group.

@rustbot label -I-prioritize +P-high

@rustbot rustbot added P-high High priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Jul 29, 2021
@apiraino apiraino added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Jul 29, 2021
@jyn514
Copy link
Member

jyn514 commented Jul 29, 2021

This fixes the regression but the check time is still exponential in the recursion:

That's the same as before the regression, right? I definitely think that's a change worth making :) I expect the doubling will have a completely different cause.

@hkratz
Copy link
Contributor

hkratz commented Jul 29, 2021

That's the same as before the regression, right?

Yes, same ballpark.

hkratz added a commit to rusticstuff/rust that referenced this issue Jul 30, 2021
bors added a commit to rust-lang-ci/rust that referenced this issue Aug 1, 2021
…vidtwco

Bail on any found recursion when expanding opaque types

Fixes rust-lang#87450. More of a bandaid because it does not fix the exponential complexity of the type folding used for opaque type expansion.
@bors bors closed this as completed in aa465a5 Aug 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. C-bug Category: This is a bug. I-compiletime Issue: Problems and improvements with respect to compile times. P-high High priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants