Emit mixin forwarders as bridges to avoid needing generic signatures#7843
Emit mixin forwarders as bridges to avoid needing generic signatures#7843lrytz merged 4 commits intoscala:2.13.xfrom
Conversation
06aee8c to
c86005a
Compare
This comment has been minimized.
This comment has been minimized.
|
I've added a second commit that removes |
|
I like it :-) One area to look for issues this may cause is overloading. See scala/bug#11271 where adding the |
|
I'd like to add that we're in unspecified territory here, as far as I can tell. The jvm spec just says "The So it's not really clear which methods can be marked bridge, and how different tools react to that. So we'd have to test a lot. |
I did see that issue but was a bit confused by the back-and-forth, tell me if my understanding of things is wrong here:
So my question is: were the methods considered ambiguous by ejc and intellij when they were emitted without the BRIDGE flag ? If so, then adding the BRIDGE flag somewhere shouldn't break these compilers. |
|
The methods only became ambiguous in ejc/IntelliJ once we added the flag. In the case of static forwarders, the end goal was to remove bridge versions anyway, we just didn't do it initially in 2.12 because it's (potentially) binary incompatible. But due to the overloading issues, we finally back-ported the change from 2.13 which removes the bridge overloads. The story is also summarized here: #7469. For mixin methods this cannot be done obviously, we need the mixin methods. |
|
My guess is that things started to go wrong because you were emitting bridge methods that don't override anything which javac would never do, mixin forwarders by definition override something, and moreover they forward to at least one of the method they override, so that seems closer to being correct. |
|
That's a promising guess. Another data point is what javac produces. Example: public class A {
public static abstract class U<T> {
public abstract <P> T foo(P p);
}
public static class V extends U<String> {
public <P> String foo(P p) { return ""; }
}
}We get (ASM syntax): So the bridge also doesn't have a signature. |
|
I've pushed an extra commit to exercise overload resolution, and not only do things appear to still work correctly, the test now passes on ecj whereas it considers the overload ambiguous on master! As an aside, I think It'd be really useful and not much work to run all the tests involving .java files with both javac and ecj. |
|
More good news: I was able to take a few existing test cases and verify that javac and ecj now see better types for them, see latest commit (static forwarders are still screwed up, but I think this really ought to be solved by generating them pre-erasure as has been suggested a few times in the past). |
|
Besides conflicting with #7752, this PR already breaks Scala.js: |
|
@sjrd So I just checked and JUnit will run test methods with the BRIDGE flag set, so I'd say Scala.js is the one doing something weird here, do you have a test case that demonstrates the issue that lead you to implement this check ? |
Yes, pretty much every test. Without that check, the test suite cannot be compiled without crashing the compiler using Scala >= 2.12.5. |
|
Well ... "retournement de situation", as we say in French: it turns out this PR also fixes scala-js/scala-js#3538, which was the issue that #7752 was meant to fix on Scala.js' side. So this PR does not conflict with #7752 as much as superseding it. And for the issue with @Test def toString()(): Unit = ...which somehow gets a bridge generated for it. (Yes, the double So I'll 👍 this PR. |
|
@SethTisue Could you run the community build on this PR ? If possible it would also be interesting to run it on 46cda53 which is this PR + don't emit the bridge flag for mixin forwarders, to see if this actually influences the community build at all. EDIT: Started a community build for this PR: https://scala-ci.typesafe.com/job/scala-2.13.x-integrate-community-build/1843/console |
Spring Framework skips Here's analagous logic in Hibernate. Graal seems to assume certain bytecode shapes within bridge methods (must delegage to a method with the same name). I don't know the context of this. |
It looks like things are more complicated than that, I found https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java which in particular lead me to the notion of "visibility bridge method" described in http://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html which is very interesting since it describes a case where javac emits a bridge from a subclass to a superclass!, and the way Spring checks for that should cover the bridges in this PR too. So arguably, any annotation framework that doesn't handle this is broken for Java too, but since it's a fairly rare thing, to be conservative we could choose to treat mixin methods with runtime-visible annotations specially:
|
|
The community build run looks OK to me given that the "unexpected failures" match the failures in a recent commit from master. |
The compiler spends a lot of effort trying to generate correct generic signatures for mixin forwarders, but is still not correct as witnessed by scala/bug#8905 among others. However, it seems that if we just emit them with the BRIDGE flag set, both javac and ecj (the Eclipse Java Compiler) will be smart enough to go look for the signature in the overridden methods, which is exactly what we want. This means that we should be able to get rid of `cloneBeforeErasure` and related code, though that would conflict with scala#7752. Because history always repeats itself, it turns out that this used to be how mixin forwarders were emitted until fe94bc7 changed this 7 years ago. The commit only says that this "has deleterious effects since many tools ignore bridge methods" which is too vague for me to do anything about. Fixes scala/bug#8905 and probably countless others.
This is no longer needed after the previous commit.
Not only does this test still pass now that mixin forwarders are emitted as bridge: it used to cause an ambiguous overload error on ecj and now works correctly.
* Pros
- When forwarding before erasure, we might end up needing a bridge,
the non-bridged forwarder therefore serves no practical purpose
and can cause clashes. These are problematic for two reasons:
1. Valid Scala 2 code might break, and can only be fixed in
binary incompatible way, like with
scala/scala@4366332
and
scala/scala@e3ef657
2. Clashes might not be detected under separate compilation, as
demonstrated by the previous commit.
Forwarding after erasure solves both of these problems.
- Scala 2 has always done it this way, so we're less likely to run
into surprising problems.
* Cons
- When forwarding after erasure, generic signatures will be missing,
Scala 2 tries to restore this information but that doesn't always
work (scala/bug#8905). We'll either have
to do something similar, or investigate a different solution like
scala/scala#7843.
* Pros
- When forwarding before erasure, we might end up needing a bridge,
the non-bridged forwarder therefore serves no practical purpose
and can cause clashes. These are problematic for two reasons:
1. Valid Scala 2 code might break, and can only be fixed in
binary incompatible way, like with
scala/scala@4366332
and
scala/scala@e3ef657
2. Clashes might not be detected under separate compilation, as
demonstrated by the previous commit.
Forwarding after erasure solves both of these problems.
- Scala 2 has always done it this way, so we're less likely to run
into surprising problems.
- Compiling the 2.12 standard library got 20% faster, I suspect that
the previous hotspot in `isCurrent` is no longer so hot when run
after erasure, so I removed the comment there.
* Cons
- When forwarding after erasure, generic signatures will be missing,
Scala 2 tries to restore this information but that doesn't always
work (scala/bug#8905). We'll either have
to do something similar, or investigate a different solution like
scala/scala#7843.
|
@lrytz @retronym What about the special case for methods with runtime-visible annotations I suggested in #7843 (comment) ? Emitting less forwarders would be binary incompatible so there's not much time left to experiment with that. |
|
I'd keep it as it is now and see if we can avoid special cases like this.. |
|
OK. I guess there's always the option of keeping these forwarders for binary-compatibility but dropping the BRIDGE flag for them. |
|
The code in Graal is used mostly for things in our core which are all in Java and was written for the bytecode that is emitted by Eclipse and For this particular case, I think it is more time-efficient to fix this issue if someone encounters it by using Scala for core Graal things. @retronym thanks for letting us know about the change. |
This is the same scheme I proposed in scala/scala#7843 which sidesteps all the issues regarding mixin forwarders and generic signatures, see the discussion in that PR for more information.
This is the same scheme I proposed in scala/scala#7843 which sidesteps all the issues regarding mixin forwarders and generic signatures, see the discussion in that PR for more information.
This is the same scheme I proposed in scala/scala#7843 which sidesteps all the issues regarding mixin forwarders and generic signatures, see the discussion in that PR for more information. Tests imported from scalac, some of the comments in them might not be correct for Dotty anymore.
This is the same scheme I proposed in scala/scala#7843 which sidesteps all the issues regarding mixin forwarders and generic signatures, see the discussion in that PR for more information. Tests imported from scalac, some of the comments in them might not be correct for Dotty anymore.
This is the same scheme I proposed in scala/scala#7843 which sidesteps all the issues regarding mixin forwarders and generic signatures, see the discussion in that PR for more information. Tests imported from scalac, some of the comments in them might not be correct for Dotty anymore.
This is the same scheme I proposed in scala/scala#7843 which sidesteps all the issues regarding mixin forwarders and generic signatures, see the discussion in that PR for more information. Tests imported from scalac, some of the comments in them might not be correct for Dotty anymore.
This is the same scheme I proposed in scala/scala#7843 which sidesteps all the issues regarding mixin forwarders and generic signatures, see the discussion in that PR for more information. Tests imported from scalac, some of the comments in them might not be correct for Dotty anymore.
They're emitted as bridge since scala#7843 and a final bridge is a weird concept that never comes up in Java (in particular, according to #11485 ByteBuddy assumes that it can always override bridges). So let's just drop the "final" part which is not essential for interop. Fixes #11485.
They're emitted as bridge since scala#7843 and a final bridge is a weird concept that never comes up in Java (in particular, according to scala/bug#11485 ByteBuddy assumes that it can always override bridges). So let's just drop the "final" part which is not essential for interop. Fixes scala/bug#11485.
They're emitted as bridge since scala#7843 and a final bridge is a weird concept that never comes up in Java (in particular, according to scala/bug#11485 ByteBuddy assumes that it can always override bridges). So let's just drop the "final" part which is not essential for interop. Fixes scala/bug#11485. Note: the final flag cannot be dropped before the backend because the inliner relies on this flag being present to know what can be inlined.
They're emitted as bridge since scala#7843 and a final bridge is a weird concept that never comes up in Java (in particular, according to scala/bug#11485 ByteBuddy assumes that it can always override bridges). So let's just drop the "final" part which is not essential for interop. Fixes scala/bug#11485. Note: the final flag cannot be dropped before the backend because the inliner relies on this flag being present to know what can be inlined.
[edit] reverted in #8037
The compiler spends a lot of effort trying to generate correct generic
signatures for mixin forwarders, but is still not correct as witnessed
by scala/bug#8905 among others. However, it seems that if we just emit
them with the BRIDGE flag set, both javac and ecj (the Eclipse Java
Compiler) will be smart enough to go look for the signature in the
overridden methods, which is exactly what we want.
This means that we should be able to get rid ofEDIT: That's not actually a problem, see #7843 (comment)cloneBeforeErasureandrelated code, though that would conflict with #7752.
Because history always repeats itself, it turns out that this used to be
how mixin forwarders were emitted until
fe94bc7 changed this 7 years ago. The
commit only says that this "has deleterious effects since many tools
ignore bridge methods" which is too vague for me to do anything about.
Fixes scala/bug#8905 and probably countless others.