@@ -45,11 +45,6 @@ import Infos.{NamespacedMethodName, ReachabilityInfo, ReachabilityInfoInClass}
45
45
final class Analyzer (config : CommonPhaseConfig , initial : Boolean ,
46
46
checkIR : Boolean , failOnError : Boolean , irLoader : IRLoader ) {
47
47
48
- import Analyzer ._
49
-
50
- private val allowAddingSyntheticMethods = initial
51
- private val checkAbstractReachability = initial
52
-
53
48
private val infoLoader : InfoLoader = {
54
49
new InfoLoader (irLoader,
55
50
if (! checkIR) InfoLoader .NoIRCheck
@@ -58,51 +53,87 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
58
53
)
59
54
}
60
55
61
- private val isNoModule = config.coreSpec.moduleKind == ModuleKind .NoModule
56
+ def computeReachability (moduleInitializers : Seq [ModuleInitializer ],
57
+ symbolRequirements : SymbolRequirement , logger : Logger )(implicit ec : ExecutionContext ): Future [Analysis ] = {
62
58
63
- private var objectClassInfo : ClassInfo = _
64
- private [this ] var classLoader : ClassLoader = _
59
+ infoLoader.update(logger)
65
60
66
- private [this ] val _errors = new GrowingList [Error ]
61
+ val run = new AnalyzerRun (config, initial, infoLoader)(
62
+ adjustExecutionContextForParallelism(ec, config.parallel))
67
63
68
- private var workTracker : WorkTracker = _
64
+ run
65
+ .computeReachability(moduleInitializers, symbolRequirements)
66
+ .map { _ =>
67
+ if (failOnError && run.errors.nonEmpty)
68
+ reportErrors(run.errors, logger)
69
69
70
- private val fromAnalyzer = FromCore (" analyzer" )
70
+ run
71
+ }
72
+ .andThen { case _ => infoLoader.cleanAfterRun() }
73
+ }
71
74
72
- private [this ] val _topLevelExportInfos : mutable.Map [(ModuleID , String ), TopLevelExportInfo ] = emptyThreadSafeMap
75
+ private def reportErrors (errors : List [Error ], logger : Logger ): Unit = {
76
+ require(errors.nonEmpty)
73
77
74
- def computeReachability (moduleInitializers : Seq [ModuleInitializer ],
75
- symbolRequirements : SymbolRequirement , logger : Logger )(implicit ec : ExecutionContext ): Future [Analysis ] = {
78
+ val maxDisplayErrors = {
79
+ val propName = " org.scalajs.linker.maxlinkingerrors"
80
+ Try (System .getProperty(propName, " 20" ).toInt).getOrElse(20 ).max(1 )
81
+ }
76
82
77
- computeInternal(moduleInitializers, symbolRequirements, logger)(
78
- adjustExecutionContextForParallelism(ec, config.parallel))
83
+ errors
84
+ .take(maxDisplayErrors)
85
+ .foreach(logError(_, logger, Level .Error ))
86
+
87
+ val skipped = errors.size - maxDisplayErrors
88
+ if (skipped > 0 )
89
+ logger.log(Level .Error , s " Not showing $skipped more linking errors " )
90
+
91
+ if (initial) {
92
+ throw new LinkingException (" There were linking errors" )
93
+ } else {
94
+ throw new AssertionError (
95
+ " There were linking errors after the optimizer has run. " +
96
+ " This is a bug, please report it. " +
97
+ " You can work around the bug by disabling the optimizer. " +
98
+ " In the sbt plugin, this can be done with " +
99
+ " `scalaJSLinkerConfig ~= { _.withOptimizer(false) }`." )
100
+ }
79
101
}
102
+ }
80
103
81
- /** Internal helper to isolate the execution context. */
82
- private def computeInternal ( moduleInitializers : Seq [ ModuleInitializer ],
83
- symbolRequirements : SymbolRequirement , logger : Logger )( implicit ec : ExecutionContext ) : Future [ Analysis ] = {
104
+ private class AnalyzerRun ( config : CommonPhaseConfig , initial : Boolean ,
105
+ infoLoader : InfoLoader )( implicit ec : ExecutionContext ) extends Analysis {
106
+ import AnalyzerRun . _
84
107
85
- resetState()
108
+ private val allowAddingSyntheticMethods = initial
109
+ private val checkAbstractReachability = initial
86
110
87
- infoLoader.update(logger)
111
+ private val isNoModule = config.coreSpec.moduleKind == ModuleKind .NoModule
112
+
113
+ private val workTracker : WorkTracker = new WorkTracker
114
+ private [this ] val classLoader : ClassLoader = new ClassLoader
115
+
116
+ private var objectClassInfo : ClassInfo = _
117
+ private var _classInfos : scala.collection.Map [ClassName , ClassInfo ] = _
118
+
119
+ def classInfos : scala.collection.Map [ClassName , Analysis .ClassInfo ] = _classInfos
88
120
89
- workTracker = new WorkTracker
90
- classLoader = new ClassLoader
121
+ private [this ] val _errors = new GrowingList [Error ]
122
+
123
+ override def errors : List [Error ] = _errors.get()
124
+
125
+ private val fromAnalyzer = FromCore (" analyzer" )
91
126
127
+ private [this ] val _topLevelExportInfos : mutable.Map [(ModuleID , String ), TopLevelExportInfo ] = emptyThreadSafeMap
128
+ def topLevelExportInfos : scala.collection.Map [(ModuleID , String ), Analysis .TopLevelExportInfo ] = _topLevelExportInfos
129
+
130
+ def computeReachability (moduleInitializers : Seq [ModuleInitializer ],
131
+ symbolRequirements : SymbolRequirement ): Future [Unit ] = {
92
132
loadObjectClass(() => loadEverything(moduleInitializers, symbolRequirements))
93
133
94
134
workTracker
95
- .future
96
- .map(_ => postLoad(moduleInitializers, logger))
97
- .andThen { case _ => infoLoader.cleanAfterRun() }
98
- }
99
-
100
- private def resetState (): Unit = {
101
- objectClassInfo = null
102
- workTracker = null
103
- _errors.clear()
104
- classLoader = null
105
- _topLevelExportInfos.clear()
135
+ .allowComplete()
136
+ .map(_ => postLoad(moduleInitializers))
106
137
}
107
138
108
139
private def loadObjectClass (onSuccess : () => Unit ): Unit = {
@@ -154,8 +185,9 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
154
185
reachInitializers(moduleInitializers)
155
186
}
156
187
157
- private def postLoad (moduleInitializers : Seq [ModuleInitializer ],
158
- logger : Logger ): Analysis = {
188
+ private def postLoad (moduleInitializers : Seq [ModuleInitializer ]): Unit = {
189
+ _classInfos = classLoader.loadedInfos()
190
+
159
191
if (isNoModule) {
160
192
// Check there is only a single module.
161
193
val publicModuleIDs = (
@@ -167,51 +199,8 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
167
199
_errors ::= MultiplePublicModulesWithoutModuleSupport (publicModuleIDs)
168
200
}
169
201
170
- val infos = classLoader.loadedInfos()
171
-
172
202
// Reach additional data, based on reflection methods used
173
- reachDataThroughReflection(infos)
174
-
175
- val errs = _errors.get()
176
-
177
- if (failOnError && errs.nonEmpty)
178
- reportErrors(logger)
179
-
180
- new Analysis {
181
- val classInfos = infos
182
- val topLevelExportInfos = _topLevelExportInfos
183
- val errors = errs
184
- }
185
- }
186
-
187
- private def reportErrors (logger : Logger ): Unit = {
188
- val errors = _errors.get()
189
-
190
- require(errors.nonEmpty)
191
-
192
- val maxDisplayErrors = {
193
- val propName = " org.scalajs.linker.maxlinkingerrors"
194
- Try (System .getProperty(propName, " 20" ).toInt).getOrElse(20 ).max(1 )
195
- }
196
-
197
- errors
198
- .take(maxDisplayErrors)
199
- .foreach(logError(_, logger, Level .Error ))
200
-
201
- val skipped = errors.size - maxDisplayErrors
202
- if (skipped > 0 )
203
- logger.log(Level .Error , s " Not showing $skipped more linking errors " )
204
-
205
- if (initial) {
206
- throw new LinkingException (" There were linking errors" )
207
- } else {
208
- throw new AssertionError (
209
- " There were linking errors after the optimizer has run. " +
210
- " This is a bug, please report it. " +
211
- " You can work around the bug by disabling the optimizer. " +
212
- " In the sbt plugin, this can be done with " +
213
- " `scalaJSLinkerConfig ~= { _.withOptimizer(false) }`." )
214
- }
203
+ reachDataThroughReflection()
215
204
}
216
205
217
206
private def reachSymbolRequirement (requirement : SymbolRequirement ): Unit = {
@@ -304,10 +293,9 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
304
293
}
305
294
306
295
/** Reach additional class data based on reflection methods being used. */
307
- private def reachDataThroughReflection (
308
- classInfos : scala.collection.Map [ClassName , ClassInfo ]): Unit = {
296
+ private def reachDataThroughReflection (): Unit = {
309
297
310
- val classClassInfo = classInfos .get(ClassClass )
298
+ val classClassInfo = _classInfos .get(ClassClass )
311
299
312
300
/* If Class.getSuperclass() is reachable, we can reach the data of all
313
301
* superclasses of classes whose data we can already reach.
@@ -320,7 +308,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
320
308
// calledFrom should always be nonEmpty if isReachable, but let's be robust
321
309
implicit val from =
322
310
getSuperclassMethodInfo.calledFrom.headOption.getOrElse(fromAnalyzer)
323
- for (classInfo <- classInfos .values.filter(_.isDataAccessed).toList) {
311
+ for (classInfo <- _classInfos .values.filter(_.isDataAccessed).toList) {
324
312
@ tailrec
325
313
def loop (classInfo : ClassInfo ): Unit = {
326
314
classInfo.accessData()
@@ -339,14 +327,16 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
339
327
340
328
private def lookupClass (className : ClassName )(
341
329
onSuccess : ClassInfo => Unit )(implicit from : From ): Unit = {
342
- workTracker.track(classLoader.lookupClass(className)) {
343
- case info : ClassInfo =>
344
- info.link()
345
- onSuccess(info)
346
-
347
- case CycleInfo (cycle, root) =>
348
- assert(root == null , s " unresolved root: $root" )
349
- _errors ::= CycleInInheritanceChain (cycle, fromAnalyzer)
330
+ workTracker.track {
331
+ classLoader.lookupClass(className).map {
332
+ case info : ClassInfo =>
333
+ info.link()
334
+ onSuccess(info)
335
+
336
+ case CycleInfo (cycle, root) =>
337
+ assert(root == null , s " unresolved root: $root" )
338
+ _errors ::= CycleInInheritanceChain (cycle, fromAnalyzer)
339
+ }
350
340
}
351
341
}
352
342
@@ -831,19 +821,18 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
831
821
})
832
822
}
833
823
834
- def tryLookupReflProxyMethod (proxyName : MethodName )(
835
- onSuccess : MethodInfo => Unit )(implicit from : From ): Unit = {
824
+ private def maybeReachReflProxyMethod (proxyName : MethodName )(implicit from : From ): Unit = {
836
825
if (! allowAddingSyntheticMethods) {
837
- tryLookupMethod(proxyName).foreach(onSuccess )
826
+ tryLookupMethod(proxyName).foreach(_.reach( this ) )
838
827
} else {
839
- publicMethodInfos.get(proxyName).fold {
840
- findReflectiveTarget (proxyName)(onSuccess )
841
- } (onSuccess )
828
+ publicMethodInfos
829
+ .get (proxyName)
830
+ .fold(findAndReachReflectiveTarget(proxyName))(_.reach( this ) )
842
831
}
843
832
}
844
833
845
- private def findReflectiveTarget ( proxyName : MethodName ) (
846
- onSuccess : MethodInfo => Unit )(implicit from : From ): Unit = {
834
+ private def findAndReachReflectiveTarget (
835
+ proxyName : MethodName )(implicit from : From ): Unit = {
847
836
/* The lookup for a target method in this code implements the
848
837
* algorithm defining `java.lang.Class.getMethod`. This mimics how
849
838
* reflective calls are implemented on the JVM, at link time.
@@ -881,15 +870,16 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
881
870
882
871
case onlyCandidate :: Nil =>
883
872
// Fast path that does not require workTracker.track
884
- val proxy = createReflProxy(proxyName, onlyCandidate.methodName)
885
- onSuccess(proxy)
873
+ createReflProxy(proxyName, onlyCandidate.methodName).reach(this )
886
874
887
875
case _ =>
888
- val targetFuture = computeMostSpecificProxyMatch(candidates)
889
- workTracker.track(targetFuture) { reflectiveTarget =>
890
- val proxy = createReflProxy(proxyName, reflectiveTarget.methodName)
891
- onSuccess(proxy )
876
+ val future = for {
877
+ reflectiveTarget <- computeMostSpecificProxyMatch(candidates)
878
+ } yield {
879
+ createReflProxy(proxyName, reflectiveTarget.methodName).reach( this )
892
880
}
881
+
882
+ workTracker.track(future)
893
883
}
894
884
}
895
885
@@ -933,8 +923,6 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
933
923
private def computeMostSpecificProxyMatch (candidates : List [MethodInfo ])(
934
924
implicit from : From ): Future [MethodInfo ] = {
935
925
936
- implicit val ec = workTracker.ec
937
-
938
926
/* From the JavaDoc of java.lang.Class.getMethod:
939
927
*
940
928
* If more than one [candidate] method is found in C, and one of these
@@ -1215,7 +1203,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
1215
1203
private def callMethodResolved (methodName : MethodName )(
1216
1204
implicit from : From ): Unit = {
1217
1205
if (methodName.isReflectiveProxy) {
1218
- tryLookupReflProxyMethod (methodName)(_.reach( this ) )
1206
+ maybeReachReflProxyMethod (methodName)
1219
1207
} else {
1220
1208
lookupMethod(methodName).reach(this )
1221
1209
}
@@ -1578,46 +1566,45 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
1578
1566
1579
1567
}
1580
1568
1581
- object Analyzer {
1569
+ private object AnalyzerRun {
1582
1570
private val getSuperclassMethodName =
1583
1571
MethodName (" getSuperclass" , Nil , ClassRef (ClassClass ))
1584
1572
1585
- private class WorkTracker (implicit val ec : ExecutionContext ) {
1573
+ private class WorkTracker (implicit ec : ExecutionContext ) {
1586
1574
private val pending = new AtomicInteger (0 )
1575
+ @ volatile private var _allowComplete = false
1587
1576
private val promise = Promise [Unit ]()
1588
1577
1589
- def track [T ](fut : Future [T ])(onSuccess : T => Unit ): Unit = {
1590
- val got = pending.incrementAndGet()
1591
- assert(got > 0 )
1578
+ def track (fut : Future [Unit ]): Unit = {
1579
+ pending.incrementAndGet()
1580
+
1581
+ fut.onComplete {
1582
+ case Success (_) =>
1583
+ if (pending.decrementAndGet() == 0 )
1584
+ tryComplete()
1592
1585
1593
- fut.map(onSuccess).onComplete {
1594
- case Success (_) => taskDone()
1595
- case Failure (t) => promise.tryFailure(t)
1586
+ case Failure (t) =>
1587
+ promise.tryFailure(t)
1596
1588
}
1597
1589
}
1598
1590
1599
- private def taskDone (): Unit = {
1600
- if (pending.decrementAndGet() == 0 ) {
1601
- /* TODO: The completion condition in the WorkTracker is not what we want:
1602
- *
1603
- * What we have is: The number of pending tasks drops to 0.
1604
- *
1605
- * What we want is: The number of pending tasks drops to 0 after a
1606
- * certain point in the main execution flow has been reached.
1607
- *
1608
- * This is currently not a problem, because `loadObjectClass` submits the
1609
- * initial task and then everything else is done inside a task
1610
- * (until `postLoad`).
1611
- *
1612
- * However, this is not strictly necessary, we could, for example, start
1613
- * loading infos for other entrypoints in parallel. So we should fix this.
1614
- */
1615
- pending.set(- 1 )
1591
+ private def tryComplete (): Unit = {
1592
+ /* Note that after _allowComplete is true and pending == 0, we are sure
1593
+ * that no new task will be submitted concurrently:
1594
+ * - _allowComplete guarantees us that no external task will be added anymore
1595
+ * - pending == 0 guarantees us that no internal task (which might create
1596
+ * more tasks) are running anymore.
1597
+ */
1598
+ if (_allowComplete && pending.get() == 0 ) {
1616
1599
promise.trySuccess(())
1617
1600
}
1618
1601
}
1619
1602
1620
- def future : Future [Unit ] = promise.future
1603
+ def allowComplete (): Future [Unit ] = {
1604
+ _allowComplete = true
1605
+ tryComplete()
1606
+ promise.future
1607
+ }
1621
1608
}
1622
1609
1623
1610
private final class GrowingList [A ] {
0 commit comments