-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #1167: Reduce the magic in Arrays.newRefArray. Implement multidimensional arrays #1188
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
Fix #1167: Reduce the magic in Arrays.newRefArray. Implement multidimensional arrays #1188
Conversation
While you are in this area, it might be good to consider whether and how to extend array support to use public class Test {
public static void main(String... args) {
int[][] multi = new int[5][10];
}
}
vs
|
@retronym IIUC, this requires non-trivial changes in the back-end. IIRC, @DarkDimius wants to keep the back-end as close as possible to the scalac one. |
Review by @DarkDimius ? |
I'm very hesitant to accept the original change at the moment. I have a feeling that we're getting a very small win: there are still generic methods after erasure. It does not give any win for any phases after erasure, except JS backend. But instead it would complicate shared backed a lot. @retronym @sjrd GenBCode does support emitting |
More precisely, it allows to remove 15 quite simple lines lines from JS backend while adding 70+ lines of hard-to-maintain-between-frontends code to JVM shared backend. |
else { | ||
val typeApplied = newArr("Ref").appliedToTypeTrees(typeArg :: Nil) | ||
val classOfArg = | ||
ref(defn.Predef_classOf).appliedToTypeTrees(typeArg :: Nil) |
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.
This code would only work before classOf
that is very early in Dotty(before 'patmat').
It should instead use tpd.clsOf(typeArg)
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.
tpd.clsOf(typeArg)
does not work. I tried that first. It choked in pickling because the pickler cannot pickle a JavaArrayType
.
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.
well this code also does not work for 80% of pipeline(it broke my optimization phases)
This code would not pass Ycheck after erasure either.
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.
So ... tpd.clsOf(typeArg)
is unreliable before pickling, and classOf[typeArg]
is unreliable after ClassOf
. Is there anything that works all the time to generate a literal classOf[T]
? Should I fix clsOf
so that it is reliable along the entire pipeline?
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.
Should I fix clsOf so that it is reliable along the entire pipeline?
I guess it would be the way to go. It should check if the type it is given derives from scala.Array
, if it does, only don't yet erase its type before erasure.
On the other side: this PR makes |
Hum ... I'm expecting this to be around 3 lines: case Apply(_, Constant(Literal(elemTpe: Type)) :: args) if isSyntheticArrayConstructor(app.symbol) =>
generatedType = ArrayBType(toTypeKind(elemTpe))
mkArrayConstructorCall(generatedType, app, args) right before the line you were pointing at in your comment.
I don't care about those. I care about https://github.com/lampepfl/dotty/pull/1188/files#diff-7d76351125e3a0376bea4b9db34a05ccL471 |
Makes sense. |
would not work without extending SharedBackendInterface. |
OK, then it can be done as in case Apply(_, Constant(const) :: args)
if isSyntheticArrayConstructor(app.symbol) && const.tag == ClazzTag =>
generatedType = ArrayBType(toTypeKind(const.typeValue))
mkArrayConstructorCall(generatedType, app, args) |
@sjrd, let me try to play with this branch before Dotty meeting and let's discuss there. |
https://github.com/scala/scala/pull/5060/files indicates that though code existed, it was never executed or tested. |
d5d92e7
to
0a09332
Compare
Waiting for scala-compiler fork to reach maven central. Backwards INcompatible with previous version. |
/rebuild |
/rebuild |
def newUnitArray(length: Int): Array[Unit] = | ||
jlr.Array.newInstance(classOf[scala.runtime.BoxedUnit], length).asInstanceOf[Array[Unit]] | ||
def newArray[Arr](componentType: Class[_], returnType: Class[Arr], dimensions: Array[Int]): Arr = | ||
jlr.Array.newInstance(componentType, dimensions: _*).asInstanceOf[Arr] |
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.
Why do you need returnType
, here?
Also, why no bound? Right now componentType
is completely unchecked; it might not be in sync. What about the following signature?
def newArray[T](componentType: Class[T], returnType: Class[Arrary[T]], dimensions: Array[Int]): Array[T] =
jlr.Array.newInstance(componentType, dimensions: _*).asInstanceOf[Arrary[T]]
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.
Why do you need returnType, here?
I need to recreate the returned type at backend. And this is the easiest way I've found to do this.
Also, why no bound? Right now componentType is completely unchecked; it might not be in sync. What about the following signature?
It's NOT in sync before erasure. When you're creating a Array[Int]
, where Int
is a primitive, you would like to pass https://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html#TYPE, that is the Class
for primitive Int, but is statically typed as Class[java.lang.Integer]
.
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.
that is the Class for primitive Int, but is statically typed as Class[java.lang.Integer].
Ouch! Why do we need this? Why can't we keep a Literal(Constant(defn.IntType))
? That's how scalac does it, and I find it much more consistent.
That's all I have at this point. |
0452155
to
55174aa
Compare
@@ -22,35 +24,8 @@ object Arrays { | |||
arr | |||
} | |||
|
|||
/** Create an array of type T. T must be of form Array[E], with | |||
* E being a reference type. | |||
/** Create an array of a reference type T. |
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.
This comment is obsolete.
That's all I have. |
tpd.cpy.Apply(tree)(newArray(targ, tree.pos), args) | ||
|
||
def newGenericArrayCall = | ||
ref(defn.DottyArraysModule).select(defn.newGenericArrayMethod).withPos(tree.pos).appliedToTypeTrees(targs).appliedToArgs(args) |
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.
This looks more readable on multiple lines.
What's the status of this PR? |
I assumed it was merged long time ago. Will rebase&merge tomorrow. |
Previously, the method `Arrays.newRefArray` was one of the only 3 methods that are kept generic after erasure. This commit removes this magic, by making it take an actual `j.l.Class[T]` as parameter. Moreover, the methods `newXArray` all receive an actual body, implemented on top of Java reflection, which means that a back-end does not *have to* special-case those methods for correctness. It might still be required for performance, though, depending on the back-end. The JVM back-end is made non-optimal in this commit, precisely because it does not specialize that method anymore. Doing so requires modifying the fork of scalac that we use, which should be done separately. The JS back-end is adapted simply by doing nothing at all on any of the newXArray methods. It will normally call the user-space implementations which use reflection. The Scala.js optimizer will inline and intrinsify the reflective calls, producing optimal code, at the end of the day.
This one is able to encode creation of array of any type and any dimension. Note, it does not handle value classes.
It's done in a separate ArrayConstructors phase now.
That knows that there exists only single magical array method.
The problem comes from JavaArrayTypes. They are invalid before erasure, and cannot be pickled, while Array[T] is invalid after erasure and should be erased.
It's needed in order to create calls to newGenricArray as it needs to infer the ClassTag.
This allowed to simplify the code in both Applications and tpd.newArray. Now, only creation of generic arrays is handled by typer. All other arrays are handled in ArrayConstructors phase.
They need to be created through their class tag.
a531c3b
to
397926b
Compare
397926b
to
ec00f1a
Compare
ec00f1a
to
5399fbe
Compare
/rebuild |
Previously, the method
Arrays.newRefArray
was one of the only 3methods that are kept generic after erasure. This commit removes
this magic, by making it take an actual
j.l.Class[T]
asparameter.
Moreover, the methods
newXArray
all receive an actual body,implemented on top of Java reflection, which means that a back-end
does not have to special-case those methods for correctness.
It might still be required for performance, though, depending on
the back-end.
The JVM back-end is made non-optimal in this commit, precisely
because it does not specialize that method anymore. Doing so
requires modifying the fork of scalac that we use, which should
be done separately.
The JS back-end is adapted simply by doing nothing at all on any
of the
newXArray
methods. It will normally call the user-spaceimplementations which use reflection. The Scala.js optimizer will
inline and intrinsify the reflective calls, producing optimal
code, at the end of the day.