Skip to content

Fix #3675: don't unnecessarily wrap arrays when eliminating java varargs #3869

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 5 commits into from
Jan 23, 2018

Conversation

abeln
Copy link
Contributor

@abeln abeln commented Jan 18, 2018

When eliminating varargs in a java method call, we need to convert Seqs
into Arrays. However, if the object we're passing is already a wrapped
array, we just need to unwrap it.

Testing

Added a unit test that checks that

import java.nio.file._
class Test {
  def test(xs: Array[String]) = {
     val p4 = Paths.get("Hello", xs: _*)
  }
}

now gives

val p4: java.nio.file.Path = java.nio.file.Paths.get("Hello", xs) (i.e. there's no call to wrapXArray)

tree match {
case Apply(fun, _) =>
val sname = fun.symbol.name
sname == nme.wrapRefArray || sname == nme.wrapXArray(elemTpe.classSymbol.name)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@allanrenucci can you suggest a more robust way to identify whether the node corresponds to a wrapped arrray?

Copy link
Contributor

Choose a reason for hiding this comment

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

You can try something like:

private def isWrapArrayMethodCall(tree: Apply)(implicit ctx: Context): Boolean = {
  val elemType = tree.tpe.elemType
  val wrapArrayMethodSym = defn.ScalaPredefModuleRef.requiredMethodRef(wrapArrayMethodName(elemTpe))
  tree.fun.symbol == wrapArrayMethodSym
}

Also I would move this method to ElimRepeated

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Though here it's more complicated since there's one method per primitive type + Ref. You could store all of them in a Set in Definitions and then just check if the current symbol is contained in the Set.

@allanrenucci allanrenucci self-assigned this Jan 18, 2018
… varargs

When eliminating varargs in a java method call, we need to convert Seqs
into Arrays. However, if the object we're passing is already a wrapped
array, we just need to unwrap it.

Testing
-------

Eliminating varargs in

```
import java.nio.file._
class Test {
  def test(xs: Array[String]) = {
     val p4 = Paths.get("Hello", xs: _*)
  }
}
```

now gives

`val p4: java.nio.file.Path = java.nio.file.Paths.get("Hello", xs)`
@@ -106,6 +108,14 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
// Because of phantomclasses, the Java array's type might not conform to the return type
}

/** Determines whether the given `tree` represents a wrapped array */
private def isWrappedArray(tree: Apply)(implicit ctx: Context): Boolean = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@allanrenucci That's much nicer! Thanks! PTAL

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 the name of the method and doc are confusing. This tests that the tree is a method call to Predef.wrappedXArray, not that the tree represents a wrapped array. I would prefer:

/** Is the tree a method call to Predef.wrapXArray */
def isWrapArrayMethodCall

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

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

Can you add a test to compiler/test/dotty/tools/backend/jvm/DottyByteCodeTests.scala that compiles:

import java.nio.file._
class Test {
  def test(xs: Array[String]) = {
     val p4 = Paths.get("Hello", xs: _*)
  }
}

and make sure there is no call to wrapRefArray in the bytecode.

@@ -106,6 +108,14 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
// Because of phantomclasses, the Java array's type might not conform to the return type
}

/** Determines whether the given `tree` represents a wrapped array */
private def isWrappedArray(tree: Apply)(implicit ctx: Context): Boolean = {
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 the name of the method and doc are confusing. This tests that the tree is a method call to Predef.wrappedXArray, not that the tree represents a wrapped array. I would prefer:

/** Is the tree a method call to Predef.wrapXArray */
def isWrapArrayMethodCall

@@ -94,6 +94,8 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
private def seqToArray(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree match {
case SeqLiteral(elems, elemtpt) =>
JavaSeqLiteral(elems, elemtpt)
case app@Apply(fun, args) if isWrappedArray(app) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

I would remove the comment below and add a comment here:

// rewrite a call to `wrapXArray(arr)` to `arr`

}
}

assert(!arrayWrapped, "Arrays should not be wrapped when passed to a Java varargs method\n")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I verified that this fails if I comment out the changes to ElimRepeated.

Copy link
Contributor Author

@abeln abeln left a comment

Choose a reason for hiding this comment

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

PTAL

@abeln
Copy link
Contributor Author

abeln commented Jan 19, 2018

Thanks for the pointer to the BC unit tests. Didn't know they existed.

@@ -338,6 +339,12 @@ class Definitions {
def Predef_classOf(implicit ctx: Context) = Predef_classOfR.symbol
lazy val Predef_undefinedR = ScalaPredefModule.requiredMethodRef("???")
def Predef_undefined(implicit ctx: Context) = Predef_undefinedR.symbol
// The set of all wrap{X, Ref}Array methods, where X is a value type
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@smarter pre-calculated the set of methods here, as suggested
PTAL

val methodNames = ScalaValueTypes.map(TreeGen.wrapArrayMethodName) + nme.wrapRefArray
methodNames.map(ScalaPredefModule.requiredMethodRef).toSet
}
def Predef_wrapArray(implicit ctx: Context): Set[Symbol] = Predef_wrapArrayR.map(_.symbol)
Copy link
Member

Choose a reason for hiding this comment

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

To avoid redoing the .map(_.symbol) every time this method is called it would make sense to cache its result. Symbols are valid at least for one run so you can use the PerRun class for this, grep for PerRun in this file for some examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@abeln
Copy link
Contributor Author

abeln commented Jan 19, 2018

PTAL

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM. Thanks @abeln!

lazy val Predef_wrapArrayR: collection.Set[TermRef] = {
val methodNames = ScalaValueTypes.map(TreeGen.wrapArrayMethodName) + nme.wrapRefArray
methodNames.map(ScalaPredefModule.requiredMethodRef)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would inline this in Predef_wrapArray since it is only used there:

val Predef_wrapArray = new PerRun[collection.Set[Symbol]] { implicit ctx =>
  val methodNames = ScalaValueTypes.map(TreeGen.wrapArrayMethodName) + nme.wrapRefArray
  methodNames.map(name => ScalaPredefModule.requiredMethodRef(name).toSymbol)
}

val moduleNode = loadClassNode(moduleIn.input)
val method = getMethod(moduleNode, "test")

val arrayWrapped = instructionsFromMethod(method).exists { instr: Instruction =>
Copy link
Contributor

Choose a reason for hiding this comment

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

You can do:

val arrayWrapped = instructionsFromMethod(method).exists {
  case inv: Invoke => inv.name.contains("wrapRefArray")
  case _ => false
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@abeln
Copy link
Contributor Author

abeln commented Jan 22, 2018

Made requested changes. @allanrenucci PTAL
Thanks for patiently shepherding this!

@allanrenucci allanrenucci merged commit d691163 into scala:master Jan 23, 2018
@abeln abeln deleted the wrapped-array branch January 23, 2018 12:11
@abeln abeln restored the wrapped-array branch January 23, 2018 12:11
@abeln abeln deleted the wrapped-array branch January 23, 2018 12:11
@abeln abeln restored the wrapped-array branch January 23, 2018 12:12
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