-
Notifications
You must be signed in to change notification settings - Fork 3.1k
SI-7974 Fix broken 'Symbol-handling code in CleanUp #3149
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
Conversation
|
Maybe @xeno-by has an idea about https://github.com/scala/scala/blob/master/src/reflect/scala/reflect/internal/Definitions.scala#L1223 ... git blame says that the comments are from him. |
Yeah, this behavior has been puzzling me as well, but I haven't been able On 18 November 2013 02:23, soc notifications@github.com wrote:
|
It would seem that any call to You could add a followup commit with a more wide-spread fix Change: def getMemberIfDefined(owner: Symbol, name: Name): Symbol =
owner.info.nonPrivateMember(name) To: def getMemberIfDefined(owner: Symbol, name: Name): Symbol =
// findMember considered harmful after erasure; e.g.
//
// scala> exitingErasure(Symbol_apply).isOverloaded
// res27: Boolean = true
//
enteringPhaseNotLaterThan(currentRun.erasurePhase)(
owner.info.nonPrivateMember(name)
) And remove the localised fix, and @xeno-by's comment workaround in Back to the motivating topic of this PR (static field initializers not going into
|
@retronym Wow, thanks a lot for debugging this! Just trying to understand this correctly ... why do we gain that second member after erasure in the first place? |
Yes, I think the pattern is that we have a few specialized locations were work is done to make statics work correctly, but we are missing the bigger picture. I'm already looking into https://github.com/scala/scala/blob/master/src/compiler/scala/tools/nsc/transform/Constructors.scala#L634 were I think the appropriate general check is missing. |
Won't this cause problems because Is something like https://github.com/scala/scala/blob/master/src/reflect/scala/reflect/internal/util/TraceSymbolActivity.scala#L90 a better way? |
You are right. We can still get to it with the method used in |
Looks like 144e416 is failing due to whitespace differences ... is there some magic incantation for that, or should I just call |
sigh ... JavapTest is pretty terrible to debug ... I think I finally figured it out. |
I was going to say something like, Wow, someone is actually using JavapTest, but I didn't want to discourage you. Was it the |
No, it just feels like the implementation does extra work in ca 10000 se of an failure to not actually tell anyone what the problem was ... but I figured it out now. :-) |
You should actually use a bytecodetest to decouple from the rep. On Wednesday, November 20, 2013, soc wrote:
|
PLS REBUILD ALL typesafehub/sbt-builds-for-ide@08cfc9d should fix ide build breakage (in my local testing, sbt 0.13 was the default) |
(kitty-note-to-self: ignore 28868185) |
PLS REBUILD/pr-scala@1b80478 |
(kitty-note-to-self: ignore 28986460) |
PLS REBUILD/pr-scala@37109b4 |
If this fails because javap decided to emit ”extends java.lang.Object” in javap 6 but stopped doing that in javap 7, I'll pull my hair out ... |
(kitty-note-to-self: ignore 28990076) |
PLS REBUILD ALL |
(kitty-note-to-self: ignore 29077590) |
Wow, this must have been my most painful, nerve-wrecking test case ever. |
Thanks for persevering, @soc! |
We should not involve the REPL and Here's an example that used ASM to output the decompiled bytecode and compares it against a checkfile: retronym@178c37bdded#diff-9821ea5ae9849e5bd4c52285ec2e56f5R5 |
case x: TermSymbol => x | ||
case _ => fatalMissingSymbol(owner, name, "method") | ||
} | ||
} | ||
|
||
private lazy val erasurePhase = findPhaseWithName("erasure") |
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.
This might be brittle in a compiler that doesn't have an "erasure" phase. (Maybe ScalaJS?)
How about?
final def findPhaseBeforeErasure: Phase = {
var ph = phase
while (ph != NoPhase && ph.erasedTypes) {
ph = ph.prev
}
if (ph eq NoPhase) phase else ph
}
def enteringPhaseNotLaterThanErasure[T](op: => T): T =
enteringPhaseNotLaterThan(findPhaseBeforeErasure)(op)
...
enteringPhaseNotLaterThanErasure(owner.info.nonPrivateMember(name))
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.
Wouldn't this skip the phase exactly before erasure, too?
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.
Let's leave that determination up to a little test case. If you're right, I'll just say that it only ever intended as pseudocode 😃
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.
Let's hope this is the last time I have to touch this code ... otherwise I'm pulling my hair out. :-)
@soc let me know if you need a hand converting this REPL test to a BytecodeTest. |
One example were this would lead to subtle bugs otherwise is Symbol_apply, where after erasure an overloaded symbol containing Symbol#apply and UniquenessCache#apply is returned. // findMember considered harmful after erasure; e.g. // // scala> exitingErasure(Symbol_apply).isOverloaded // res27: Boolean = true
Looks like the transformation did never happen because the pattern failed to match. Why did the pattern fail to match? Because the Symbol.apply we see in the tree claims to be a method while Symbol_apply defined in Definitions wants to be a value. This issue was caused because nonPrivateMember starts spitting out overloaded symbols after erasure. This issue has been fixed in the earlier commit, so what happens in this commit is adding tests and fixing documentation.
@retronym Replaced JavapTest with BytecodeTest. Is this better now? |
LGTM. Sorry about the delay in reviewing. /cc @axel22. I'm curious what sort of code you originally encountered that had bottlenecks from Symbol lookup that warranted this hoisting in the first place. Why not just use string literals in performance sensitive code? |
val result = | ||
classString.split('\n') | ||
.dropWhile(elem => elem != "public class Symbols {") | ||
.filterNot(elem => elem.startsWith(" @Lscala/reflect/ScalaSignature") || elem.startsWith(" ATTRIBUTE ScalaSig")) |
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.
Nitpick: this test could have been a touch more focussed by doing this filtering at the ASM level, rather than with Strings.
Further more, the checkfile contains some irrelevant details, like line numbers (try skipDebugInfo = true
next time).
But I'll give you a pass this time as this PR has been spinning for too long.
Fix broken 'Symbol-handling code in CleanUp
In Scala 2.8.2, an optimization was added to create a static cache for Symbol literals (ie, the results of `Symbol.apply("foo"))`. This saves the map lookup on the second pass through code. This actually was broken somewhere during the Scala 2.10 series, after the addition of an overloaded `apply` method to `Symbol`. The cache synthesis code was made aware of the overload and brought back to working condition recently, in scala#3149. However, this has uncovered a latent bug when the Symbol literals are defined with traits. One of the enclosed tests failed with: jvm > t8933b-run.log java.lang.IllegalAccessError: tried to access field MotherClass.symbol$1 from class MixinWithSymbol$class at MixinWithSymbol$class.symbolFromTrait(A.scala:3) at MotherClass.symbolFromTrait(Test.scala:1) This commit simply disables the optimization if we are in a trait. Alternative fixes might be: a) make the static Symbol cache field public / b) "mixin" the static symbol cache. Neither of these seem worth the effort and risk for an already fairly situational optimization. Here's how the optimization looks in a class: % cat sandbox/test.scala; qscalac sandbox/test.scala && echo ":javap C" | qscala; class C { 'a; 'b } Welcome to Scala version 2.11.5-20141106-145558-aa558dce6d (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_20). Type in expressions to have them evaluated. Type :help for more information. scala> :javap C Size 722 bytes MD5 checksum 6bb00189166917254e8d40499ee7c887 Compiled from "test.scala" public class C { public static {}; descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 0: getstatic #16 // Field scala/Symbol$.MODULE$:Lscala/Symbol$; 3: ldc #18 // String a 5: invokevirtual #22 // Method scala/Symbol$.apply:(Ljava/lang/String;)Lscala/Symbol; 8: putstatic #26 // Field symbol$1:Lscala/Symbol; 11: getstatic #16 // Field scala/Symbol$.MODULE$:Lscala/Symbol$; 14: ldc #28 // String b 16: invokevirtual #22 // Method scala/Symbol$.apply:(Ljava/lang/String;)Lscala/Symbol; 19: putstatic #31 // Field symbol$2:Lscala/Symbol; 22: return public C(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #34 // Method java/lang/Object."<init>":()V 4: getstatic #26 // Field symbol$1:Lscala/Symbol; 7: pop 8: getstatic #31 // Field symbol$2:Lscala/Symbol; 11: pop 12: return }
In Scala 2.8.2, an optimization was added to create a static cache for Symbol literals (ie, the results of `Symbol.apply("foo"))`. This saves the map lookup on the second pass through code. This actually was broken somewhere during the Scala 2.10 series, after the addition of an overloaded `apply` method to `Symbol`. The cache synthesis code was made aware of the overload and brought back to working condition recently, in scala#3149. However, this has uncovered a latent bug when the Symbol literals are defined with traits. One of the enclosed tests failed with: jvm > t8933b-run.log java.lang.IllegalAccessError: tried to access field MotherClass.symbol$1 from class MixinWithSymbol$class at MixinWithSymbol$class.symbolFromTrait(A.scala:3) at MotherClass.symbolFromTrait(Test.scala:1) This commit simply disables the optimization if we are in a trait. Alternative fixes might be: a) make the static Symbol cache field public / b) "mixin" the static symbol cache. Neither of these seem worth the effort and risk for an already fairly situational optimization. Here's how the optimization looks in a class: % cat sandbox/test.scala; qscalac sandbox/test.scala && echo ":javap C" | qscala; class C { 'a; 'b } Welcome to Scala version 2.11.5-20141106-145558-aa558dce6d (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_20). Type in expressions to have them evaluated. Type :help for more information. scala> :javap C Size 722 bytes MD5 checksum 6bb00189166917254e8d40499ee7c887 Compiled from "test.scala" public class C { public static {}; descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 0: getstatic #16 // Field scala/Symbol$.MODULE$:Lscala/Symbol$; 3: ldc #18 // String a 5: invokevirtual #22 // Method scala/Symbol$.apply:(Ljava/lang/String;)Lscala/Symbol; 8: putstatic #26 // Field symbol$1:Lscala/Symbol; 11: getstatic #16 // Field scala/Symbol$.MODULE$:Lscala/Symbol$; 14: ldc #28 // String b 16: invokevirtual #22 // Method scala/Symbol$.apply:(Ljava/lang/String;)Lscala/Symbol; 19: putstatic #31 // Field symbol$2:Lscala/Symbol; 22: return public C(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #34 // Method java/lang/Object."<init>":()V 4: getstatic #26 // Field symbol$1:Lscala/Symbol; 7: pop 8: getstatic #31 // Field symbol$2:Lscala/Symbol; 11: pop 12: return } fixup
A classic mistake of discarding a non-trivial qualifier. We actually should have fixed this before merging scala#3149, as it was raised in review, but I suppose we got too caught up in the difficulty of resolving the right overload of `Symbol_apply` that we forgot.
The transformation did never happen because the pattern
failed to match. Why did the pattern fail to match? Because
Symbol_apply turned into an overloaded Symbol after erasure.