Skip to content

Commit ee203b2

Browse files
smarterVladimirNik
authored andcommitted
New representation for arrays of value classes
For a value class V whose underlying type is U, instead of representing an array of V as V[] on the JVM, we use our own class VCXArray where X is "U" if U is a primitive type and is "Object" otherwise. This avoids boxing when creating arrays but we still need to box and unbox when using such an array in a generic position, this also breaks reflection and makes it pretty hard to use an array of a value class from Java or Scala 2.
1 parent 8af61ab commit ee203b2

13 files changed

+429
-91
lines changed

src/dotty/DottyPredef.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,15 @@ object DottyPredef {
1010

1111
implicit def arrayTag[T](implicit ctag: ClassTag[T]): ClassTag[Array[T]] =
1212
ctag.wrap
13+
14+
// This implicit will never be used for arrays of primitives because
15+
// the wrap*Array in scala.Predef have a higher priority.
16+
implicit def wrapVCArray[T <: AnyVal](xs: Array[T]): WrappedArray[T] =
17+
new WrappedArray[T] {
18+
val array = xs
19+
lazy val elemTag = ClassTag[T](arrayElementClass(array.getClass))
20+
def length: Int = array.length
21+
def apply(index: Int): T = array(index)
22+
def update(index: Int, elem: T) { array(index) = elem }
23+
}
1324
}

src/dotty/runtime/vc/VCPrototype.scala

Lines changed: 69 additions & 62 deletions
Large diffs are not rendered by default.

src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Compiler {
5151
new ElimRepeated, // Rewrite vararg parameters and arguments
5252
new NormalizeFlags, // Rewrite some definition flags
5353
new ExtensionMethods, // Expand methods of value classes with extension methods
54+
new VCParents,
5455
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
5556
new TailRec, // Rewrite tail recursion to loops
5657
new LiftTry, // Put try expressions that might execute on non-empty stacks into their own methods
@@ -68,6 +69,7 @@ class Compiler {
6869
new AugmentScala2Traits, // Expand traits defined in Scala 2.11 to simulate old-style rewritings
6970
new ResolveSuper), // Implement super accessors and add forwarders to trait methods
7071
List(new Erasure), // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
72+
List(new VCArrays),
7173
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
7274
new VCElideAllocations, // Peep-hole optimization to eliminate unnecessary value class allocations
7375
new Mixin, // Expand trait fields and trait initializers

src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,34 @@ class Definitions {
752752
/** The type of the boxed class corresponding to primitive value type `tp`. */
753753
def boxedType(tp: Type)(implicit ctx: Context): TypeRef = boxedTypes(scalaClassName(tp))
754754

755+
lazy val vcPrototype: Map[Symbol, Symbol] =
756+
refClasses.map(vc => vc -> ctx.requiredClass(s"dotty.runtime.vc.VC${vc.name}Prototype")).toMap
757+
lazy val vcPrototypeValues = vcPrototype.values.toSet
758+
lazy val vcCompanion: Map[Symbol, Symbol] =
759+
refClasses.map(vc => vc -> ctx.requiredClass(s"dotty.runtime.vc.VC${vc.name}Companion")).toMap
760+
lazy val vcArray: Map[Symbol, Symbol] =
761+
refClasses.map(vc => vc -> ctx.requiredClass(s"dotty.runtime.vc.VC${vc.name}Array")).toMap
762+
763+
lazy val VCPrototypeClass = ctx.requiredClass(s"dotty.runtime.vc.VCPrototype")
764+
def VCPrototypeType = VCPrototypeClass.typeRef
765+
lazy val VCArrayPrototypeClass = ctx.requiredClass(s"dotty.runtime.vc.VCArrayPrototype")
766+
def VCArrayPrototypeType = VCArrayPrototypeClass.typeRef
767+
768+
def vcPrototypeOf(vc: ClassDenotation) = {
769+
val underlying = ValueClasses.valueClassUnbox(vc).info.classSymbol
770+
vcPrototype.getOrElse(underlying, vcPrototype(defn.ObjectClass))
771+
}
772+
def vcCompanionOf(vc: ClassDenotation) = {
773+
val underlying = ValueClasses.valueClassUnbox(vc).info.classSymbol
774+
vcCompanion.getOrElse(underlying, vcCompanion(defn.ObjectClass))
775+
}
776+
def vcArrayOf(vc: ClassDenotation) = {
777+
val underlying = ValueClasses.valueClassUnbox(vc).info.classSymbol
778+
vcArray.getOrElse(underlying, vcArray(defn.ObjectClass))
779+
}
780+
781+
782+
755783
def wrapArrayMethodName(elemtp: Type): TermName = {
756784
val cls = elemtp.classSymbol
757785
if (cls.isPrimitiveValueClass) nme.wrapXArray(cls.name)

src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ object StdNames {
228228

229229
// Compiler-internal
230230
val ANYname: N = "<anyname>"
231+
val ARR: N = "arr"
231232
val CONSTRUCTOR: N = Names.CONSTRUCTOR.toString
232233
val DEFAULT_CASE: N = "defaultCase$"
233234
val EVT2U: N = "evt2u$"

src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
386386

387387
private def eraseArray(tp: RefinedType)(implicit ctx: Context) = {
388388
val defn.ArrayOf(elemtp) = tp
389+
// Arrays are semi-erased to ErasedValueType(V, U)[] and fully erased in
390+
// VCArrays
389391
def arrayErasure(tpToErase: Type) =
390-
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
392+
erasureFn(isJava, semiEraseVCs = true, isConstructor, wildcardOK)(tpToErase)
391393
if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType)
392394
else if (isUnboundedGeneric(elemtp)) defn.ObjectType
393395
else JavaArrayType(arrayErasure(elemtp))

src/dotty/tools/dotc/transform/ExtensionMethods.scala

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,27 +76,12 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful
7676
val evt2uSym = ctx.newSymbol(moduleSym, nme.EVT2U, Synthetic | Method,
7777
MethodType(List(nme.x_0), List(evt), underlying))
7878

79-
val defn = ctx.definitions
80-
81-
val underlyingCls = underlying.classSymbol
82-
val underlyingClsName =
83-
if (underlyingCls.isNumericValueClass || underlyingCls == defn.BooleanClass) underlyingCls.name
84-
else nme.Object
85-
86-
val syp = ctx.requiredClass(s"dotty.runtime.vc.VC${underlyingClsName}Companion").asClass
87-
88-
newSuperClass = tpd.ref(syp).select(nme.CONSTRUCTOR).appliedToType(valueClass.typeRef).tpe.resultType
89-
9079
decls1.enter(u2evtSym)
9180
decls1.enter(evt2uSym)
9281
}
9382

94-
// Add the extension methods, the cast methods u2evt$ and evt2u$, and a VC*Companion superclass
95-
moduleClassSym.copySymDenotation(info =
96-
cinfo.derivedClassInfo(
97-
// FIXME: use of VC*Companion superclasses is disabled until the conflicts with SyntheticMethods are solved.
98-
//classParents = ctx.normalizeToClassRefs(List(newSuperClass), moduleSym, decls1),
99-
decls = decls1))
83+
// Add the extension methods, the cast methods u2evt$ and evt2u$
84+
moduleClassSym.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1))
10085
case _ =>
10186
moduleClassSym
10287
}

src/dotty/tools/dotc/transform/SeqLiterals.scala

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import dotty.tools.dotc.transform.TreeTransforms._
77
import Contexts.Context
88
import Symbols._
99
import Phases._
10-
import Decorators._
10+
import Decorators._, ValueClasses._
1111

1212
/** A transformer that eliminates SeqLiteral's, transforming `SeqLiteral(elems)` to an operation
1313
* equivalent to
@@ -36,13 +36,20 @@ class SeqLiterals extends MiniPhaseTransform {
3636
//println(i"trans seq $tree, arr = $arr: ${arr.tpe} ${arr.tpe.elemType}")
3737
val elemtp = tree.elemtpt.tpe
3838
val elemCls = elemtp.classSymbol
39-
val (wrapMethStr, targs) =
40-
if (elemCls.isPrimitiveValueClass) (s"wrap${elemCls.name}Array", Nil)
41-
else if (elemtp derivesFrom defn.ObjectClass) ("wrapRefArray", elemtp :: Nil)
42-
else ("genericWrapArray", elemtp :: Nil)
43-
ref(defn.ScalaPredefModule)
44-
.select(wrapMethStr.toTermName)
45-
.appliedToTypes(targs)
46-
.appliedTo(arr)
39+
if (isDerivedValueClass(elemCls)) {
40+
ref(defn.DottyPredefModule)
41+
.select("wrapVCArray".toTermName)
42+
.appliedToTypes(List(elemtp))
43+
.appliedTo(arr)
44+
} else {
45+
val (wrapMethStr, targs) =
46+
if (elemCls.isPrimitiveValueClass) (s"wrap${elemCls.name}Array", Nil)
47+
else if (elemtp derivesFrom defn.ObjectClass) ("wrapRefArray", elemtp :: Nil)
48+
else ("genericWrapArray", elemtp :: Nil)
49+
ref(defn.ScalaPredefModule)
50+
.select(wrapMethStr.toTermName)
51+
.appliedToTypes(targs)
52+
.appliedTo(arr)
53+
}
4754
}
4855
}

src/dotty/tools/dotc/transform/SyntheticMethods.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import scala.language.postfixOps
3131
* implementation already exists:
3232
*
3333
* def equals(other: Any): Boolean
34-
* def hashCode(): Int
3534
*/
3635
class SyntheticMethods(thisTransformer: DenotTransformer) {
3736
import ast.tpd._
@@ -41,7 +40,7 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
4140

4241
private def initSymbols(implicit ctx: Context) =
4342
if (myValueSymbols.isEmpty) {
44-
myValueSymbols = List(defn.Any_hashCode, defn.Any_equals)
43+
myValueSymbols = List(defn.Any_equals)
4544
myCaseSymbols = myValueSymbols ++ List(defn.Any_toString, defn.Product_canEqual,
4645
defn.Product_productArity, defn.Product_productPrefix)
4746
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import ast.{Trees, tpd}
5+
import core._, core.Decorators._
6+
import Contexts._, Trees._, Types._, StdNames._, Symbols._
7+
import DenotTransformers._, TreeTransforms._, Phases.Phase
8+
import TypeErasure.ErasedValueType, ValueClasses._
9+
10+
/** This phase erases arrays of value classes to their runtime representation.
11+
*
12+
* For a value class V whose erased underlying type is U, an array of V has type
13+
* Array[V] before Erasure and Array[ErasedValueType(V, U)] afterwards. This phase
14+
* replaces this type by VCXArray where X is "U" if U is a primitive type and is "Object"
15+
* otherwise.
16+
*/
17+
class VCArrays extends MiniPhaseTransform with InfoTransformer {
18+
import tpd._
19+
20+
override def phaseName: String = "vcArrays"
21+
22+
override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type =
23+
eraseVCArrays(tp)
24+
25+
private def eraseVCArrays(tp: Type)(implicit ctx: Context): Type = tp match {
26+
case JavaArrayType(ErasedValueType(cls, _)) =>
27+
defn.vcArrayOf(cls).typeRef
28+
case tp: MethodType =>
29+
val paramTypes = tp.paramTypes.mapConserve(eraseVCArrays)
30+
val retType = eraseVCArrays(tp.resultType)
31+
tp.derivedMethodType(tp.paramNames, paramTypes, retType)
32+
case _ =>
33+
tp
34+
}
35+
36+
private def transformTypeOfTree(tree: Tree)(implicit ctx: Context): Tree =
37+
tree.withType(eraseVCArrays(tree.tpe))
38+
39+
override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
40+
val tpt1 = transformTypeOfTree(tree.tpt)
41+
cpy.ValDef(tree)(tpt = tpt1)
42+
}
43+
override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
44+
val tpt1 = transformTypeOfTree(tree.tpt)
45+
cpy.DefDef(tree)(tpt = tpt1)
46+
}
47+
48+
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree =
49+
tree match {
50+
case TypeApply(sel @ Select(_, _), _) if (sel.symbol == defn.newRefArrayMethod) =>
51+
// Preserve the semi-erased type of the array so that we can properly transform
52+
// it in transformApply
53+
tree
54+
case TypeApply(fun, args) =>
55+
val tree1 = cpy.TypeApply(tree)(fun, args.map(transformTypeOfTree(_)))
56+
transformTypeOfTree(tree1)
57+
}
58+
59+
override def transformSeqLiteral(tree: SeqLiteral)(implicit ctx: Context, info: TransformerInfo): Tree =
60+
tree.tpe match {
61+
// [arg1, arg2, ...] => new VCXArray([V.evt2u$(arg1), V.evt2u$(arg2), ...])
62+
case JavaArrayType(ErasedValueType(cls, _)) =>
63+
val evt2uMethod = ref(evt2u(cls))
64+
val underlyingArray = JavaSeqLiteral(tree.elems.map(evt2uMethod.appliedTo(_)))
65+
val mod = cls.companionModule
66+
New(defn.vcArrayOf(cls).typeRef, List(underlyingArray, ref(mod)))
67+
case _ =>
68+
tree
69+
}
70+
71+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
72+
tree match {
73+
// newRefArray[ErasedValueType(V, U)[]](args) => New VCXArray(newXArray(args), V)
74+
case Apply(ta @ TypeApply(sel @ Select(_,_), List(targ)), args)
75+
if (sel.symbol == defn.newRefArrayMethod) =>
76+
targ.tpe match {
77+
case JavaArrayType(ErasedValueType(cls, underlying)) =>
78+
val mod = cls.companionModule
79+
New(defn.vcArrayOf(cls).typeRef,
80+
List(newArray(TypeTree(underlying), tree.pos).appliedToArgs(args),
81+
ref(mod)))
82+
case _ =>
83+
tree
84+
}
85+
// array.[]update(idx, elem) => array.arr().[]update(idx, elem)
86+
case Apply(Select(array, nme.primitive.arrayUpdate), List(idx, elem)) =>
87+
elem.tpe.widen match {
88+
case ErasedValueType(cls, _) =>
89+
array.select(nme.ARR).appliedToNone
90+
.select(nme.primitive.arrayUpdate).appliedTo(idx, ref(evt2u(cls)).appliedTo(elem))
91+
case _ =>
92+
tree
93+
}
94+
// array.[]apply(idx) => array.arr().[]apply(idx)
95+
case Apply(Select(array, nme.primitive.arrayApply), List(idx)) =>
96+
tree.tpe.widen match {
97+
case ErasedValueType(cls, _) =>
98+
ref(u2evt(cls)).appliedTo(array.select(nme.ARR).appliedToNone
99+
.select(nme.primitive.arrayApply).appliedTo(idx))
100+
case _ =>
101+
tree
102+
}
103+
// array.[]length() => array.arr().[]length()
104+
case Apply(Select(array, nme.primitive.arrayLength), Nil)
105+
if (array.tpe <:< defn.VCArrayPrototypeType) =>
106+
array.select(nme.ARR).appliedToNone
107+
.select(nme.primitive.arrayLength).appliedToNone
108+
case _ =>
109+
tree
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)