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

Cache skolem types #14909

Merged
merged 1 commit into from
Apr 26, 2022
Merged

Cache skolem types #14909

merged 1 commit into from
Apr 26, 2022

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Apr 12, 2022

Skolem types were not cached, which means that any type containing a skolem type
was not cached either. This meant that the same match type with a skolem type as
selector was created many times instead of once, so its reduction was not cached
either. We now cache skolem types. It's a bet that in practice few skolem types are
created and that therefore the hashtable pollution with skolemtypes is less of
a problem than the potential problem of losing identity of types containing
skolem types.

Fixes #14903

@odersky
Copy link
Contributor Author

odersky commented Apr 12, 2022

test performance please

@dottybot
Copy link
Member

performance test scheduled: 1 job(s) in queue, 1 running.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit https://dotty-bench.epfl.ch/14909/ to see the changes.

Benchmarks is based on merging with main (73df472)

@odersky odersky requested a review from smarter April 13, 2022 08:26
@odersky odersky assigned odersky and smarter and unassigned odersky Apr 13, 2022
smarter added a commit to dotty-staging/dotty that referenced this pull request Apr 20, 2022
Previously, when reducing `a.T` we checked if the type of `a` was a subtype of
`RefinedType(.., T, TypeAlias(...))`, now we extend this check to handle
refinements where the `info` is a `TypeBounds` where both bounds are equal.

This solves two big issues at once:
- We can restore tests/pos/13491.scala to its original form from before scala#13780.
  The check for abstract types introduced by scala#13780 for soundness reasons is no
  longer hit because the type selection is reduced before we get to that point.
  This is important because parboiled2 relies on this and is therefore currently
  broken on 3.1.3-RC1 and main (sirthias/parboiled2#365).
- This fixes scala#14904 (slow compilation issue affecting parboiled2) without
  caching skolems (as in the alternative fix scala#14909). Again, this is due to the
  type containing skolem being reducible to a simpler type and therefore cacheable.
Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opened #14987 as an alternative to this PR which fixes the performance issue without requiring skolems to be cached.

smarter added a commit to dotty-staging/dotty that referenced this pull request Apr 20, 2022
Previously, when reducing `a.T` we checked if the type of `a` was a subtype of
`RefinedType(.., T, TypeAlias(...))`, now we extend this check to handle
refinements where the `info` is a `TypeBounds` where both bounds are equal.

This solves two big issues at once:
- We can restore tests/pos/13491.scala to its original form from before scala#13780.
  The check for abstract types introduced by scala#13780 for soundness reasons is no
  longer hit because the type selection is reduced before we get to that point.
  This is important because parboiled2 relies on this and is therefore currently
  broken on 3.1.3-RC1 and main (sirthias/parboiled2#365).
- This fixes scala#14903 (slow compilation issue affecting parboiled2) without
  caching skolems (as in the alternative fix scala#14909). Again, this is due to the
  type containing skolem being reducible to a simpler type and therefore cacheable.
@odersky odersky reopened this Apr 21, 2022
@odersky
Copy link
Contributor Author

odersky commented Apr 21, 2022

I think it's still worth to merge this, to prevent creating too many type copies in other situations

override def underlying(using Context): Type = info
def derivedSkolemType(info: Type)(using Context): SkolemType =
if (info eq this.info) this else SkolemType(info)
override def hashCode: Int = System.identityHashCode(this)

override def computeHash(bs: Binders): Int = identityHash(bs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return NotCached if info itself is not cache-able?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a skolem is in any case unique. We never merge two skolems. So it does not matter whether we
cache the info or not.

@@ -0,0 +1,22 @@
trait Wrapper[T] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is already present as https://github.com/lampepfl/dotty/blob/main/tests/pos/i14903a.scala, and without any test nor any hash table statistics it's hard for me to say if this change is a net positive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can argue by analogy to type variables. Both skolem types and type variables are locally generated, and both are usually part of more complicated types. Type variables are cached, so it makes sense to cache skolem types as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've edited the commit message to mention this and dropped the duplicate test.

michelou pushed a commit to michelou/scala3 that referenced this pull request Apr 25, 2022
Previously, when reducing `a.T` we checked if the type of `a` was a subtype of
`RefinedType(.., T, TypeAlias(...))`, now we extend this check to handle
refinements where the `info` is a `TypeBounds` where both bounds are equal.

This solves two big issues at once:
- We can restore tests/pos/13491.scala to its original form from before scala#13780.
  The check for abstract types introduced by scala#13780 for soundness reasons is no
  longer hit because the type selection is reduced before we get to that point.
  This is important because parboiled2 relies on this and is therefore currently
  broken on 3.1.3-RC1 and main (sirthias/parboiled2#365).
- This fixes scala#14903 (slow compilation issue affecting parboiled2) without
  caching skolems (as in the alternative fix scala#14909). Again, this is due to the
  type containing skolem being reducible to a simpler type and therefore cacheable.
Skolem types were not cached, which means that any type containing a skolem type
was not cached either. This meant that the same match type with a skolem type as
selector was created many times instead of once, so its reduction was not cached
either. We now cache skolem types. It's a bet that in practice few skolem types are
created and that therefore the hashtable pollution with skolemtypes is less of
a problem than the potential problem of losing identity of types containing
skolem types.

This was originally motivated by scala#14903 but that ended up being fixed in a
different way. It still seems like a good idea to do this since it matches what
we do for type variables.
@smarter smarter enabled auto-merge April 26, 2022 13:44
@smarter smarter merged commit 856ba1c into scala:main Apr 26, 2022
@smarter smarter deleted the fix-14903 branch April 26, 2022 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Slow compilation with chained dependent match types
3 participants