Skip to content
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

Delay name mangling #2128

Merged
merged 68 commits into from
Apr 11, 2017
Merged

Delay name mangling #2128

merged 68 commits into from
Apr 11, 2017

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Mar 21, 2017

This PR aims to delay name mangling until the point where we emit code. Before, names should be "semantic", i.e. the kind of a name is represented by its structure instead of its characters.

/** A term name that's derived from an `underlying` name and that
* adds `info` to it.
*/
class DerivedTermName(override val underlying: TermName, override val info: NameInfo)
Copy link
Member

Choose a reason for hiding this comment

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

Instead of having a NameInfo object as parameter, what about a NameFlagSet ?

@@ -163,7 +163,7 @@ object Names {
def info = NameInfo.TermName
def underlying: TermName = unsupported("underlying")

private var derivedNames: AnyRef /* SimpleMap | j.u.HashMap */ =
@sharable private var derivedNames: AnyRef /* SimpleMap | j.u.HashMap */ =
Copy link
Member

Choose a reason for hiding this comment

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

Why is this safe? synchronized is not used when modifying derivedNames currently.

@odersky
Copy link
Contributor Author

odersky commented Mar 28, 2017

The build failures are due to ContextEscapeDetection not compiling. I see:

/drone/src/github.com/lampepfl/dotty/compiler/test/dotty/tools/ContextEscapeDetection.java:23: cannot find symbol
232s
865
[error] symbol: class Context
232s
866
[error] location: class dotty.tools.dotc.core.Contexts
232s
867
[error] public abstract Contexts.Context getCtx();

Locally sbt test succeeds for me. Who can help debug this?

@odersky
Copy link
Contributor Author

odersky commented Mar 28, 2017

Rebased to master

@smarter
Copy link
Member

smarter commented Mar 29, 2017

Locally sbt test succeeds for me. Who can help debug this?

The issue only happen with the bootstrapped tests, to run them you need to first publish the non-bootstrapped compiler:

publishLocal

Then run the tests in the dotty-compiler-bootstrapped project:

dotty-compiler-bootstrapped/test

The test failure is very reminiscent of a problem I fixed in the backend some times ago: lampepfl/scala#4

The problem was that the inner class table in the classfile had incorrect outer class names, and indeed if we do javap -v on the compiled-by-dotty Contexts.scala we see:

public static abstract #10= #7 of #9; //Context=class dotty/tools/dotc/core/Contexts$Context of class dotty/tools/dotc/core/Contexts$

Instead of (from the compiled-by-scalac Contexts.scala):

public static abstract #25= #24 of #2; //Context=class dotty/tools/dotc/core/Contexts$Context of class dotty/tools/dotc/core/Contexts

The crucial difference is the last thing in the line: Contexts$ instead of Contexts. The problem comes from DottyBackendInterface#nameHelper#dropModule:

def dropModule: Name = n.stripModuleClassSuffix

If I print n.stripModuleClassSuffix I get a name which ends with a $ which is clearly wrong. Further debugging shows that the names are TypeName which wrap SimpleTermName so they have no semantic information, I have no idea why.

@smarter
Copy link
Member

smarter commented Mar 29, 2017

Ah, I see now, the full relevant code in the backend is:

          val outerName = innerClassSym.rawowner.javaBinaryName
          // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec.
          val outerNameModule = if (innerClassSym.rawowner.isTopLevelModuleClass) outerName.dropModule

Where javaBinaryName is defined in the backend interface:

def javaBinaryName: Name = toDenot(sym).fullNameSeparated("/")

And fullNameSeparated is defined to return a non-semantic name. This can be fixed using .unmangleClassName, I've pushed a commit to do so in javaBinaryName, though I'm not sure if this is the best place for it.

@smarter
Copy link
Member

smarter commented Mar 29, 2017

The current failures are due to:

assert(isValidJVMName(sym.name), s"${sym.fullName} name is invalid on jvm")

Because in some cases we get sym.name.toString like: scala.collection$TraversableOnce$collectFirst$$B which is technically incorrect since it contains a .. I don't know why this happens or why it's only happening in the bootstrapped tests.

@smarter
Copy link
Member

smarter commented Mar 30, 2017

I have two features request to aid debugging, let me know what you think:

  • A rawString or equivalent method to get the full structure of the name, like printing a raw type or a raw tree, debugString is not enough since it doesn't tell if you're dealing with a PrefixNamedKind, etc.
  • An (optional?) id for names to figure out where they were constructed by doing assert(id == 42), like with symbols. (Names don't have stable hashcodes so that can't be used).

@odersky odersky force-pushed the add-semantic-names branch 3 times, most recently from e52ad28 to 29b51e8 Compare March 31, 2017 14:06
@@ -339,7 +339,12 @@ object Names {

def isSimple = false
def asSimpleName = throw new UnsupportedOperationException(s"$debugString is not a simple name")
def toSimpleName = termName(toString)

private var simpleName: SimpleTermName = null
Copy link
Member

Choose a reason for hiding this comment

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

I think you want private[this] var here to avoid creating a setter

def name = myName

/** Update the name; only called when unpickling top-level classes */
def name_=(n: Name) = myName = n
Copy link
Member

Choose a reason for hiding this comment

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

Should this setter be protected[dotc] like def info_= ?

override def newScopeLikeThis() = new PackageScope
}

private[core] val currentDecls: MutableScope = new PackageScope()

def isFlatName(name: SimpleTermName) = name.lastIndexOf('$', name.length - 2) >= 0
Copy link
Member

Choose a reason for hiding this comment

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

Could you add a comment explaining the name.length - 2 here? Also why is this logic different from the one for the other overload for isFlatName ?

newScopeEntry(defn.newFunctionNTrait(name.asTypeName))
else if (isFlatName(mangled.toSimpleName) && enterFlatClasses.isDefined) {
Stats.record("package scopes with flatnames entered")
enterFlatClasses.get(ctx)
Copy link
Member

Choose a reason for hiding this comment

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

Why not use ensureComplete here?

@odersky
Copy link
Contributor Author

odersky commented Apr 6, 2017

Rebased to master

@odersky odersky changed the title [WIP] Delay name mangling Delay name mangling Apr 7, 2017
@odersky
Copy link
Contributor Author

odersky commented Apr 10, 2017

This commit is too big to leave in the queue. There's constant breakage wrt other commits. So I want to get this in by the end of today at the latest.

@smarter
Copy link
Member

smarter commented Apr 10, 2017

This PR LGTM, except that I don't see how derivedNames can be made @sharable without using synchronized blocks (also there's a test failure but I assume you noticed that).

@smarter
Copy link
Member

smarter commented Apr 10, 2017

The use of @sharable for:

  @sharable private val simpleNameKinds = new mutable.HashMap[Int, ClassifiedNameKind]
  @sharable private val qualifiedNameKinds = new mutable.HashMap[Int, QualifiedNameKind]
  @sharable private val uniqueNameKinds = new mutable.HashMap[String, UniqueNameKind]

is also suspicious. Right now it should be fine since all the NameKinds are instantiated in object NameKinds, but nothing enforces that, it would help if the constructors for NameKinds were all private.

Remove unused functionality
It fails without any test output in partest
Drop Seq implementation of name. This implementation
was always problematic because it entailed potentially
very costly conversions to toSimpleName. We now have
better control over when we convert a name to a simple
name.
toSimpleName is called a lot from the backend, so it makes
sense to memoize it. It would be even better to communicate
with the backend using strings, because then we would not
have to enter all these simple names in the name table.
Classfile parsing is about JVM-level names, not Scala level ones. So it
is more consistent to use mangled names throughout.
Simplifies the test that the class represented by a classfile
is the class logically referenced by the file. The simplification
is needed if we want to populate package scopes with unmangled
classnames.
Once we start using unencoded operators internally, we will face the problem
that one cannot decode realiably a class file filename. We therefore turn
things around, keeping members of package scopes in mangled and encoded form.
This is compensated by (1) mangling names for lookup of such members and
(2) when unpickling from Scala 2 info or Tasty, comparing mangled names when
matching a read class or module object against a root.
Mangled is like toSimpleName, except that it keeps the term/type distinction.
Prevviously they were actually unused.
and fix a bug in TreeUnpickler
Since we now have separate package-scopes, it's easier to have
them take into account the special role played by the scala package.
So we can drop the funky logic of `makeScalaSpecial`.
Names with internal $'s are entered in package scopes only if

  - we look for a name with internal $'s.
  - we want to know all the members of a package scope

 This optimization seems to be fairly effective. The typical range
 of package scopes that need $-names is between 0 and 20%. The optimization
seems to improve execution time of all unit tests by about 3%.

Also. drop the inheritance from Iterable to Scope. The reason
is that we now need a context parameter for toList and
other Iterable operations which makes them impossible to
fit into the Iterable framework.
Running the test suite with the pickling printer on showed up two more
problems which are fixed in this commit.
Used a hardcoded string before, which caused test failures.
@odersky
Copy link
Contributor Author

odersky commented Apr 11, 2017

I'll roll the private constructor changes into the next PR.

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.

3 participants