-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Conversation
tree match { | ||
case Apply(fun, _) => | ||
val sname = fun.symbol.name | ||
sname == nme.wrapRefArray || sname == nme.wrapXArray(elemTpe.classSymbol.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.
@allanrenucci can you suggest a more robust way to identify whether the node corresponds to a wrapped arrray?
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.
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
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 would store the result of requiredMethodRef
in a val in https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Definitions.scala (see existing examples like https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Definitions.scala#L339-L340)
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.
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.
… 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 = { |
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.
@allanrenucci That's much nicer! Thanks! PTAL
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 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
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.
Done.
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.
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 = { |
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 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) => |
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 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") |
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 verified that this fails if I comment out the changes to ElimRepeated.
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.
PTAL
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 |
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.
@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) |
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.
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.
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.
Done.
PTAL |
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.
Otherwise LGTM. Thanks @abeln!
lazy val Predef_wrapArrayR: collection.Set[TermRef] = { | ||
val methodNames = ScalaValueTypes.map(TreeGen.wrapArrayMethodName) + nme.wrapRefArray | ||
methodNames.map(ScalaPredefModule.requiredMethodRef) | ||
} |
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 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 => |
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.
You can do:
val arrayWrapped = instructionsFromMethod(method).exists {
case inv: Invoke => inv.name.contains("wrapRefArray")
case _ => false
}
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.
Done.
Made requested changes. @allanrenucci PTAL |
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
now gives
val p4: java.nio.file.Path = java.nio.file.Paths.get("Hello", xs)
(i.e. there's no call to wrapXArray)