Skip to content

Commit 5a52095

Browse files
committed
Make scheduler deterministic by propagating odering constraints
1 parent fd8a02e commit 5a52095

File tree

3 files changed

+87
-34
lines changed

3 files changed

+87
-34
lines changed

compiler/src/dotty/tools/dotc/config/Printers.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ object Printers {
3333
val exhaustivity: Printer = noPrinter
3434
val patmatch: Printer = noPrinter
3535
val simplify: Printer = noPrinter
36+
val plugins: Printer = noPrinter
3637
}

compiler/src/dotty/tools/dotc/plugins/Plugins.scala

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Contexts._
66
import config.PathResolver
77
import dotty.tools.io._
88
import Phases._
9+
import config.Printers.plugins.{ println => debug }
910

1011
import scala.collection.mutable.ListBuffer
1112

@@ -144,56 +145,107 @@ object Plugins {
144145
* Note: this algorithm is factored out for unit test.
145146
*/
146147
def schedule(plan: List[List[Phase]], pluginPhases: List[PluginPhase]): List[List[Phase]] = {
147-
import scala.collection.mutable.{ Set => MSet, Map => MMap }
148-
type OrderingReq = (MSet[Class[_]], MSet[Class[_]])
148+
import scala.collection.mutable.{ Map => MMap, Set => MSet }
149+
type OrderingReq = (Set[Class[_]], Set[Class[_]])
149150

150151
val orderRequirements = MMap[Class[_], OrderingReq]()
151-
val existingPhases = {
152-
val set = MSet.empty[Class[_]]
153-
for (ps <- plan; p <- ps) set += p.getClass
154-
set
152+
val primitivePhases = plan.flatMap(ps => ps.map(_.getClass.asInstanceOf[Class[_]])).toSet
153+
154+
def isPrimitive(phase: Class[_]): Boolean = primitivePhases.contains(phase)
155+
156+
def constraintConflict(phase: Phase): String = {
157+
val (runsAfter, runsBefore) = orderRequirements(phase.getClass)
158+
s"""
159+
|Ordering conflict for phase ${phase.phaseName}
160+
|after: ${runsAfter.mkString("[", ", ", "]")}
161+
|before: ${runsBefore.mkString("[", ", ", "]")}
162+
""".stripMargin
155163
}
156164

157-
def updateOrdering(phase: PluginPhase): Unit = {
158-
val runsBefore: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*)
159-
val runsAfter: MSet[Class[_]] = MSet(phase.runsAfter.toSeq: _*)
165+
// init ordering map, no propagation
166+
pluginPhases.foreach { phase =>
167+
val runsAfter : Set[Class[_]] = phase.runsAfter.asInstanceOf[Set[Class[_]]]
168+
val runsBefore : Set[Class[_]] = phase.runsBefore.asInstanceOf[Set[Class[_]]]
160169

161-
if (!orderRequirements.contains(phase.getClass)) {
162-
orderRequirements.update(phase.getClass, (runsBefore, runsAfter) )
163-
} else {
164-
val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass)
165-
runsAfter1 ++= runsAfter
166-
runsBefore1 ++= runsBefore
167-
}
170+
orderRequirements.update(phase.getClass, (runsAfter, runsBefore))
171+
}
172+
173+
// propagate ordering constraint : reflexivity
174+
pluginPhases.foreach { phase =>
168175

169-
runsBefore.foreach { phaseClass =>
170-
if (!orderRequirements.contains(phaseClass))
171-
orderRequirements.update(phaseClass, (MSet.empty, MSet.empty))
172-
val (_, runsAfter) = orderRequirements(phaseClass)
173-
runsAfter += phase.getClass
176+
var (runsAfter, runsBefore) = orderRequirements(phase.getClass)
177+
178+
// propagate transitive constraints to related phases
179+
runsAfter.filter(!isPrimitive(_)).foreach { phaseClass =>
180+
val (runsAfter1, runsBefore1) = orderRequirements(phaseClass)
181+
orderRequirements.update(phaseClass, (runsAfter1, runsBefore1 + phase.getClass))
174182
}
175183

176-
runsAfter.foreach { phaseClass =>
177-
if (!orderRequirements.contains(phaseClass))
178-
orderRequirements.update(phaseClass, (MSet.empty, MSet.empty))
179-
val (runsBefore, _) = orderRequirements(phaseClass)
180-
runsBefore += phase.getClass
184+
runsBefore.filter(!isPrimitive(_)).foreach { phaseClass =>
185+
val (runsAfter1, runsBefore1) = orderRequirements(phaseClass)
186+
orderRequirements.update(phaseClass, (runsAfter1 + phase.getClass, runsBefore1))
181187
}
188+
182189
}
183190

184-
pluginPhases.foreach(updateOrdering)
191+
debug(
192+
s""" reflexive constraints:
193+
| ${orderRequirements.mkString("\n")}
194+
""".stripMargin
195+
)
196+
197+
// propagate transitive constraints from related phases to current phase: transitivity
198+
def propagate(phase: Phase): OrderingReq = {
199+
def propagateRunsBefore(beforePhase: Class[_]): Set[Class[_]] =
200+
if (beforePhase == phase.getClass)
201+
throw new Exception(constraintConflict(phase))
202+
else if (primitivePhases.contains(beforePhase))
203+
Set(beforePhase)
204+
else {
205+
val (_, runsBefore) = orderRequirements(beforePhase)
206+
runsBefore.flatMap(propagateRunsBefore) + beforePhase
207+
}
208+
209+
def propagateRunsAfter(afterPhase: Class[_]): Set[Class[_]] =
210+
if (afterPhase == phase.getClass)
211+
throw new Exception(constraintConflict(phase))
212+
else if (primitivePhases.contains(afterPhase))
213+
Set(afterPhase)
214+
else {
215+
val (runsAfter, _) = orderRequirements(afterPhase)
216+
runsAfter.flatMap(propagateRunsAfter) + afterPhase
217+
}
218+
219+
var (runsAfter, runsBefore) = orderRequirements(phase.getClass)
220+
221+
runsAfter = runsAfter.flatMap(propagateRunsAfter)
222+
runsBefore = runsBefore.flatMap(propagateRunsBefore)
223+
224+
// orderRequirements.update(phase.getClass, (runsBefore, runsAfter) )
225+
226+
(runsAfter, runsBefore)
227+
}
185228

186229
var updatedPlan = plan
230+
var insertedPhase = primitivePhases
187231
pluginPhases.sortBy(_.phaseName).foreach { phase =>
188-
val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass)
189-
val runsBefore = runsBefore1 & existingPhases
190-
val runsAfter = runsAfter1 & existingPhases
232+
var (runsAfter1, runsBefore1) = propagate(phase)
233+
234+
debug(
235+
s"""propagated constraints for ${phase}:
236+
|after: ${runsAfter1.mkString("[", ", ", "]")}
237+
|before: ${runsBefore1.mkString("[", ", ", "]")}
238+
""".stripMargin
239+
)
240+
241+
var runsAfter = runsAfter1 & insertedPhase
242+
val runsBefore = runsBefore1 & insertedPhase
191243

192244
// beforeReq met after the split
193245
val (before, after) = updatedPlan.span { ps =>
194246
val classes = ps.map(_.getClass)
195247
val runsAfterSat = runsAfter.isEmpty
196-
runsAfter --= classes
248+
runsAfter = runsAfter -- classes
197249
// Prefer the point immediately before the first beforePhases.
198250
// If beforePhases not specified, insert at the point immediately
199251
// after the last afterPhases.
@@ -209,7 +261,7 @@ object Plugins {
209261
throw new Exception(s"Ordering conflict for phase ${phase.phaseName}")
210262
}
211263

212-
existingPhases += phase.getClass
264+
insertedPhase = insertedPhase + phase.getClass
213265
updatedPlan = before ++ (List(phase) :: after)
214266
}
215267

compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ class PluginsTest {
163163

164164
// M1 inserted to plan first
165165
val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil)
166-
assert(updatedPlan1(6)(0) eq M1)
167-
assert(updatedPlan1(7)(0) eq M2)
166+
assert(updatedPlan1(4)(0) eq M1)
167+
assert(updatedPlan1(5)(0) eq M2)
168168

169169
// M2 inserted to plan first
170170
val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil)

0 commit comments

Comments
 (0)