Skip to content

Commit 2ff2322

Browse files
committed
detecting what are to become "really static" classes
This commit tracks "really static" module classes. A follow-up commit will add support for "statification" proper (a transformation performed on-the-fly by GenBCode for "really static" module classes and their usage sites). A "really static" module-class fulfills four conditions: (0) marked @reallyStatic (1) the module-class is a static module (2) the module-class is direct subclass of AnyRef (3) the module-class extends either no interfaces or just marker interfaces
1 parent fed4a72 commit 2ff2322

File tree

3 files changed

+214
-2
lines changed

3 files changed

+214
-2
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala

+143-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,46 @@ abstract class BCodeTypes extends SubComponent with BytecodeWriters {
2525

2626
val isLateClosuresOn = (settings.isClosureConvDelegating || settings.isClosureConvMH)
2727

28+
def isCustomValueClass(csym: Symbol): Boolean = enteringErasure(csym.isDerivedValueClass)
29+
30+
/*
31+
* The set `knownModClassStatification` contains
32+
* those module-classes of static-modules as well as those module-classes of custom-value-classes
33+
* that fulfill the conditions for `statification` checked by `isStatifiableModuleClass()`
34+
*
35+
* The module-class of a custom-value-class may have a side-effecting class-initializer, as the following examples show.
36+
*
37+
* Example 1:
38+
* class A { println("A") }
39+
* object B extends A { }
40+
* class B(val underlying: Int) extends AnyVal { }
41+
*
42+
* Example 2:
43+
* object C {
44+
* val singleton1 = ...
45+
* }
46+
* class C(val underlying: Int) extends AnyVal { }
47+
*
48+
*/
49+
val knownModClassStatification = mutable.Set.empty[BType]
50+
51+
/*
52+
* "Statification" is a GenBCode-level transform that turns into static the members of those module classes
53+
* that fulfill the conditions checked in `isStatifiableModuleClass()` EXCEPT those members overriding methods in the Object API.
54+
* Usage sites are also adapted.
55+
*/
56+
def shouldStatifyClass(bt: BType): Boolean = { knownModClassStatification(bt) }
57+
58+
def shouldStatifyClass(csym: Symbol): Boolean = {
59+
shouldStatifyClass(exemplar(csym).c)
60+
}
61+
62+
def shouldStatifyMethod(msym: Symbol, owner: BType, methodName: String): Boolean = {
63+
(methodName != nme.CONSTRUCTOR.toString) &&
64+
shouldStatifyClass(owner) &&
65+
(msym.overriddenSymbol(definitions.ObjectClass) == NoSymbol)
66+
}
67+
2868
object BT {
2969

3070
import global.chrs
@@ -963,6 +1003,7 @@ abstract class BCodeTypes extends SubComponent with BytecodeWriters {
9631003
def clearBCodeTypes() {
9641004
symExemplars.clear()
9651005
exemplars.clear()
1006+
knownModClassStatification.clear()
9661007
clearBCodeOpt()
9671008
}
9681009

@@ -1291,15 +1332,20 @@ abstract class BCodeTypes extends SubComponent with BytecodeWriters {
12911332

12921333
/*
12931334
* must-single-thread
1335+
*
1336+
* @param csym denotes either a "plain class" or a "module class", see `exemplar()`
1337+
* @param key the BType for csym's javaBinaryName
12941338
*/
12951339
private def buildExemplar(key: BType, csym: Symbol): Tracked = {
1340+
val isImplClass = csym.isImplClass
12961341
val sc =
1297-
if(csym.isImplClass) definitions.ObjectClass
1342+
if(isImplClass) definitions.ObjectClass
12981343
else csym.superClass
1344+
val isInterface = csym.isInterface
12991345
assert(
13001346
if(csym == definitions.ObjectClass)
13011347
sc == NoSymbol
1302-
else if(csym.isInterface)
1348+
else if(isInterface)
13031349
sc == definitions.ObjectClass
13041350
else
13051351
((sc != NoSymbol) && !sc.isInterface) || isCompilingStdLib,
@@ -1323,9 +1369,104 @@ abstract class BCodeTypes extends SubComponent with BytecodeWriters {
13231369

13241370
val innersChain = saveInnerClassesFor(csym, key)
13251371

1372+
if(!isImplClass && !isInterface) {
1373+
// collect info needed later about custom value classes
1374+
if(csym.isModuleClass) {
1375+
trackModuleClass(csym, key)
1376+
} else if(isCustomValueClass(csym)) {
1377+
val mcBT = brefType(key.getInternalName + "$")
1378+
val modCSym = enteringErasure{ csym.linkedClassOfClass }
1379+
trackCustomValueClass(csym, modCSym, mcBT)
1380+
// notice we're not adding exemplar for the module-class --- someone else will do that
1381+
}
1382+
}
1383+
13261384
Tracked(key, flags, tsc, ifacesArr, innersChain)
13271385
}
13281386

1387+
/*
1388+
* Track the module class of a custom value class that fulfills the criteria for "statification".
1389+
*/
1390+
private def trackCustomValueClass(plainClass: Symbol, modClass: Symbol, modClassBT: BType) {
1391+
1392+
assert(modClass != NoSymbol)
1393+
1394+
if(knownModClassStatification(modClassBT)) { return }
1395+
1396+
val isAnnotated = (modClass hasAnnotation definitions.ReallyStaticClass) || (
1397+
(plainClass != NoSymbol) && (plainClass hasAnnotation definitions.ReallyStaticClass)
1398+
)
1399+
1400+
if(!isAnnotated || !isStatifiableModuleClass(modClass)) { return }
1401+
knownModClassStatification += modClassBT
1402+
1403+
// // collect syms of extension methods
1404+
// val extMSyms = enteringErasure {
1405+
// plainClass.info.members.collect{ case m if m.isMethodWithExtension => extensionMethods.extensionMethod(m) }
1406+
// }
1407+
1408+
}
1409+
1410+
/*
1411+
* Track the module-classes of static-modules that fulfill the criteria for "statification".
1412+
*/
1413+
private def trackModuleClass(modClass: Symbol, modClassBT: BType) {
1414+
if(knownModClassStatification(modClassBT)) { return }
1415+
val isAnnotated = (modClass hasAnnotation definitions.ReallyStaticClass)
1416+
if(!isAnnotated || !isStatifiableModuleClass(modClass)) { return }
1417+
knownModClassStatification += modClassBT
1418+
}
1419+
1420+
/*
1421+
* @return true iff all of the conditions below hold:
1422+
* (1) modClass is a static module
1423+
* (2) modClass is direct subclass of AnyRef
1424+
* (3) modClass doesn't extend any non-marker interfaces (thus, the members of the mod-class can be made static).
1425+
*
1426+
* Please notice the invoker has to test for the presence of @reallyStatic.
1427+
*
1428+
* must-single-thread
1429+
*
1430+
*/
1431+
private def isStatifiableModuleClass(modClass: Symbol): Boolean = {
1432+
val msg = impedimentsToStatifiabilityOfModuleClass(modClass)
1433+
if(msg == null) true
1434+
else { log(msg); false }
1435+
}
1436+
1437+
/*
1438+
* @return null iff the modClass is statifiable (please notice no check for @reallyStatic is performed here).
1439+
* Otherwise returns a String listing restrictions not fulfilled by modClass and that prevent statifiability.
1440+
*/
1441+
def impedimentsToStatifiabilityOfModuleClass(modClass: Symbol): String = {
1442+
assert(modClass != NoSymbol)
1443+
1444+
var result: List[String] = Nil
1445+
1446+
if(modClass.isJavaDefined) { result ::= "is a Java-defined class" }
1447+
if(!isStaticModule(modClass)) { result ::= "isn't a static module (ie has an outer-instance" }
1448+
1449+
val scSym = modClass.superClass
1450+
if(scSym != definitions.ObjectClass && scSym != NoSymbol) {
1451+
result ::= s"has a superClass other than AnyRef: ${scSym.fullName}"
1452+
}
1453+
1454+
def isMarkerInterface(isym: Symbol): Boolean = {
1455+
isym.info.declarations.isEmpty &&
1456+
(isym.mixinClasses forall isMarkerInterface)
1457+
}
1458+
1459+
val nonMarkerIfaces = modClass.mixinClasses filter { iface => !isMarkerInterface(iface) }
1460+
if(!nonMarkerIfaces.isEmpty) {
1461+
result ::= s"extends non-marker interfaces ${prettyPrintFullnames(nonMarkerIfaces)}"
1462+
}
1463+
1464+
if(result.isEmpty) null
1465+
else {
1466+
s"Won't statify the static module class of ${modClass.fullName} because it " + result.mkString(". Moreover, it ")
1467+
}
1468+
}
1469+
13291470
// ---------------- utilities around interfaces represented by Tracked instances. ----------------
13301471

13311472
/* Drop redundant interfaces (those which are implemented by some other).

src/library/scala/reallyStatic.scala

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* __ *\
2+
** ________ ___ / / ___ Scala API **
3+
** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL **
4+
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
5+
** /____/\___/_/ |_/____/_/ | | **
6+
** |/ **
7+
\* */
8+
9+
package scala
10+
11+
/**
12+
* An annotation that indicates the static-module or value-class thus annotated
13+
* will have only static members in the bytecode emitted for its module-class.
14+
*
15+
* The compiler accepts @reallyStatic occurrences provided the target module-class
16+
* fulfills all of the following:
17+
* (1) the module-class is a static module
18+
* (2) the module-class is direct subclass of AnyRef
19+
* (3) the module-class extends either no interfaces or just marker interfaces
20+
*
21+
* With or without this annotation, a module-class allows accessing via MODULE$
22+
* the singleton in question. However, @reallyStatic results in all callsites
23+
* targeting that singleton being `invokestatic` instructions, ie the singleton is not loaded
24+
* before any arguments. This may affect when the side-effects (if any) of the
25+
* (static) class-initializer of the module-class become visible. In the simplest case,
26+
* the class-initializer of a module-class just limits itself to assigning the singleton to MODULE$.
27+
* But it's also possible for the class-initializer to perform more work, e.g. whenever
28+
* the companion-object contains statements, or defines vals or vars.
29+
*
30+
* Finally, an object annotated with @reallyStatic that also defines @inline methods
31+
* gives the inliner permission to replace an `invokestatic` (targeting the method in question) with its body,
32+
* thus potentially skipping running the class-initializer for the associated module-class.
33+
*
34+
* The caveats above are listed for completeness. When used for its intended purpose, @reallyStatic is really useful!
35+
*
36+
* Before @reallyStatic, functionality defined on objects (and in the companion to value-classes) was accessed following the idiom:
37+
* load singleton-module
38+
* invokevirtual(arg1 ... argN)
39+
* After JIT-compilation, performance is great because there's a single method method implementation to dispatch.
40+
*
41+
* However that could have been conveyed more directly by emitting "really static" methods, correspondinlgy invoked via invokestatic.
42+
* This is what the @reallyStatic annotation allows,
43+
* for a static module (ie an object definitions that lacks outer instance) or for a value class.
44+
*
45+
* In case the compiler can't honor the request for really-static-ness, a descriptive error message is shown.
46+
* This can happen in two situations:
47+
*
48+
* (a) extending another class:
49+
*
50+
* {{{
51+
* @reallyStatic
52+
* case class Valued(val repr: Int) extends AnyVal {
53+
* def odd = (repr&1)==1
54+
* }
55+
*
56+
* error: Won't statify the static module class of Valued because it has a superClass other than AnyRef: scala.runtime.AbstractFunction1
57+
* case class Valued(val repr: Int) extends AnyVal {
58+
* ^
59+
* }}}
60+
*
61+
* (b) extending non-marker interfaces
62+
*
63+
* For example, a value-class that "extends AnyVal with Serializable" is ok,
64+
* but extending an interface that declares one or more members results in an error for an @reallyStatic-tagged class.
65+
*
66+
*
67+
* @author Miguel Garcia, http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
68+
* @since 2.11
69+
*/
70+
class reallyStatic extends scala.annotation.StaticAnnotation

src/reflect/scala/reflect/internal/Definitions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,7 @@ trait Definitions extends api.StandardDefinitions {
900900
lazy val UncheckedClass = requiredClass[scala.unchecked]
901901
lazy val UnspecializedClass = requiredClass[scala.annotation.unspecialized]
902902
lazy val VolatileAttr = requiredClass[scala.volatile]
903+
lazy val ReallyStaticClass = requiredClass[scala.reallyStatic]
903904

904905
// Meta-annotations
905906
lazy val BeanGetterTargetClass = requiredClass[meta.beanGetter]

0 commit comments

Comments
 (0)