Skip to content

Commit 4776258

Browse files
committed
optimizes Scala reflection GIL
First of all, GIL should only apply to runtime reflection, because noone is going to run toolboxes in multiple threads: a) that's impossible, b/c the compiler isn't thread safe, b) ToolBox api prevents that. Secondly, the only things in symbols which require synchronization are: 1) info/validTo (completers aren't thread-safe), 2) rawInfo and its dependencies (it shares a mutable field with info) 3) non-trivial caches like in typeAsMemberOfLock If you think about it, other things like sourceModule or associatedFile don't need synchronization, because they are either set up when a symbol is created or cloned or when it's completed. We can say that symbols can be in four possible states: 1) being created, 2) created, but not yet initialized, 3) initializing, 4) initialized. single thread. #2 and #4 don't need synchronization either, because the only mutation symbols in runtime reflection can undergo is init. #3 is dangerous and needs protection.
1 parent ba3fc21 commit 4776258

File tree

4 files changed

+38
-61
lines changed

4 files changed

+38
-61
lines changed

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,13 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
889889
final def isInitialized: Boolean =
890890
validTo != NoPeriod
891891

892+
/** Some completers call sym.setInfo when still in-flight and then proceed with initialization (e.g. see LazyPackageType)
893+
* setInfo sets _validTo to current period, which means that after a call to setInfo isInitialized will start returning true.
894+
* Unfortunately, this doesn't mean that info becomes ready to be used, because subsequent initialization might change the info.
895+
* Therefore we need this method to distinguish between initialized and really initialized symbol states.
896+
*/
897+
final def isFullyInitialized: Boolean = _validTo != NoPeriod && (flags & LOCKED) == 0
898+
892899
/** Can this symbol be loaded by a reflective mirror?
893900
*
894901
* Scalac relies on `ScalaSignature' annotation to retain symbols across compilation runs.

src/reflect/scala/reflect/runtime/Gil.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ private[reflect] trait Gil {
1212
lazy val gil = new java.util.concurrent.locks.ReentrantLock
1313

1414
@inline final def gilSynchronized[T](body: => T): T = {
15-
try {
16-
gil.lock()
17-
body
18-
} finally {
19-
gil.unlock()
15+
if (isCompilerUniverse) body
16+
else {
17+
try {
18+
gil.lock()
19+
body
20+
} finally {
21+
gil.unlock()
22+
}
2023
}
2124
}
2225
}

src/reflect/scala/reflect/runtime/SynchronizedOps.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private[reflect] trait SynchronizedOps extends internal.SymbolTable
4949
// we can keep this lock fine-grained, because methods of Scope don't do anything extraordinary, which makes deadlocks impossible
5050
// fancy subclasses of internal.Scopes#Scope should do synchronization themselves (e.g. see PackageScope for an example)
5151
private lazy val syncLock = new Object
52-
def syncLockSynchronized[T](body: => T): T = syncLock.synchronized { body }
52+
def syncLockSynchronized[T](body: => T): T = if (isCompilerUniverse) body else syncLock.synchronized { body }
5353
override def isEmpty: Boolean = syncLockSynchronized { super.isEmpty }
5454
override def size: Int = syncLockSynchronized { super.size }
5555
override def enter[T <: Symbol](sym: T): T = syncLockSynchronized { super.enter(sym) }

src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala

Lines changed: 22 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,16 @@ private[reflect] trait SynchronizedSymbols extends internal.Symbols { self: Symb
2828

2929
trait SynchronizedSymbol extends Symbol {
3030

31-
override def rawflags = gilSynchronized { super.rawflags }
32-
override def rawflags_=(x: Long) = gilSynchronized { super.rawflags_=(x) }
33-
34-
override def rawowner = gilSynchronized { super.rawowner }
35-
override def owner_=(owner: Symbol) = gilSynchronized { super.owner_=(owner) }
36-
37-
override def validTo = gilSynchronized { super.validTo }
38-
override def validTo_=(x: Period) = gilSynchronized { super.validTo_=(x) }
39-
40-
override def pos = gilSynchronized { super.pos }
41-
override def setPos(pos: Position): this.type = { gilSynchronized { super.setPos(pos) }; this }
42-
43-
override def privateWithin = gilSynchronized { super.privateWithin }
44-
override def privateWithin_=(sym: Symbol) = gilSynchronized { super.privateWithin_=(sym) }
31+
def gilSynchronizedIfNotInited[T](body: => T): T = {
32+
if (isFullyInitialized) body
33+
else gilSynchronized { body }
34+
}
4535

46-
override def info = gilSynchronized { super.info }
47-
override def info_=(info: Type) = gilSynchronized { super.info_=(info) }
48-
override def updateInfo(info: Type): Symbol = gilSynchronized { super.updateInfo(info) }
49-
override def rawInfo: Type = gilSynchronized { super.rawInfo }
36+
override def validTo = gilSynchronizedIfNotInited { super.validTo }
37+
override def info = gilSynchronizedIfNotInited { super.info }
38+
override def rawInfo: Type = gilSynchronizedIfNotInited { super.rawInfo }
5039

51-
override def typeParams: List[Symbol] = gilSynchronized {
40+
override def typeParams: List[Symbol] = gilSynchronizedIfNotInited {
5241
if (isCompilerUniverse) super.typeParams
5342
else {
5443
if (isMonomorphicType) Nil
@@ -64,22 +53,14 @@ private[reflect] trait SynchronizedSymbols extends internal.Symbols { self: Symb
6453
}
6554
}
6655
}
67-
override def unsafeTypeParams: List[Symbol] = gilSynchronized {
56+
override def unsafeTypeParams: List[Symbol] = gilSynchronizedIfNotInited {
6857
if (isCompilerUniverse) super.unsafeTypeParams
6958
else {
7059
if (isMonomorphicType) Nil
7160
else rawInfo.typeParams
7261
}
7362
}
7463

75-
override def reset(completer: Type): this.type = gilSynchronized { super.reset(completer) }
76-
77-
override def infosString: String = gilSynchronized { super.infosString }
78-
79-
override def annotations: List[AnnotationInfo] = gilSynchronized { super.annotations }
80-
override def setAnnotations(annots: List[AnnotationInfo]): this.type = { gilSynchronized { super.setAnnotations(annots) }; this }
81-
82-
8364
// ------ creators -------------------------------------------------------------------
8465

8566
override protected def createAbstractTypeSymbol(name: TypeName, pos: Position, newFlags: Long): AbstractTypeSymbol =
@@ -130,40 +111,26 @@ private[reflect] trait SynchronizedSymbols extends internal.Symbols { self: Symb
130111

131112
// ------- subclasses ---------------------------------------------------------------------
132113

133-
trait SynchronizedTermSymbol extends TermSymbol with SynchronizedSymbol {
134-
override def name_=(x: Name) = gilSynchronized { super.name_=(x) }
135-
override def rawname = gilSynchronized { super.rawname }
136-
override def referenced: Symbol = gilSynchronized { super.referenced }
137-
override def referenced_=(x: Symbol) = gilSynchronized { super.referenced_=(x) }
138-
}
114+
trait SynchronizedTermSymbol extends SynchronizedSymbol
139115

140116
trait SynchronizedMethodSymbol extends MethodSymbol with SynchronizedTermSymbol {
141-
override def typeAsMemberOf(pre: Type): Type = gilSynchronized { super.typeAsMemberOf(pre) }
142-
override def paramss: List[List[Symbol]] = gilSynchronized { super.paramss }
143-
override def returnType: Type = gilSynchronized { super.returnType }
117+
// we can keep this lock fine-grained, because it's just a cache over asSeenFrom, which makes deadlocks impossible
118+
// unfortunately we cannot elide this lock, because the cache depends on `pre`
119+
private lazy val typeAsMemberOfLock = new Object
120+
override def typeAsMemberOf(pre: Type): Type = gilSynchronizedIfNotInited { typeAsMemberOfLock.synchronized { super.typeAsMemberOf(pre) } }
144121
}
145122

123+
trait SynchronizedModuleSymbol extends ModuleSymbol with SynchronizedTermSymbol
124+
146125
trait SynchronizedTypeSymbol extends TypeSymbol with SynchronizedSymbol {
147-
override def name_=(x: Name) = gilSynchronized { super.name_=(x) }
148-
override def rawname = gilSynchronized { super.rawname }
149-
override def typeConstructor: Type = gilSynchronized { super.typeConstructor }
150-
override def tpe: Type = gilSynchronized { super.tpe }
126+
// unlike with typeConstructor, a lock is necessary here, because tpe calculation relies on
127+
// temporarily assigning NoType to tpeCache to detect cyclic reference errors
128+
private lazy val tpeLock = new Object
129+
override def tpe: Type = gilSynchronizedIfNotInited { tpeLock.synchronized { super.tpe } }
151130
}
152131

153-
trait SynchronizedClassSymbol extends ClassSymbol with SynchronizedTypeSymbol {
154-
override def associatedFile = gilSynchronized { super.associatedFile }
155-
override def associatedFile_=(f: AbstractFile) = gilSynchronized { super.associatedFile_=(f) }
156-
override def thisSym: Symbol = gilSynchronized { super.thisSym }
157-
override def thisType: Type = gilSynchronized { super.thisType }
158-
override def typeOfThis: Type = gilSynchronized { super.typeOfThis }
159-
override def typeOfThis_=(tp: Type) = gilSynchronized { super.typeOfThis_=(tp) }
160-
override def children = gilSynchronized { super.children }
161-
override def addChild(sym: Symbol) = gilSynchronized { super.addChild(sym) }
162-
}
132+
trait SynchronizedClassSymbol extends ClassSymbol with SynchronizedTypeSymbol
163133

164-
trait SynchronizedModuleClassSymbol extends ModuleClassSymbol with SynchronizedClassSymbol {
165-
override def sourceModule = gilSynchronized { super.sourceModule }
166-
override def implicitMembers: Scope = gilSynchronized { super.implicitMembers }
167-
}
134+
trait SynchronizedModuleClassSymbol extends ModuleClassSymbol with SynchronizedClassSymbol
168135
}
169136

0 commit comments

Comments
 (0)