Skip to content

Cleanups after the big Name refactoring #3833

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

Merged
merged 7 commits into from
Nov 7, 2019
Merged

Conversation

sjrd
Copy link
Member

@sjrd sjrd commented Nov 5, 2019

No description provided.

@sjrd sjrd requested a review from gzm0 November 5, 2019 18:03
@sjrd sjrd closed this Nov 5, 2019
@sjrd sjrd reopened this Nov 5, 2019
@sjrd
Copy link
Member Author

sjrd commented Nov 5, 2019

GitHub is partially down at the moment, so the CI is not triggered. I will try to re-close and re-open later.

@sjrd sjrd closed this Nov 5, 2019
@sjrd sjrd reopened this Nov 5, 2019
Since that object defines Name classes and constant Names, it makes
more sense to call it `Names`.
@sjrd sjrd force-pushed the cleanup-names branch 3 times, most recently from a6fc468 to fa23839 Compare November 7, 2019 10:08
@@ -548,18 +548,18 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) {

case Assign(select, rhs) =>
select match {
case Select(This(), ClassRef(cls), FieldIdent(_, _))
case Select(This(), cls, FieldIdent(_, _))
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems kind of inconsistent that we are not renaming cls -> className here. Intended?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not intended, I forgot it. Fixed.


/** Returns `true` iff this is the name of a static initializer. */
def isStaticInitializer: Boolean = this == StaticInitializerSimpleName
def isStaticInitializer: Boolean = encoded(0) == '<' && encoded(1) == 'c'
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this change? Is it actually faster?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's clearly faster because:

  • in the true case, testing against StaticInitializerSimpleName requires at least the same comparisons to happen, but loading the rhs from memory instead of as immediates, plus more comparisons for the subsequent characters.
  • in the false case, we trade looking up this._hashCode and that._hashCode for looking up just this.encoded(0)

Copy link
Member Author

Choose a reason for hiding this comment

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

Also this avoids defining some methods of SimpleMethodName in terms of constant instances of SimpleMethodName itself.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've reverted that change in that commit, and instead I've added another commit later that uses an even better mechanism, using reference equality.

throw new IllegalArgumentException(
"A constructor or static initializer must not have a result type")
"A reflective proxy must have a result type of java.lang.Object")
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit strange that the check is against a constant but the name is inlined... It's a detail though.

}
buildInner(scope.withEnv(scope.env.withLocalDefs(newMappings)), cont1)
buildInner(scope.withEnv(newEnv), cont1)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why the old code wouldn't work here...

Copy link
Member Author

Choose a reason for hiding this comment

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

withLocalDefs wants a List[(LocalName, LocalDef)], but now newMappings is a List[(Binding.Name, LocalDef)].

* `equals()` and `hashCode()`, along with `==` and `##`, are just as
* broken for `EncodedName` as for `Array`s. Use the methods in the
* companion object instead.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Why put them in the companion? Is there a disadvantage to simply overriding them? (IIUC for an AnyVal the byte code will look pretty much the same than a method in the companion when not boxed).

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately, overriding equals and hashCode is prohibited in an AnyVal. The compiler rejects it. I never understood why that restriction exists, but there's nothing I can do about it now.

scala.util.hashing.MurmurHash3.bytesHash(encoded.bytes)

private[Names] def concat(encoded1: EncodedName,
encoded2: EncodedName): EncodedName = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Define + on EncodedName?

Copy link
Member Author

Choose a reason for hiding this comment

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

I called it ++ instead. + only means concatenation for Strings, and it has the problem of allowing String + Anything without any check. ++ consistently means concatenation for collections in Scala.

* broken for `EncodedName` as for `Array`s. Use the methods in the
* companion object instead.
*/
final class EncodedName private (private[ir] val bytes: Array[Byte])
Copy link
Contributor

Choose a reason for hiding this comment

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

I like how this gives a neat separation between base encoding and higher level name guarantees. In this line: Have you considered putting this into a separate file (so the utf8 encoding methods would get closer together without increasing noise in this file).

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, consider simply calling this UTF8String.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@@ -106,14 +106,16 @@ object Serializers {
final val FormatNoPositionValue = -1
}

private final class ByteString(val bytes: Array[Byte]) {
private final class EncodedNameKey(val encoded: EncodedName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If EncodedName gets the equals and hash code directly, this class can simply disappear, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, but as I said above, EncodedName cannot get equals and hashCode :(

It made sense to use `ClassRef`s when they wrapped bare `String`s,
as it provided better type safety. However, now that we have
type-safe `ClassName`s, the wrapping `ClassRef` are just noise and
useless indirections.

We also more consistently use `className` as a variable of type
`ClassName`, instead of `cls` which was used for `ClassRef`s.

After this commit, `ClassRef`s are only used as one case of
`TypeRef`, as they should.
Copy link
Member Author

@sjrd sjrd left a comment

Choose a reason for hiding this comment

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

Updated.

@@ -548,18 +548,18 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) {

case Assign(select, rhs) =>
select match {
case Select(This(), ClassRef(cls), FieldIdent(_, _))
case Select(This(), cls, FieldIdent(_, _))
Copy link
Member Author

Choose a reason for hiding this comment

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

Not intended, I forgot it. Fixed.


/** Returns `true` iff this is the name of a static initializer. */
def isStaticInitializer: Boolean = this == StaticInitializerSimpleName
def isStaticInitializer: Boolean = encoded(0) == '<' && encoded(1) == 'c'
Copy link
Member Author

Choose a reason for hiding this comment

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

It's clearly faster because:

  • in the true case, testing against StaticInitializerSimpleName requires at least the same comparisons to happen, but loading the rhs from memory instead of as immediates, plus more comparisons for the subsequent characters.
  • in the false case, we trade looking up this._hashCode and that._hashCode for looking up just this.encoded(0)

}
buildInner(scope.withEnv(scope.env.withLocalDefs(newMappings)), cont1)
buildInner(scope.withEnv(newEnv), cont1)
Copy link
Member Author

Choose a reason for hiding this comment

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

withLocalDefs wants a List[(LocalName, LocalDef)], but now newMappings is a List[(Binding.Name, LocalDef)].

* broken for `EncodedName` as for `Array`s. Use the methods in the
* companion object instead.
*/
final class EncodedName private (private[ir] val bytes: Array[Byte])
Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

scala.util.hashing.MurmurHash3.bytesHash(encoded.bytes)

private[Names] def concat(encoded1: EncodedName,
encoded2: EncodedName): EncodedName = {
Copy link
Member Author

Choose a reason for hiding this comment

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

I called it ++ instead. + only means concatenation for Strings, and it has the problem of allowing String + Anything without any check. ++ consistently means concatenation for collections in Scala.


/** Returns `true` iff this is the name of a static initializer. */
def isStaticInitializer: Boolean = this == StaticInitializerSimpleName
def isStaticInitializer: Boolean = encoded(0) == '<' && encoded(1) == 'c'
Copy link
Member Author

Choose a reason for hiding this comment

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

Also this avoids defining some methods of SimpleMethodName in terms of constant instances of SimpleMethodName itself.

sjrd added 3 commits November 7, 2019 14:49
…ame.

In doing so, we can turn `resultTypeRef` from being optional to
being mandatory. For constructors and static initializers, it must
be set to `void`, whereas for reflective proxies, it must be set
to `java.lang.Object`. This is consistent with how they are
typechecked both at definition site and call site.
Instead of using a fake `LocalName` for `this`, we now use a
dedicated ADT for the `name` of a `Binding`, which be either
`Binding.This` or `Binding.Local(name, originalName)`.

In `OptEnv`, we put the `LocalDef` for `this` in a separate field.
Instead of being a raw `Array[Byte]`, we now use a dedicated
class `UTF8String` which is an immutable valid UTF-8 sequence,
validated as such when constructed. This allows to expose it to
users of `Name`s in a safe way.

We also remove or hide all the constructors that by-pass name
validation, so that it is not possible to create invalid names
from outside the `ir` package.
Copy link
Contributor

@gzm0 gzm0 left a comment

Choose a reason for hiding this comment

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

Consider dropping the "the" in the message of the new commit so it's not too long.

sjrd added 2 commits November 7, 2019 16:09
Since we need to validate `<init>` and `<clinit>` separately
anyway, using the result of this validation to ensure the
uniqueness of the resulting `SimpleMethodName`s is free.

This allows to implement `isConstructor` and `isStaticInitializer`
with `eq`.
After the recent refactorings, we were left with a number of fields
and variables named `encodedName`, even though they have the type
`ClassName` or `MethodName`. We have now renamed all of them to
`className` and `methodName`, respectively.
@sjrd
Copy link
Member Author

sjrd commented Nov 7, 2019

Consider dropping the "the" in the message of the new commit so it's not too long.

Good point :)

@sjrd sjrd merged commit 3d653a7 into scala-js:master Nov 7, 2019
@sjrd sjrd deleted the cleanup-names branch November 7, 2019 21:24
sjrd added a commit to sjrd/dotty that referenced this pull request Nov 26, 2019
There were several changes to the IR in 1.0.0-RC1 that have an
impact on this codebase:

* Primitive types now have dedicated `PrimRef`s when used as
  `TypeRef`s, so for example `IntRef` instead of `ClassRef("I")`.
  See scala-js/scala-js#3802
* Names now have dedicated types instead of `String`s, and depend
  on the kind of symbol: `LocalName`, `LabelName`, `FieldName`,
  `MethodName` and `ClassName`. There are different kinds of
  `js.Ident`s to go with each kind of `Name`.
  See scala-js/scala-js#3804,
  scala-js/scala-js#3814 and
  scala-js/scala-js#3833
* Original names are now represented by a dedicated `OriginalName`
  type, and they are stored only at definition site, not at use
  site.
  See scala-js/scala-js#3845
sjrd added a commit to dotty-staging/dotty that referenced this pull request Nov 26, 2019
There were several changes to the IR in 1.0.0-RC1 that have an
impact on this codebase:

* Primitive types now have dedicated `PrimRef`s when used as
  `TypeRef`s, so for example `IntRef` instead of `ClassRef("I")`.
  See scala-js/scala-js#3802
* Names now have dedicated types instead of `String`s, and depend
  on the kind of symbol: `LocalName`, `LabelName`, `FieldName`,
  `MethodName` and `ClassName`. There are different kinds of
  `js.Ident`s to go with each kind of `Name`.
  See scala-js/scala-js#3804,
  scala-js/scala-js#3814 and
  scala-js/scala-js#3833
* Original names are now represented by a dedicated `OriginalName`
  type, and they are stored only at definition site, not at use
  site.
  See scala-js/scala-js#3845
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.

2 participants