Skip to content

Remove PreName add (almost) all other implicit conversions #4077

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

OlivierBlanvillain
Copy link
Contributor

This is followup to #3986 than removes a few virtual calls that the JIT complained about.

@OlivierBlanvillain
Copy link
Contributor Author

test performance please

@dottybot
Copy link
Member

dottybot commented Mar 6, 2018

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

@dottybot
Copy link
Member

dottybot commented Mar 6, 2018

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/4077/ to see the changes.

Benchmarks is based on merging with master (4be2e76)

@odersky
Copy link
Contributor

odersky commented Mar 7, 2018

It does not seem to get us any performance gain. And there is quite a bit of duplication, in particular by duplicating InheritedCache. So I am dubious this is actually an improvement.

regarding PreName: yes we need relatively few overloads if we drop it, but that is not the end of the story. The problem is that in the future there will be constant hassles that a method exists for Name but not for String or vice versa, which will require either annoying conversions or more ad-hoc overloads. So I would actually be in favor of keeping PreName in as well.

@OlivierBlanvillain OlivierBlanvillain force-pushed the cleanup-megamorphic-calls-2 branch 2 times, most recently from d78fefb to e6b24fb Compare March 8, 2018 10:24
@OlivierBlanvillain
Copy link
Contributor Author

@odersky I remove the InheritedCache commit, I agree it was too much duplication.

Regarding the PreName commit I updated it to minimize of number of String method (instead of minimizing the diff size, which I did previously). The result has only 4 overloaded methods that have a overloaded String version, which corresponds to the once that are heavily used in Definitions.scala. For everything else, Strings should be explicitly converted at call site with .toTermName/.toTypeName.

I believe the PreName change is worth doing for readability alone. The change was actually motivated by a real life confusion: @gsps came to my office a few weeks asking "how do I convert a String to PreName?", we spent ~10 minutes pocking around the code trying to figure things out, and finally had to resort to compiling Definitions.scala with -Xprint:typer to understand what was actually going on there.

@odersky
Copy link
Contributor

odersky commented Mar 9, 2018

Looking at the diffs I am still not convinced about PreName. I think the code reads worse now than before. Regarding discoverability, let's just put the String instance for PreName next to PreName, then it is never an issue.

@OlivierBlanvillain OlivierBlanvillain force-pushed the cleanup-megamorphic-calls-2 branch from e6b24fb to e429912 Compare March 9, 2018 18:44
PreName was abusing the implementation of implicit class by using the
underlying implicit conversion. For instance, several method form
Definitions where expecting an argument of type PreName and called with
a String.

This commit completely removes the PreName abstraction by explicitly
calling .toTermName and .toTypeName as required. This change should be
beneficial for both readability (more precise types) and performance.
Indeed by "pushing out" all the calls to .toTermName and .toTypeName to
the call site, these methods will never use virtual dispatch.

To keep the concisness of string literals a couple of methods where
duplicated to take String arguments (and forward to the
TypeName/TermName version).
@OlivierBlanvillain OlivierBlanvillain force-pushed the cleanup-megamorphic-calls-2 branch from e429912 to a33c99d Compare March 9, 2018 18:44
@OlivierBlanvillain OlivierBlanvillain changed the title Remove PreName Remove PreName add (almost) all other implicit conversions Mar 9, 2018
@OlivierBlanvillain
Copy link
Contributor Author

The few additional .toTypeName and .toTermName are the price to pay to get ride of implicit conversions. If these changes are too much I guess we should abandon the idea of further deprecating them, as it would imply adding import scala.language.implicitConversions on the ~100 files touched by this PR.

I newly pushed commits remove all the other implicit conversions, with the exception of these two to use Symbol as if it was a SymDenotation.

FlagConjunction relyvily relied on implicit conversion and was
boxing/unboxing on every use. This commit remove the FlagConjunction
abstraction, replacing by the following two constructs:

- .isBoth(flagA, and = flagB) instead of is(allOf(flagA, flagB))

- .isBoth(Private, and = Local) instead of is(PrivateLocal) & co

- .isClassTypeParam / .isJavaTrait / .isParamForwarder as their
  translation into flags is non trivial.
@OlivierBlanvillain OlivierBlanvillain force-pushed the cleanup-megamorphic-calls-2 branch from a33c99d to c49d246 Compare March 9, 2018 20:22
@@ -73,3 +73,4 @@ compiler/after-pickling.txt
*.dotty-ide-version

*.decompiled.out
bench/compile.txt
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

@@ -134,7 +134,7 @@ object Types {
}

/** Is this type different from NoType? */
def exists: Boolean = true
final def exists: Boolean = !this.eq(NoType)
Copy link
Contributor

Choose a reason for hiding this comment

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

Use this.ne(NoType), otherwise this commit LGTM

Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM to me also

@@ -91,7 +91,7 @@ object Trees {
/** The type of the tree. In case of an untyped tree,
* an UnAssignedTypeException is thrown. (Overridden by empty trees)
*/
def tpe: T @uncheckedVariance = {
final def tpe: T @uncheckedVariance = {
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

def ensureMethodic(implicit ctx: Context): Type = self match {
case self: MethodicType => self
case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

implicit def positionCoord(pos: Position): Coord =
def indexCoord(n: Int): Coord = new Coord(n + 1)

def pos2coord(pos: Position): Coord =
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

Copy link
Contributor

Choose a reason for hiding this comment

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

Overall I am skeptical about merging this.

val undoMsg = Ansi.Color.Blue(Ansi.Str.parse(" ...undoing last action, `Alt -` or `Esc -` to redo"))
val cannotUndoMsg = Ansi.Color.Blue(Ansi.Str.parse(" ...no more actions to undo"))
val redoMsg = Ansi.Color.Blue(Ansi.Str.parse(" ...redoing last action"))
val cannotRedoMsg = Ansi.Color.Blue(Ansi.Str.parse(" ...no more actions to redo"))
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure about this one.

@@ -716,7 +716,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
def isDeprecated: Boolean = false
def isMutable: Boolean = sym is Flags.Mutable
def hasAbstractFlag: Boolean =
(sym is Flags.Abstract) || (sym is Flags.JavaInterface) || (sym is Flags.Trait)
(sym is Flags.Abstract) || (sym is Flags.Trait)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(fs >>> TYPESHIFT) == (flags.bits >>> TYPESHIFT)
}
/** Does this flag set have both of the given flags? */
def isBoth(flags: FlagSet, and: FlagSet): Boolean =
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

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

I went through all commits except the test commits and added my opinion to each.

@@ -154,9 +154,9 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
lazy val Predef_classOf: Symbol = defn.ScalaPredefModule.requiredMethod(nme.classOf)
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer we leave PreName, but move the String -> PreName conversion to the companion object of PreName.

Copy link
Contributor Author

@OlivierBlanvillain OlivierBlanvillain Apr 12, 2018

Choose a reason for hiding this comment

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

That doesn't work as moving it the companion of PreName is not enough to make .toTermName and .toTypeName available on Strings. I guess we could have two implicit conversions, one in PreName's companion and one available by import, but I feel it would just make things worse...

Are you sure you don't like this commit as is? In my opinion, completely removing an abstraction from the code base is a clear simplification. The diff might look large but in reality, most of the line changes are about making type more precise by having the TermName/TypeName instead of PreName. The balance sheet in term of added boilerplate is also reasonable:

toTermName added: 22
toTermName removed: 12

toTypeName added: 12
toTypeName removed: 4

+4 overloaded methods with String & Name arguments

Copy link
Contributor

Choose a reason for hiding this comment

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

I still think we should leave PreName. I don't like to have to overload methods, and the problem is, if you are systematic about it (and you should be!) you will end up with a lot more overloaded methods. PreName is a single abstraction which fixes this quite nicely. For me it is really analogous to IterableOnce in collections. Before we had some overloaded methods that took Iterables and Iterators respectively. At first there were only a few. Then people complained that, by rights, some other methods should also be dual purpose, and then some others more, and so on. The introduction of IterableOnce stopped the bleeding.

I realize it's a contentious issue, but then the maxime "when in doubt, don't change it" applies.

One other thing: We have to tell everyone working on the compiler that Decorators is a highly recommended (maybe mandatory?) input. I have seen so much awkward code because people did not know about it. Is there a good place where we can put it so people will read it?

@@ -23,6 +23,7 @@ import config.Config
import util.common._
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

@@ -134,7 +134,7 @@ object Types {
}

/** Is this type different from NoType? */
def exists: Boolean = true
final def exists: Boolean = !this.eq(NoType)
Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM to me also

@@ -685,9 +685,6 @@ object Contexts {

Copy link
Contributor

Choose a reason for hiding this comment

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

This commit LGTM

@@ -260,9 +260,9 @@ object desugar {
@sharable private val synthetic = Modifiers(Synthetic)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this commit should not be merged. There is a reason for rawMods/modsDeco: mods should not be called on a typed tree because there mods is typically outdated - one should consult symbol.flags instead. The setup here makes sure that in code working with typed trees using a

import tpd._

import, mods is not visible, because modsDeco is not in scope. So this is a safety measure, not a convenience implicit.

implicit def positionCoord(pos: Position): Coord =
def indexCoord(n: Int): Coord = new Coord(n + 1)

def pos2coord(pos: Position): Coord =
Copy link
Contributor

Choose a reason for hiding this comment

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

Overall I am skeptical about merging this.

@@ -601,7 +601,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
" := " ~ tp.toText(printer)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Not an improvement, lots of nitty gritty boilerplate to add.

@@ -828,8 +828,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
def freshLocal(cunit: CompilationUnit, name: String, tpe: Type, pos: Position, flags: Flags): Symbol =
Copy link
Contributor

Choose a reason for hiding this comment

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

SymUtils is not used much, to this commit looks smallish. But we should use it more often because it gives us a way to add methods without overloading SymDenotation. In that case I think it is essential that it works for Symbol and SymDenotation in the same way. So my vote is to leave this as is.

/** A decorator that provides methods for modeling type application */
class TypeApplications(val self: Type) extends AnyVal {
trait TypeApplications extends Any {
def self: Type
Copy link
Contributor

Choose a reason for hiding this comment

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

That will cause a box for every method call. So don't think this is an improvement.

@@ -47,17 +47,17 @@ object StdNames {
}

abstract class DefinedNames[N <: Name] {
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer the current version.

@odersky odersky removed their assignment Mar 28, 2018
@@ -44,12 +44,14 @@ class ReplTest extends ReplDriver(
def storedOutput(): String =
stripColor(out.asInstanceOf[StoringPrintStream].flushStored())

protected implicit def toParsed(expr: String)(implicit state: State): Parsed = {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a better solution is to expose a compile method in ReplTest that takes a String

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.

5 participants