Skip to content

Commit 88946d5

Browse files
committed
Erasure phase PoC
Still missing: bridge method generation, signatures. Other changes - Turned around Checking and NoChecking. Checking is the default, NoChecking disables it. - Refactored Typer#typed to expose typedNamed, so that it can be overridden in erasure. - Made logging more forgiving wrt off-buy-one phase errors.
1 parent aacc3d5 commit 88946d5

File tree

9 files changed

+386
-60
lines changed

9 files changed

+386
-60
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import dotty.tools.dotc.core.Denotations.SingleDenotation
1717

1818
class Compiler {
1919

20-
def phases: List[List[Phase]] = List(
20+
def phases: List[List[Phase]] =
21+
List(
2122
List(new FrontEnd),
2223
List(new LazyValsCreateCompanionObjects), //force separataion between lazyVals and LVCreateCO
2324
List(new LazyValTranformContext().transformer, new TypeTestsCasts),

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ class Definitions {
182182
lazy val DoubleClass = valueClassSymbol("scala.Double", BoxedDoubleClass, java.lang.Double.TYPE, DoubleEnc)
183183

184184
lazy val BoxedUnitClass = ctx.requiredClass("scala.runtime.BoxedUnit")
185+
186+
lazy val BoxedUnit_UNIT = BoxedUnitClass.linkedClass.requiredValue("UNIT")
187+
185188
lazy val BoxedBooleanClass = ctx.requiredClass("java.lang.Boolean")
186189
lazy val BoxedByteClass = ctx.requiredClass("java.lang.Byte")
187190
lazy val BoxedShortClass = ctx.requiredClass("java.lang.Short")
@@ -428,6 +431,8 @@ class Definitions {
428431
FloatClass,
429432
DoubleClass)
430433

434+
lazy val ScalaBoxedClasses = ScalaValueClasses map boxedClass
435+
431436
private[this] val _boxedClass = mutable.Map[Symbol, Symbol]()
432437
private[this] val _unboxedClass = mutable.Map[Symbol, Symbol]()
433438

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ object NameOps {
181181
name.drop(tpnme.HK_TRAIT_PREFIX.length).toList.map(varianceOfSuffix)
182182
}
183183

184+
/** The name of the generic runtime operation corresponding to an array operation */
185+
def genericArrayOp: TermName = name match {
186+
case nme.apply => nme.array_apply
187+
case nme.length => nme.array_length
188+
case nme.update => nme.array_update
189+
case nme.clone_ => nme.array_clone
190+
}
191+
184192
/** If name length exceeds allowable limit, replace part of it by hash */
185193
def compactified(implicit ctx: Context): TermName = termName(compactify(name.toString))
186194
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,10 @@ object SymDenotations {
12141214
val cname = if (this is ImplClass) nme.IMPLCLASS_CONSTRUCTOR else nme.CONSTRUCTOR
12151215
decls.denotsNamed(cname).first.symbol
12161216
}
1217+
1218+
def underlyingOfValueClass: Type = ???
1219+
1220+
def valueClassUnbox: Symbol = ???
12171221
}
12181222

12191223
/** The denotation of a package class.

src/dotty/tools/dotc/core/transform/Erasure.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import util.DotClass
77

88
object Erasure {
99

10+
case class ErasedValueType(cls: ClassSymbol, underlying: Type) extends CachedGroundType {
11+
override def computeHash = doHash(cls, underlying)
12+
}
13+
1014
private def erasureIdx(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcardOK: Boolean) =
1115
(if (isJava) 1 else 0) +
1216
(if (isSemi) 2 else 0) +
@@ -123,10 +127,14 @@ class Erasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcard
123127
val parent = tp.parent
124128
if (parent isRef defn.ArrayClass) eraseArray(tp)
125129
else this(parent)
126-
case tp: ConstantType =>
130+
case tp: TermRef =>
131+
val sym = tp.symbol
132+
if (sym.owner is Package) sym.termRef
133+
else tp.derivedSelect(this(tp.prefix))
134+
case _: ThisType | _: ConstantType =>
127135
tp
128136
case tp: TypeProxy =>
129-
this(tp.underlying)
137+
this(tp.underlying)
130138
case AndType(tp1, tp2) =>
131139
mergeAnd(this(tp1), this(tp2))
132140
case OrType(tp1, tp2) =>
@@ -138,7 +146,7 @@ class Erasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wildcard
138146
case tp: PolyType =>
139147
this(tp.resultType)
140148
case tp @ ClassInfo(pre, cls, classParents, decls, _) =>
141-
def eraseTypeRef = this.asInstanceOf[TypeRef => TypeRef]
149+
def eraseTypeRef(p: TypeRef) = this(p).asInstanceOf[TypeRef]
142150
val parents: List[TypeRef] =
143151
if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil
144152
else if (cls eq defn.ArrayClass) defn.ObjectClass.typeRef :: Nil

src/dotty/tools/dotc/reporting/Reporter.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,16 @@ trait Reporting { this: Context =>
106106
def incompleteInputError(msg: String, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit =
107107
reporter.incomplete(Diagnostic(msg, pos, ERROR))(ctx)
108108

109+
/** Log msg if current phase or its precedessor is mentioned in
110+
* settings.log.
111+
* The reason we also pick the predecessor is that during the
112+
* tree transform of phase X, we often are already in phase X+1.
113+
* It's convenient to have logging work independently of whether
114+
* we have advanced the phase or not.
115+
*/
109116
def log(msg: => String): Unit =
110-
if (this.settings.log.value.containsPhase(phase))
117+
if (this.settings.log.value.containsPhase(phase) ||
118+
this.settings.log.value.containsPhase(phase.prev))
111119
echo(s"[log ${ctx.phasesStack.reverse.mkString(" -> ")}] $msg")
112120

113121
def debuglog(msg: => String): Unit =
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core.Phases._
5+
import core.DenotTransformers._
6+
import core.Denotations._
7+
import core.SymDenotations._
8+
import core.Symbols._
9+
import core.Contexts._
10+
import core.Types._
11+
import core.Names._
12+
import core.StdNames._
13+
import core.NameOps._
14+
import core.Decorators._
15+
import core.Constants._
16+
import typer.NoChecking
17+
import typer.ProtoTypes._
18+
import typer.ErrorReporting._
19+
import core.transform.Erasure._
20+
import core.Decorators._
21+
import ast.{tpd, untpd}
22+
import ast.Trees._
23+
24+
class Erasure extends Phase with DenotTransformer {
25+
26+
override def name: String = "erasure"
27+
28+
def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match {
29+
case ref: SymDenotation =>
30+
assert(ctx.phase == this, s"transforming $ref at ${ctx.phase}")
31+
ref.copySymDenotation(info = transformInfo(ref.symbol, ref.info))
32+
case ref =>
33+
ref.derivedSingleDenotation(ref.symbol, erasure(ref.info))
34+
}
35+
36+
val eraser = new Erasure.Typer
37+
38+
def run(implicit ctx: Context): Unit = {
39+
val unit = ctx.compilationUnit
40+
unit.tpdTree = eraser.typedExpr(unit.tpdTree)(ctx.fresh.withPhase(this.next))
41+
}
42+
}
43+
44+
object Erasure {
45+
46+
import tpd._
47+
48+
object Boxing {
49+
50+
def isUnbox(sym: Symbol)(implicit ctx: Context) =
51+
sym.name == nme.unbox && (defn.ScalaBoxedClasses contains sym.owner)
52+
53+
def isBox(sym: Symbol)(implicit ctx: Context) =
54+
sym.name == nme.box && (defn.ScalaValueClasses contains sym.owner)
55+
56+
def boxMethod(cls: ClassSymbol)(implicit ctx: Context) = cls.info.member(nme.box).symbol
57+
def unboxMethod(cls: ClassSymbol)(implicit ctx: Context) = cls.info.member(nme.unbox).symbol
58+
59+
/** Isf this tree is an unbox operation which can be safely removed
60+
* when enclosed in a box, the unboxed argument, otherwise EmptyTree.
61+
* Note that one can't always remove a Box(Unbox(x)) combination because the
62+
* process of unboxing x may lead to throwing an exception.
63+
* This is important for specialization: calls to the super constructor should not box/unbox specialized
64+
* fields (see TupleX). (ID)
65+
*/
66+
private def safelyRemovableUnboxArg(tree: Tree)(implicit ctx: Context): Tree = tree match {
67+
case Apply(fn, arg :: Nil)
68+
if isUnbox(fn.symbol) && (defn.ScalaBoxedClasses contains arg.tpe.typeSymbol) =>
69+
arg
70+
case _ =>
71+
EmptyTree
72+
}
73+
74+
def isErasedValueType(tpe: Type)(implicit ctx: Context): Boolean = tpe.isInstanceOf[ErasedValueType]
75+
def isPrimitiveValueType(tpe: Type)(implicit ctx: Context): Boolean = tpe.classSymbol.isPrimitiveValueClass
76+
77+
def constant(tree: Tree, const: Tree)(implicit ctx: Context) =
78+
if (isIdempotentExpr(tree)) Block(tree :: Nil, const) else const
79+
80+
final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") {
81+
tree.tpe match {
82+
case ErasedValueType(clazz, _) =>
83+
New(clazz.typeRef, cast(tree, clazz.underlyingOfValueClass) :: Nil) // todo: use adaptToType?
84+
case _ =>
85+
val cls = tree.tpe.classSymbol
86+
if (cls eq defn.UnitClass) constant(tree, ref(defn.BoxedUnit_UNIT))
87+
else if (cls eq defn.NothingClass) tree // a non-terminating expression doesn't need boxing
88+
else {
89+
assert(cls ne defn.ArrayClass)
90+
val arg = safelyRemovableUnboxArg(tree)
91+
if (arg.isEmpty) Apply(ref(boxMethod(cls.asClass)), tree :: Nil)
92+
else {
93+
ctx.log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}")
94+
arg
95+
}
96+
}
97+
}
98+
}
99+
100+
def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") {
101+
pt match {
102+
case ErasedValueType(clazz, underlying) =>
103+
val tree1 =
104+
if ((tree.tpe isRef defn.NullClass) && isPrimitiveValueType(underlying))
105+
// convert `null` directly to underlying type, as going
106+
// via the unboxed type would yield a NPE (see SI-5866)
107+
unbox(tree, underlying)
108+
else
109+
Apply(Select(adaptToType(tree, clazz.typeRef), clazz.valueClassUnbox), Nil)
110+
cast(tree1, pt)
111+
case _ =>
112+
val cls = pt.classSymbol
113+
if (cls eq defn.UnitClass) constant(tree, Literal(Constant(())))
114+
else {
115+
assert(cls ne defn.ArrayClass)
116+
Apply(ref(unboxMethod(cls.asClass)), tree :: Nil)
117+
}
118+
}
119+
}
120+
121+
/** Generate a synthetic cast operation from tree.tpe to pt.
122+
*/
123+
def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree =
124+
if (pt isRef defn.UnitClass) unbox(tree, pt)
125+
else (tree.tpe, pt) match {
126+
case (defn.ArrayType(treeElem), defn.ArrayType(ptElem))
127+
if isPrimitiveValueType(treeElem) && !isPrimitiveValueType(ptElem) =>
128+
// See SI-2386 for one example of when this might be necessary.
129+
cast(runtimeCall(nme.toObjectArray, tree :: Nil), pt)
130+
case _ =>
131+
println(s"casting from ${tree.showSummary}: ${tree.tpe.show} to ${pt.show}")
132+
TypeApply(Select(tree, defn.Object_asInstanceOf), TypeTree(pt) :: Nil)
133+
}
134+
135+
/** Adaptation of an expression `e` to an expected type `PT`, applying the following
136+
* rewritings exhaustively as long as the type of `e` is not a subtype of `PT`.
137+
*
138+
* e -> box(e) if `e` is of erased value type
139+
* e -> unbox(e, PT) otherwise, if `PT` is an erased value type
140+
* e -> box(e) if `e` is of primitive type and `PT` is not a primitive type
141+
* e -> unbox(e, PT) if `PT` is a primitive type and `e` is not of primitive type
142+
* e -> cast(e, PT) otherwise
143+
*/
144+
def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree =
145+
if (tree.tpe <:< pt)
146+
tree
147+
else if (isErasedValueType(tree.tpe))
148+
adaptToType(box(tree), pt)
149+
else if (isErasedValueType(pt))
150+
adaptToType(unbox(tree, pt), pt)
151+
else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt))
152+
adaptToType(box(tree), pt)
153+
else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe))
154+
adaptToType(unbox(tree, pt), pt)
155+
else
156+
cast(tree, pt)
157+
}
158+
159+
class Typer extends typer.Typer with NoChecking {
160+
import Boxing._
161+
162+
def box(tree: Tree): Tree = ???
163+
def unbox(tree: Tree, target: Type): Tree = ???
164+
def cast(tree: Tree, target: Type): Tree = ???
165+
166+
private def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
167+
assert(tree.hasType)
168+
println(s"prompting ${tree.show}: ${tree.tpe.asInstanceOf[Type].showWithUnderlying(2)}")
169+
tree.withType(erasure(tree.tpe.asInstanceOf[Type]))
170+
}
171+
172+
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = {
173+
val tree1 = promote(tree)
174+
println(i"typed ident ${tree.name}: ${tree1.tpe} at phase ${ctx.phase}, history = ${tree1.symbol.history}")
175+
tree1
176+
}
177+
178+
/** Type check select nodes, applying the following rewritings exhaustively
179+
* on selections `e.m`.
180+
*
181+
* e.m1 -> e.m2 if `m1` is a member of Any or AnyVal and `m2` is
182+
* the same-named member in Object.
183+
* e.m -> box(e).m if `e` is primitive and `m` is a member or a reference class
184+
* or `e` has an erased value class type.
185+
* e.m -> unbox(e).m if `e` is not primitive and `m` is a member of a primtive type.
186+
*
187+
* Additionally, if the type of `e` does not derive from the type `OT` of the owner of `m`,
188+
* the following rewritings are performed, where `ET` is the erased type of the selection's
189+
* original qualifier expression.
190+
*
191+
* e.m -> cast(OT).m if `m` is not an array operation
192+
* e.m -> cast(ET).m if `m` is an array operation and `ET` is an array type
193+
* e.m -> runtime.array_m(e)
194+
* if `m` is an array operation and `ET` is Object
195+
*/
196+
override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
197+
val sym = tree.symbol
198+
assert(sym.exists)
199+
200+
def select(qual: Tree, sym: Symbol): Tree =
201+
untpd.cpy.Select(tree, qual, sym.name) withType qual.tpe.select(sym)
202+
203+
def selectArrayMember(qual: Tree, erasedPre: Type) =
204+
if (erasedPre isRef defn.ObjectClass) runtimeCall(tree.name.genericArrayOp, qual :: Nil)
205+
else recur(cast(qual, erasedPre))
206+
207+
def recur(qual: Tree): Tree = {
208+
val qualIsPrimitive = isPrimitiveValueType(qual.tpe)
209+
val symIsPrimitive = sym.owner.isPrimitiveValueClass
210+
if ((sym.owner eq defn.AnyClass) || (sym.owner eq defn.AnyValClass))
211+
select(qual, defn.ObjectClass.info.decl(sym.name).symbol)
212+
else if (qualIsPrimitive && !symIsPrimitive || isErasedValueType(qual.tpe))
213+
recur(box(qual))
214+
else if (!qualIsPrimitive && symIsPrimitive)
215+
recur(unbox(qual, sym.owner.typeRef))
216+
else if (qual.tpe.derivesFrom(sym.owner) || qual.isInstanceOf[Super])
217+
select(qual, sym)
218+
else if (sym.owner eq defn.ArrayClass)
219+
selectArrayMember(qual, erasure(tree.qualifier.tpe))
220+
else
221+
recur(cast(qual, sym.owner.typeRef))
222+
}
223+
224+
recur(typed(tree.qualifier, AnySelectionProto))
225+
}
226+
227+
override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context) =
228+
typedExpr(tree.fun, pt)
229+
230+
override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
231+
val Apply(fun, args) = tree
232+
val fun1 = typedExpr(fun, WildcardType)
233+
fun1.tpe.widen match {
234+
case mt: MethodType =>
235+
val args1 = args.zipWithConserve(mt.paramTypes)(typedExpr)
236+
untpd.cpy.Apply(tree, fun1, args1) withType mt.resultType
237+
}
238+
}
239+
240+
override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree =
241+
promote(tree)
242+
243+
override def ensureNoLocalRefs(block: Block, pt: Type, forcedDefined: Boolean = false)(implicit ctx: Context): Tree =
244+
block // optimization, no checking needed, as block symbols do not change.
245+
246+
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = {
247+
val ddef1 = untpd.cpy.DefDef(ddef, ddef.mods, ddef.name, Nil, ddef.vparamss, ddef.tpt, ddef.rhs)
248+
super.typedDefDef(ddef1, sym)
249+
}
250+
251+
override def typedClassDef(cdef: untpd.TypeDef, sym: ClassSymbol)(implicit ctx: Context) = {
252+
val TypeDef(mods, name, impl @ Template(constr, parents, self, body)) = cdef
253+
val cdef1 = untpd.cpy.TypeDef(cdef, mods, name,
254+
untpd.cpy.Template(impl, constr, parents, untpd.EmptyValDef, body))
255+
super.typedClassDef(cdef1, sym)
256+
}
257+
258+
/*
259+
override def transformStats(stats: List[Tree], exprOwner: Symbol)(implicit ctx: Context) = {
260+
val stats1 = super.transform(stats, exprOwner)
261+
if (ctx.owner.isClass) addBridges(stats1) else stats1
262+
}
263+
*/
264+
override def typedNamed(tree: untpd.NameTree, pt: Type)(implicit ctx: Context): Tree = {
265+
if (tree eq untpd.EmptyValDef) return tpd.EmptyValDef
266+
assert(tree.hasType, tree)
267+
val sym = tree.symbol
268+
assert(sym.exists, tree)
269+
def localContext = ctx.fresh.withTree(tree).withOwner(sym)
270+
tree match {
271+
case tree: untpd.Ident => typedIdent(tree, pt)
272+
case tree: untpd.Select => typedSelect(tree, pt)
273+
case tree: untpd.ValDef => typedValDef(tree, sym)(localContext)
274+
case tree: untpd.DefDef => typedDefDef(tree, sym)(localContext)
275+
case tree: untpd.TypeDef =>
276+
if (tree.isClassDef) typedClassDef(tree, sym.asClass)(localContext)
277+
else EmptyTree
278+
}
279+
}
280+
281+
override def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree =
282+
ctx.traceIndented(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) {
283+
assert(ctx.phase == ctx.erasurePhase.next)
284+
if (tree.isEmpty) tree else adaptToType(tree, pt)
285+
}
286+
}
287+
}

0 commit comments

Comments
 (0)