Skip to content

Commit 36088c3

Browse files
Sidestep Zinc and use the compiler interface directly
Basic benchmarking shows that we're incurring a ~49% overhead from using Zinc instead of the Scala compiler directly. That is, we wrote a basic worker that invokes the Scala 2 compiler programmatically via `scala.tools.nsc.Main`, and found that it was ~49% faster across-the-board. This doesn't necessarily mean that we can speed up `ZincRunner` by 49%. A lot of that time is spent in compilation phases [added](https://github.com/scala/scala/blob/2.13.x/src/sbt-bridge/scala/tools/xsbt/CallbackGlobal.scala#L166) by the compiler bridge (implementation of the compiler interface) itself. There are three of these phases; they're responsible for: 1. Mapping generated `.class` files to sources 2. Analyzing depedendencies between code 3. Producing the analysis information outputted by Zinc We rely on 2 and 3 for: - Dependency checking - Detecting main classes - Test discovery We only use 2 and 3, and although it's likely the phases could be slimmed down to only extract the information we need, implementing them ourselves would add a lot of complexity to `ZincRunner` and make it less portable . There's also no guarantee that we could do a better job. What can be eliminated is all the overhead related to incremental compilation. We haven't used incremental compilation for years and have no intention of re-enabling it. This commit isn't about ripping out Zinc entirely; we still use Zinc for the following things: - Classloading the compiler interface from the compiler classpath - Implementations of various `xsbti.*` traits and interfaces
1 parent 321e0a9 commit 36088c3

File tree

10 files changed

+361
-447
lines changed

10 files changed

+361
-447
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package higherkindness.rules_scala.workers.zinc.compile
2+
3+
import java.io.File
4+
import java.nio.file.Path
5+
import java.util
6+
import java.util.Optional
7+
import sbt.internal.inc.SourceInfos
8+
import scala.collection.mutable.ArrayBuffer
9+
import xsbti.*
10+
import xsbti.api.{ClassLike, DependencyContext}
11+
import xsbti.compile.analysis.ReadSourceInfos
12+
13+
case class AnnexAnalysis(
14+
apis: ArrayBuffer[ClassLike] = ArrayBuffer.empty,
15+
mainClasses: ArrayBuffer[String] = ArrayBuffer.empty,
16+
usedJars: ArrayBuffer[Path] = ArrayBuffer.empty,
17+
) {
18+
def getCallback(converter: FileConverter): AnalysisCallback3 = new AnalysisCallback3 {
19+
override def api(sourceFile: File, classApi: ClassLike): Unit = apis += classApi
20+
override def api(sourceFile: VirtualFileRef, classApi: ClassLike): Unit = apis += classApi
21+
override def apiPhaseCompleted(): Unit = {}
22+
override def binaryDependency(
23+
onBinaryEntry: File,
24+
onBinaryClassName: String,
25+
fromClassName: String,
26+
fromSourceFile: File,
27+
context: DependencyContext,
28+
): Unit = usedJars += onBinaryEntry.toPath
29+
30+
override def binaryDependency(
31+
onBinaryEntry: Path,
32+
onBinaryClassName: String,
33+
fromClassName: String,
34+
fromSourceFile: VirtualFileRef,
35+
context: DependencyContext,
36+
): Unit = usedJars += onBinaryEntry
37+
38+
override def classDependency(onClassName: String, sourceClassName: String, context: DependencyContext): Unit = {}
39+
override def classesInOutputJar(): java.util.Set[String] = java.util.Collections.emptySet()
40+
override def dependencyPhaseCompleted(): Unit = {}
41+
override def enabled(): Boolean = true
42+
override def generatedLocalClass(source: File, classFile: File): Unit = {}
43+
override def generatedLocalClass(source: VirtualFileRef, classFile: Path): Unit = {}
44+
override def generatedNonLocalClass(
45+
source: File,
46+
classFile: File,
47+
binaryClassName: String,
48+
sourceClassName: String,
49+
): Unit = {}
50+
51+
override def generatedNonLocalClass(
52+
source: VirtualFileRef,
53+
classFile: Path,
54+
binaryClassName: String,
55+
sourceClassName: String,
56+
): Unit = {}
57+
58+
override def getPickleJarPair: Optional[T2[Path, Path]] = Optional.empty()
59+
60+
/**
61+
* I don't really understand what this is supposed to do. It doesn't seem to be used in the Scala 3 compiler; the
62+
* only place I could find it being used is the Scala 2 compiler bridge (the implementation of the compiler
63+
* interface for Scala 2):
64+
* [[https://github.com/sbt/zinc/blob/75d54b672adbbda1b528f5759235704de1ba333f/internal/compiler-bridge/src/main/scala/xsbt/CompilerBridge.scala#L196]]
65+
*
66+
* It seems that when there are compilation problems, the Scala 2 compiler bridge invokes this method, shoves the
67+
* result in a `xsbt.InterfaceCompileFailed2`, throws that exception. That doesn't seem very useful, since we
68+
* could've just called it ourselves.
69+
*/
70+
override def getSourceInfos: ReadSourceInfos = SourceInfos.empty
71+
override def isPickleJava: Boolean = false
72+
override def mainClass(sourceFile: File, className: String): Unit = mainClasses += className
73+
override def mainClass(sourceFile: VirtualFileRef, className: String): Unit = mainClasses += className
74+
override def problem(what: String, pos: Position, message: String, severity: Severity, reported: Boolean): Unit = {}
75+
override def problem2(
76+
what: String,
77+
position: Position,
78+
message: String,
79+
severity: Severity,
80+
reported: Boolean,
81+
rendered: Optional[String],
82+
diagnosticCode: Optional[DiagnosticCode],
83+
diagnosticRelatedInformation: util.List[DiagnosticRelatedInformation],
84+
actions: util.List[Action],
85+
): Unit = {}
86+
87+
override def startSource(source: File): Unit = {}
88+
override def startSource(source: VirtualFile): Unit = {}
89+
override def toVirtualFile(path: Path): VirtualFile = converter.toVirtualFile(path)
90+
override def usedName(className: String, name: String, useScopes: java.util.EnumSet[UseScope]): Unit = {}
91+
}
92+
}

src/main/scala/higherkindness/rules_scala/workers/zinc/compile/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ scala_binary(
1212
"//src/main/scala/higherkindness/rules_scala/common/interrupt",
1313
"//src/main/scala/higherkindness/rules_scala/common/worker",
1414
"//src/main/scala/higherkindness/rules_scala/workers/common",
15+
"//src/main/scala/sbt/internal/inc/classfile",
1516
"//third_party/bazel/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/jarhelper",
1617
],
1718
)

src/main/scala/higherkindness/rules_scala/workers/zinc/compile/Deps.scala

Lines changed: 0 additions & 66 deletions
This file was deleted.

src/main/scala/higherkindness/rules_scala/workers/zinc/compile/FilteredInfos.scala

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/main/scala/higherkindness/rules_scala/workers/zinc/compile/FilteredRelations.scala

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/main/scala/higherkindness/rules_scala/workers/zinc/compile/ZincPersistence.scala

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)