-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Optimizations in Namer and Typer #5875
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
https://scala-ci.typesafe.com/job/scala-2.12.x-validate-test/4897/consoleText
|
/rebuild |
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.
Nice to see cleaner & faster go hand in hand. A few suggestions, but otherwise looks great!
@@ -588,9 +590,12 @@ trait Implicits { | |||
// We can only rule out a subtype relationship if the left hand | |||
// side is a class, else we may not know enough. | |||
case tr1 @ TypeRef(_, sym1, _) if sym1.isClass => | |||
def hasMember(tp: Type, name: 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.
Could we narrow this helper method's scope a bit? typeRefHasNoMember
? I fear a future refactoring could pull this out and miss that it's only valid under certain assumptions.
def pop(): Unit = () | ||
def inMode(mode: Mode, tree: Tree): Tree = tree | ||
} | ||
class CheckingCheckDead() extends CheckDead { |
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.
final
?
// for use with silent type checking to when we can't have results with undetermined type params | ||
// note that this captures the context var | ||
val isMonoContext = (_: Any) => context.undetparams.isEmpty | ||
private var _checkDead: CheckDead = _ |
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.
private[this]
?
Sorry, something went wrong.
All reactions
-
👍 1 reaction
@@ -196,11 +197,13 @@ abstract class SymbolTable extends macros.Universe | |||
final def pushPhase(ph: Phase): Phase = { | |||
val current = phase | |||
phase = ph | |||
phStack ::= ph | |||
phStack(phStackIndex) = ph |
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.
bounds check, just in case?
Sorry, something went wrong.
All reactions
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 was happy to let an ArrayIndexOutOfBoundException
occur in that case rather than adding explicit code here to handle it.
Sorry, something went wrong.
All reactions
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 alternative would be to use an ArrayBuffer
, which doesn't require a hard limit to the depth of recursive atPhase
-s. But I was being a bit paranoid with performance.
Sorry, something went wrong.
All reactions
@@ -171,11 +171,12 @@ abstract class SymbolTable extends macros.Universe | |||
final val NoRunId = 0 | |||
|
|||
// sigh, this has to be public or enteringPhase doesn't inline. | |||
var phStack: List[Phase] = Nil | |||
var phStack: Array[Phase] = new Array(128) |
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] Only store phase name?
Sorry, something went wrong.
All reactions
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.
Storage wise, there isn't a difference, both are just already allocated Object
refs.
If we store the phase name, we have to eagerly call the virtual Phase.name
in phase_=
, even though under normal circumstances we don't use the result.
In general, It would be good to avoid retaining Phase
-s as fields in Global
so they are eligible for GC after the Run
that spawned them has been collected. But we know we'll end up with an empty phase stack here at the end of the run, so it doesn't make a practical difference.
Sorry, something went wrong.
All reactions
Non local returns aren't eliminated after inlined in 2.11 or 2.12 ``` ⚡ scala Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_112). Type in expressions for evaluation. Or try :help. scala> @inlune def foo(a: => Any) = if ("".isEmpty) a else "" <console>:11: error: not found: type inlune @inlune def foo(a: => Any) = if ("".isEmpty) a else "" ^ scala> @inline def foo(a: => Any) = if ("".isEmpty) a else "" foo: (a: => Any)Any scala> class InlineReturn { def test: Any = foo(return "") } defined class InlineReturn scala> :javap -c InlineReturn#test public java.lang.Object test(); Code: 0: new #4 // class java/lang/Object 3: dup 4: invokespecial #32 // Method java/lang/Object."<init>":()V 7: astore_1 8: getstatic #36 // Field $line4/$read$$iw$$iw$.MODULE$:L$line4/$read$$iw$$iw$; 11: aload_1 12: invokedynamic #59, 0 // InvokeDynamic #0:apply:(Ljava/lang/Object;)Lscala/Function0; 17: invokevirtual #63 // Method $line4/$read$$iw$$iw$.foo:(Lscala/Function0;)Ljava/lang/Object; 20: goto 44 23: astore_2 24: aload_2 25: invokevirtual #66 // Method scala/runtime/NonLocalReturnControl.key:()Ljava/lang/Object; 28: aload_1 29: if_acmpne 39 32: aload_2 33: invokevirtual #69 // Method scala/runtime/NonLocalReturnControl.value:()Ljava/lang/Object; 36: goto 41 39: aload_2 40: athrow 41: goto 44 44: areturn Exception table: from to target type 8 20 23 Class scala/runtime/NonLocalReturnControl ``` ``` ⚡ ~/scala/2.11.8/bin/scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_112). Type in expressions for evaluation. Or try :help. scala> @inline def foo(a: => Any) = if ("".isEmpty) a else "" foo: (a: => Any)Any scala> class InlineReturn { def test: Any = foo(return "") } defined class InlineReturn scala> :javap -c InlineReturn#test public java.lang.Object test(); Code: 0: new #4 // class java/lang/Object 3: dup 4: invokespecial #13 // Method java/lang/Object."<init>":()V 7: astore_1 8: getstatic #19 // Field .MODULE$:L; 11: new #21 // class InlineReturn$$anonfun$test$1 14: dup 15: aload_0 16: aload_1 17: invokespecial #24 // Method InlineReturn$$anonfun$test$1."<init>":(LInlineReturn;Ljava/lang/Object;)V 20: invokevirtual #28 // Method .foo:(Lscala/Function0;)Ljava/lang/Object; 23: goto 39 26: astore_2 27: aload_2 28: invokevirtual #31 // Method scala/runtime/NonLocalReturnControl.key:()Ljava/lang/Object; 31: aload_1 32: if_acmpne 40 35: aload_2 36: invokevirtual #34 // Method scala/runtime/NonLocalReturnControl.value:()Ljava/lang/Object; 39: areturn 40: aload_2 41: athrow Exception table: from to target type 8 26 26 Class scala/runtime/NonLocalReturnControl scala> :quit ```
We can avoid the call altogether in some cases.
Rather than doing this generically with a traversal in `atPos`. This method is hot enough to be as frugal as possible.
As it turns out, all our fast track macros are defined as `def foo = macro ???`, rather than `def foo = ???`. We can assume that the MACRO flag is authoritative.
When using the primary constructor paramater trees as prototypes for the parameter trees of the apply method, restrict tree duplication and reset attrs to the primary constructor, rather than the entire case class body.
We're only interested in whether or not a candidate implicit type has a member with a given name, which we can determine cheaply by checking if any ancestor has a so-named decl. We avoid both the as-seen-froms that FindMember uses to differentiate overriding and overloading, and creation of overloaded symbols.
…bility Avoids lots of calls to `isFunctionSymbol`.
If we're already in the requested phase, avoid the bookkeeping in atPhaseStack.
Keep the phase stack (which is just used for logging) in a pre-allocated array, rather than in a List.
Use an iterator to inspect annotations of owners, rather than creating a list of owners.
Contrary to the comment, it wasn't capturing the the context var at typer initialization, which would not have been correct anyway. The inlining here is motivated by avoiding allocation of lambda in every Typer. Even when we're using this value in eta expansion, SilentResult.filter will actually be inlined to avoid the need for a lambda at all.
As per review suggestions
I've resolved the merge conflict by dropping the commit "Avoid overhead of dead code checking unless |
All reactions
Sorry, something went wrong.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
More of the same as scala#5875 The change to default getters reduced tree churn by 6x in a real world project.
adriaanm
Successfully merging this pull request may close these issues.
These amount to a 3% performance improvement.