-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Some fixes for AnnotatedTypes mapping #19957
Conversation
✅ Fixed: this was due to an interaction between 2 test failures in
Relevant tests: @Test def runNeg = scala(f("@main def Test = prin", ".sc"))(1)
@Test def runRun = scala(f("@main def Test = ???", ".sc"))(1)
@Test def runPos = scala(f("@main def Test = ()", ".sc"))(0) // Failing
@Test def scNeg = scalac("-script", f("@main def Test = prin", ".sc"))(1)
@Test def scRun = scalac("-script", f("@main def Test = ???", ".sc"))(1)
@Test def scPos = scalac("-script", f("@main def Test = ()", ".sc"))(0) // Failing Run with:
|
This comment was marked as outdated.
This comment was marked as outdated.
878cc3e
to
131f2e3
Compare
95a664f
to
1cf88db
Compare
4a0cff8
to
58e5f74
Compare
test performance please |
performance test scheduled: 4 job(s) in queue, 1 running. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/19957/ to see the changes. Benchmarks is based on merging with main (6d29951) |
test performance please |
(let's do it again, to see whether this is a fluke or a trend) |
performance test scheduled: 4 job(s) in queue, 1 running. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/19957/ to see the changes. Benchmarks is based on merging with main (6af2bcf) |
0d1183d
to
76492df
Compare
|
hash | jvm | benchmark | time median | time min | time max | time error minus | time error plus |
---|---|---|---|---|---|---|---|
a6f4514 | openjdk | applyTypeMap(id,v1) | 15,857,622 | 14,224,785 | 17,418,021 | 1,632,837 | 1,560,399 |
a6689bc | openjdk | applyTypeMap(id,v1) | 16,294,074 | 14,841,272 | 17,925,590 | 1,452,802 | 1,631,516 |
a6f4514 | openjdk | applyTypeMap(id,v2) | 6,970,539 | 6,429,362 | 7,380,593 | 541,177 | 410,053 |
a6689bc | openjdk | applyTypeMap(id,v2) | 7,553,913 | 7,221,825 | 7,848,735 | 332,087 | 294,822 |
a6f4514 | openjdk | applyTypeMap(id,v3) | 951,639 | 908,826 | 1,083,239 | 42,813 | 131,599 |
a6689bc | openjdk | applyTypeMap(id,v3) | 1,080,714 | 1,024,838 | 1,120,194 | 55,875 | 39,480 |
a6f4514 | openjdk | applyTypeMap(id,v4) | 449,014 | 380,642 | 491,410 | 68,372 | 42,396 |
a6689bc | openjdk | applyTypeMap(id,v4) | 480,909 | 361,007 | 493,837 | 119,902 | 12,928 |
a6f4514 | openjdk | applyTypeMap(mapInts,v1) | 1,361,795 | 1,162,717 | 1,433,297 | 199,078 | 71,502 |
a6689bc | openjdk | applyTypeMap(mapInts,v1) | 1,395,522 | 1,333,792 | 1,473,311 | 61,730 | 77,790 |
a6f4514 | openjdk | applyTypeMap(mapInts,v2) | 881,566 | 831,334 | 931,736 | 50,232 | 50,171 |
a6689bc | openjdk | applyTypeMap(mapInts,v2) | 908,371 | 867,399 | 958,703 | 40,972 | 50,332 |
a6f4514 | openjdk | applyTypeMap(mapInts,v3) | 151,217 | 140,365 | 157,185 | 10,852 | 5,968 |
a6689bc | openjdk | applyTypeMap(mapInts,v3) | 34,095 | 19,082 | 35,680 | 15,014 | 1,585 |
a6f4514 | openjdk | applyTypeMap(mapInts,v4) | 47,729 | 43,171 | 49,587 | 4,558 | 1,858 |
a6689bc | openjdk | applyTypeMap(mapInts,v4) | 14,768 | 9,174 | 15,650 | 5,595 | 882 |
I also ran with GraalVM; the results are similar to OpenJDK.
Conclusions
As expected, runtime when are types don't change are unaffected, as we don't enter the TreeTypeMap
in these cases.
When we do need to enter the TreeTypeMap
(in the cases applyTypeMap(mapInts,v3)
and applyTypeMap(mapInts,v4)
), the runtime in the fixed version is significantly slower (7x and 3x slower). This is expected as applying a TreeTypeMap
is significantly slower than applying a TypeMap
or TypeAccumulator
.
I addressed both questions (in 2 separate commits to ease review, but I can squash them afterward). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still have several questions about the logic. Why the duplication? Why single out PostTyper? And, why does transforming an annotation require its symbols to be copied separately? Isn't that logic already handled in TreeTypeMap?
val diff = findDiff(NoType, args) | ||
if tm.isRange(diff) then EmptyAnnotation | ||
else if diff.exists then derivedAnnotation(tm.mapOver(tree)) | ||
else if diff.exists then | ||
// If the annotation has been transformed, we need to make sure that the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does transforming an annotation duplicate symbols? Don't we forget the old copy and use only the transformed one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that we have an Annotated
tree whose type is an AnnotatedType
, the Annotated
tree has an annot
field which is a tree and the AnnotatedType
has .annot.tree
. Initially, these trees are equal, but in PostTyper we call transformAnnot
on both separately, so if PostTyper does some transformation, we end up with two non-referentially-equal trees that define the same symbol and crash in pickler (when PostTyper#transformAnnot does nothing for a specific annotation, then there is only one shared tree and pickler is happy).
I think we could fix this locally in pickler by not pickling the annot
field of the Annotated tree and instead reconstructing it from the type on unpickling, but more generally for type trees, it seems that Annotated#annot
should just be a forward call to .tpe.tree.annot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I spoke too soon, it seems that in practice the Annotated
trees are kept in sync with their AnnotatedType
. Things start going wrong when that AnnotatedType
is used to type another tree (for example in
tpd.TypeTree(relocate(sym.info)) |
AnnotatedType
is shared by multiple trees, this PR assumes this is always the case and defensively performs symbol copies.
try | ||
val res = transform(annot) | ||
if res ne annot then | ||
// If the annotation has been transformed, we need to make sure that the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to handle this here as well? I thought annotation transforms already took care of this themselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed orally at the time I believe, PostTyper
doesn't use TypeMap
, it instead calls transform
with the annotation manually, hence the need to also copy it manually.
scala3/compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Lines 157 to 166 in 5e83606
private def transformAnnot(annot: Tree)(using Context): Tree = { | |
val saved = inJavaAnnot | |
inJavaAnnot = annot.symbol.is(JavaDefined) | |
if (inJavaAnnot) checkValidJavaAnnotation(annot) | |
try transform(annot) | |
finally inJavaAnnot = saved | |
} | |
private def transformAnnot(annot: Annotation)(using Context): Annotation = | |
annot.derivedAnnotation(transformAnnot(annot.tree)) |
The following still fails with this PR: class dummy(b: Boolean) extends annotation.StaticAnnotation with annotation.RefiningAnnotation
object Test:
def foo(elem: Int, bla: Int @dummy(elem == 0)) = bla -- Error: try/annn2.scala:9:37 -------------------------------------------------
9 | def foo(elem: Int, bla: Int @dummy(elem == 0)) = bla
| ^^^^^^^^^
| undefined: elem.== # -1: TermRef(TermParamRef(elem),==) at typer The initial typing of the arguments work, but when we want to create the MethodType using scala3/compiler/src/dotty/tools/dotc/core/Types.scala Lines 4697 to 4700 in d490d13
To avoid this issue it seems we'd need |
I rebased on top of |
I squashed two commits together to minimize the changes. No code changes. |
`Annotation.mapWith` maps an `Annotation` with a type map `tm`. As an optimization, this function first checks if `tm` would result in any change (by traversing the annotation’s argument trees with a `TreeAccumulator`) before applying `tm` to the whole annotation tree. This optimization had two problems: 1. it didn’t include type parameters, and 2. it used `frozen_=:=` to compare types, which didn’t work as expected with `NoType`. This commit fixes these issues. Additionally, positions of trees that appear only inside `AnnotatedType` were not pickled. This commit also fixes this.
Backports #19957 to the 3.3.5. PR submitted by the release tooling. [skip ci]
In a nutshell: when mapping annotated types, we can currently end up with the same symbol being declared in distinct trees, which crashes the pickler as it expects each symbol to be declared in a single place. See #19957 (comment) and #19957 (comment) for more context. This PR ensures that all symbols in annotation trees are different by creating fresh symbols for all symbols in annotation tree during `PostTyper`. In my [previous attempt](ab70f18) which was discussed on #19957, I did it in `Annotations.mapWith`. Here, it's only done once in `PostTyper`, so this is more lightweight. Fixes #17939, fixes #19846 and fixes (partially?) #20272.
Fixes #5789,
fixes#17939, fixes #18064 andfixes#19846.Description of 3e1667c: