Skip to content

Commit 1e4d7ea

Browse files
committed
Implemented handling of <label> DefDefs in backend.
1 parent b649129 commit 1e4d7ea

File tree

3 files changed

+202
-7
lines changed

3 files changed

+202
-7
lines changed

src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
6767
type ArrayValue = NonExistentTree
6868
type ApplyDynamic = NonExistentTree
6969
type ModuleDef = NonExistentTree
70-
type LabelDef = NonExistentTree
71-
70+
type LabelDef = tpd.DefDef
7271

7372
val NoSymbol = Symbols.NoSymbol
7473
val NoPosition: Position = Positions.NoPosition
@@ -216,6 +215,8 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
216215

217216
def emitAsmp: Option[String] = None
218217

218+
def shouldEmitJumpAfterLabels = true
219+
219220
def dumpClasses: Option[String] =
220221
if(ctx.settings.Ydumpclasses.isDefault) None
221222
else Some(ctx.settings.Ydumpclasses.value)
@@ -549,6 +550,9 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
549550
}
550551
case Types.ClassInfo(_, sym, _, _, _) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info)
551552

553+
case t: MethodType => // triggers for LabelDefs
554+
t.resultType.toTypeKind(ct)(storage)
555+
552556
/* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for
553557
* meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning.
554558
* The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala.
@@ -671,9 +675,15 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{
671675
}
672676

673677
object LabelDef extends LabelDeconstructor {
674-
def _1: Name = ???
675-
def _2: List[Ident] = ???
676-
def _3: Tree = ???
678+
def _1: Name = field.name
679+
def _2: List[Symbol] = field.vparamss.flatMap(_.map(_.symbol))
680+
def _3: Tree = field.rhs
681+
682+
override def unapply(s: LabelDef): DottyBackendInterface.this.LabelDef.type = {
683+
if(s.symbol is Flags.Label) this.field = s
684+
else this.field = null
685+
this
686+
}
677687
}
678688

679689
object Typed extends TypedDeconstrutor {
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package dotty.tools.backend.jvm
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Contexts.Context
5+
import dotty.tools.dotc.core.Types
6+
import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, MiniPhase, MiniPhaseTransform}
7+
import dotty.tools.dotc.ast.tpd
8+
import dotty.tools.dotc
9+
import dotty.tools.dotc.backend.jvm.DottyPrimitives
10+
import dotty.tools.dotc.core.Flags.FlagSet
11+
import dotty.tools.dotc.transform.Erasure
12+
import dotty.tools.dotc.transform.SymUtils._
13+
import java.io.{File => JFile}
14+
15+
import scala.collection.generic.Clearable
16+
import scala.collection.mutable
17+
import scala.collection.mutable.{ListBuffer, ArrayBuffer}
18+
import scala.reflect.ClassTag
19+
import scala.reflect.internal.util.WeakHashSet
20+
import scala.reflect.io.{Directory, PlainDirectory, AbstractFile}
21+
import scala.tools.asm.{ClassVisitor, FieldVisitor, MethodVisitor}
22+
import scala.tools.nsc.backend.jvm.{BCodeHelpers, BackendInterface}
23+
import dotty.tools.dotc.core._
24+
import Periods._
25+
import SymDenotations._
26+
import Contexts._
27+
import Types._
28+
import Symbols._
29+
import Denotations._
30+
import Phases._
31+
import java.lang.AssertionError
32+
import dotty.tools.dotc.util.Positions.Position
33+
import Decorators._
34+
import tpd._
35+
import StdNames.nme
36+
37+
/**
38+
* Verifies that each Label DefDef has only a single address to jump back and
39+
* reorders them such that they are not nested and this address is a fall-through address for JVM
40+
*
41+
* ei such code
42+
*
43+
*
44+
* <label> def foo(i: Int) = {
45+
* <label> def bar = 0
46+
* <label> def dough(i: Int) = if(i == 0) bar else foo(i-1)
47+
* dough(i)
48+
* }
49+
*
50+
* foo(100)
51+
*
52+
* will get rewritten to
53+
*
54+
* \
55+
* <label> def foo(i: Int) = dough(i)
56+
* <label> def dough(i: Int) = if(i == 0) bar else foo(i-1)
57+
* <label> def bar = 2
58+
* foo(100)
59+
*
60+
* Proposed way to generate this pattern in backend is:
61+
*
62+
* foo(100)
63+
* <jump foo>
64+
* <label> def foo(i: Int) = dough(i)
65+
* // <jump a> // unreachable
66+
* <label> def dough(i: Int) = if(i == 0) bar else foo(i-1)
67+
* // <jump a> // unreachable
68+
* <label> def bar = 2
69+
* // <jump a> // unreachable
70+
* <asm point a>
71+
*
72+
* Unreachable jumps will be eliminated by local dead code analysis.
73+
* After JVM is smart enough to remove next-line jumps
74+
*
75+
* Note that Label DefDefs can be only nested in Block, otherwise no one would be able to call them
76+
* Other DefDefs are eliminated
77+
*/
78+
class LabelDefs extends MiniPhaseTransform {
79+
def phaseName: String = "labelDef"
80+
81+
val queue = new ArrayBuffer[Tree]()
82+
83+
84+
85+
override def transformBlock(tree: tpd.Block)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
86+
collectLabelDefs.clear
87+
val newStats = collectLabelDefs.transformStats(tree.stats)
88+
val newExpr = collectLabelDefs.transform(tree.expr)
89+
val labelCalls = collectLabelDefs.labelCalls
90+
val entryPoints = collectLabelDefs.parentLabelCalls
91+
val labelDefs = collectLabelDefs.labelDefs
92+
93+
// make sure that for every label there's a single location it should return and single entry point
94+
// if theres already a location that it returns to that's a failure
95+
val disallowed = new mutable.HashMap[Symbol, Tree]()
96+
queue.sizeHint(labelCalls.size + entryPoints.size)
97+
def moveLabels(entryPoint: Tree): List[Tree] = {
98+
if((entryPoint.symbol is Flags.Label) && labelDefs.contains(entryPoint.symbol)) {
99+
val visitedNow = new mutable.HashMap[Symbol, Tree]()
100+
val treesToAppend = new ArrayBuffer[Tree]() // order matters. parents should go first
101+
queue.clear()
102+
103+
var visited = 0
104+
queue += entryPoint
105+
while (visited < queue.size) {
106+
val owningLabelDefSym = queue(visited).symbol
107+
val owningLabelDef = labelDefs(owningLabelDefSym)
108+
for (call <- labelCalls(owningLabelDefSym))
109+
if (disallowed.contains(call.symbol)) {
110+
val oldCall = disallowed(call.symbol)
111+
ctx.error(s"Multiple return locations for Label $oldCall and $call", call.symbol.pos)
112+
} else {
113+
if ((!visitedNow.contains(call.symbol)) && labelDefs.contains(call.symbol)) {
114+
val df = labelDefs(call.symbol)
115+
visitedNow.put(call.symbol, labelDefs(call.symbol))
116+
queue += call
117+
}
118+
}
119+
if(!treesToAppend.contains(owningLabelDef))
120+
treesToAppend += owningLabelDef
121+
visited += 1
122+
}
123+
disallowed ++= visitedNow
124+
125+
treesToAppend.toList
126+
} else Nil
127+
}
128+
129+
cpy.Block(tree)(entryPoints.flatMap(moveLabels).toList ++ newStats, newExpr)
130+
131+
}
132+
133+
val collectLabelDefs = new TreeMap() {
134+
135+
// label calls from this DefDef
136+
var parentLabelCalls: mutable.Set[Tree] = new mutable.HashSet[Tree]()
137+
var isInsideLabel = false
138+
var isInsideBlock = false
139+
140+
def shouldMoveLabel = !isInsideBlock
141+
142+
// labelSymbol -> Defining tree
143+
val labelDefs = new mutable.HashMap[Symbol, Tree]()
144+
// owner -> all calls by this owner
145+
val labelCalls = new mutable.HashMap[Symbol, mutable.Set[Tree]]()
146+
val labelCallCounts = new mutable.HashMap[Symbol, Int]()
147+
148+
def clear = {
149+
parentLabelCalls.clear()
150+
labelDefs.clear()
151+
labelCalls.clear()
152+
}
153+
154+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
155+
case t: Template => t
156+
case t: Block =>
157+
val tmp = isInsideBlock
158+
isInsideBlock = true
159+
val r = super.transform(t)
160+
isInsideBlock = tmp
161+
r
162+
case t: DefDef =>
163+
assert(t.symbol is Flags.Label)
164+
val st = parentLabelCalls
165+
parentLabelCalls = new mutable.HashSet[Tree]()
166+
val tmp = isInsideLabel
167+
isInsideLabel = true
168+
val r = super.transform(tree)
169+
isInsideLabel = tmp
170+
labelCalls(r.symbol) = parentLabelCalls
171+
parentLabelCalls = st
172+
if(shouldMoveLabel) {
173+
labelDefs(r.symbol) = r
174+
EmptyTree
175+
} else r
176+
case t: Apply if t.symbol is Flags.Label =>
177+
parentLabelCalls = parentLabelCalls + t
178+
labelCallCounts.get(t.symbol)
179+
super.transform(tree)
180+
case _ =>
181+
super.transform(tree)
182+
183+
}
184+
}
185+
}

src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import dotty.tools.dotc.core.DenotTransformers.DenotTransformer
1515
import dotty.tools.dotc.core.Denotations.SingleDenotation
1616

1717

18-
import dotty.tools.backend.jvm.GenBCode
18+
import dotty.tools.backend.jvm.{LabelDefs, GenBCode}
1919

2020
class Compiler {
2121

@@ -63,7 +63,7 @@ class Compiler {
6363
List(new LambdaLift,
6464
new Flatten,
6565
new RestoreScopes),
66-
List(new PrivateToStatic, new CollectEntryPoints),
66+
List(new PrivateToStatic, new CollectEntryPoints, new LabelDefs),
6767
List(new GenBCode)
6868
)
6969

0 commit comments

Comments
 (0)