Skip to content

Commit 253ba27

Browse files
committed
statification, @reallyStatic module-classes and their usages-sites
"Statification" is a transformation performed on-the-fly by GenBCode for "really static" module classes and their usage sites. A @reallyStatic module-classes has all of its members (for example, extension methods of a custom value classes) emitted bytecode-level static, EXCEPT those members overriding methods in the Object API. The reason for this is that some clients may invoke those methods on the singleton (viewed as an object) and expect the overriding implementation to be dispatched. Additionally, @reallyStatic also results in invokestatic callsites for what used to be invokevirtual callsites with the singleton as receiver, ie the singleton is not loaded anymore before any arguments are loaded. There's an interplay with @inline methods in an @reallyStatic module-class: the inliner gets permission to have an `invokestatic` (targeting the method in question) replaced with its body, thus potentially skipping running the class-initializer for the associated module-class.
1 parent 2ff2322 commit 253ba27

File tree

2 files changed

+160
-24
lines changed

2 files changed

+160
-24
lines changed

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -3575,7 +3575,13 @@ abstract class BCodeTypes extends SubComponent with BytecodeWriters {
35753575

35763576
mirrorMethod.visitCode()
35773577

3578-
mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
3578+
val isTargetStatified = !m.isStaticMember && {
3579+
val modBT = brefType(moduleName)
3580+
shouldStatifyMethod(m, modBT, mirrorMethodName)
3581+
}
3582+
if(!isTargetStatified) {
3583+
mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
3584+
}
35793585

35803586
var index = 0
35813587
for(jparamType <- paramJavaTypes) {
@@ -3584,7 +3590,8 @@ abstract class BCodeTypes extends SubComponent with BytecodeWriters {
35843590
index += jparamType.getSize
35853591
}
35863592

3587-
mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).getDescriptor)
3593+
val opc = if(isTargetStatified) asm.Opcodes.INVOKESTATIC else asm.Opcodes.INVOKEVIRTUAL
3594+
mirrorMethod.visitMethodInsn(opc, moduleName, mirrorMethodName, asmMethodType(m).getDescriptor)
35883595
mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN))
35893596

35903597
mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments

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

+151-22
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import scala.tools.nsc.symtab._
1313
import scala.annotation.switch
1414

1515
import scala.tools.asm
16-
import asm.tree.{FieldNode, MethodInsnNode, MethodNode}
16+
import scala.tools.asm.tree.{FieldInsnNode, FieldNode, MethodInsnNode, MethodNode}
1717

1818
/*
1919
* Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk.
@@ -739,6 +739,8 @@ abstract class GenBCode extends BCodeOptInter {
739739
val closuresForDelegates = mutable.Map.empty[MethodSymbol, DClosureEndpoint]
740740

741741
private var claszSymbol: Symbol = null
742+
private var thisBT: BType = null
743+
private var statifyThisClass = false
742744
private var isCZParcelable = false
743745
private var isCZStaticModule = false
744746
private var isCZRemote = false
@@ -1011,6 +1013,31 @@ abstract class GenBCode extends BCodeOptInter {
10111013
cnode.isStaticModule = isCZStaticModule
10121014

10131015
initJClass(cnode)
1016+
thisBT = exemplar(claszSymbol).c
1017+
statifyThisClass = shouldStatifyClass(thisBT)
1018+
1019+
/*
1020+
* In case GenBCode can't honor a request for really-static-ness, a descriptive error is emitted.
1021+
*/
1022+
if(statifyThisClass) {
1023+
log(s"Will statify ${thisBT.getInternalName}")
1024+
} else {
1025+
if(claszSymbol hasAnnotation definitions.ReallyStaticClass) {
1026+
if(claszSymbol.isImplClass) { cunit.error(cd.pos, "The @reallyStatic annotation isn't applicable to implementation-classes") }
1027+
else if(claszSymbol.isInterface) { cunit.error(cd.pos, "The @reallyStatic annotation isn't applicable to interfaces") }
1028+
else {
1029+
val toTest: Symbol = if(claszSymbol.isModuleClass) { claszSymbol } else { claszSymbol.linkedClassOfClass }
1030+
if(toTest == NoSymbol) {
1031+
cunit.error(cd.pos, "Not amenable to statification: " + claszSymbol.fullName)
1032+
} else {
1033+
val msg = impedimentsToStatifiabilityOfModuleClass(toTest)
1034+
if(msg != null) {
1035+
cunit.error(cd.pos, msg)
1036+
}
1037+
}
1038+
}
1039+
}
1040+
}
10141041

10151042
val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor)
10161043
if(!hasStaticCtor) {
@@ -1128,7 +1155,9 @@ abstract class GenBCode extends BCodeOptInter {
11281155
*/
11291156
def initJMethod(flags: Int, paramAnnotations: List[List[AnnotationInfo]]) {
11301157

1131-
val jgensig = getGenericSignature(methSymbol, claszSymbol)
1158+
// a java-signature computed for an instance field can't in general be used for a field statified after-the-fact.
1159+
val jgensig = if(statifyThisClass) null else getGenericSignature(methSymbol, claszSymbol)
1160+
11321161
addRemoteExceptionAnnot(isCZRemote, hasPublicBitSet(flags), methSymbol)
11331162
val (excs, others) = methSymbol.annotations partition (_.symbol == definitions.ThrowsClass)
11341163
val thrownExceptions: List[String] = getExceptions(excs)
@@ -1191,9 +1220,13 @@ abstract class GenBCode extends BCodeOptInter {
11911220
* No code is needed for this module symbol.
11921221
*/
11931222
for (f <- fieldSymbols(claszSymbol)) {
1194-
val javagensig = getGenericSignature(f, claszSymbol)
1223+
1224+
// a java-signature computed for an instance field can't in general be used for a field statified after-the-fact.
1225+
val javagensig = if(statifyThisClass) null else getGenericSignature(f, claszSymbol)
1226+
11951227
val flags = mkFlags(
11961228
javaFieldFlags(f),
1229+
if(statifyThisClass) asm.Opcodes.ACC_STATIC else 0,
11971230
if(isDeprecated(f)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
11981231
)
11991232

@@ -1231,6 +1264,35 @@ abstract class GenBCode extends BCodeOptInter {
12311264
}
12321265
}
12331266

1267+
/* Usages sites of fields and methods that are "statified after-the-fact" by GenBCode
1268+
* result in bytecode being emitted to load the receiver. Usually GETSTATIC C.MODULE$ , othertimes ALOAD 0,
1269+
* but also a method invocation. Due to statification, the receiver should be removed from the operand stack.
1270+
* */
1271+
private def adaptReceiverDueToStatification(modClassBT: BType) {
1272+
val stream = mnode.instructions
1273+
val last = stream.getLast
1274+
last.getOpcode match {
1275+
1276+
case asm.Opcodes.GETSTATIC =>
1277+
val fi = last.asInstanceOf[FieldInsnNode]
1278+
assert(fi.owner == modClassBT.getInternalName)
1279+
stream.remove(fi)
1280+
1281+
case _ =>
1282+
if(asm.optimiz.Util.isLOAD(last)) {
1283+
val load = last.asInstanceOf[asm.tree.VarInsnNode]
1284+
assert(
1285+
if(load.`var` == 0) { thisBT == modClassBT } else true,
1286+
"Found ALOAD_0 in a context where adaptReceiverDueToStatification() expected to " +
1287+
s"load a ${modClassBT.getInternalName}} instance, but instead ${thisBT.getInternalName}} was loaded."
1288+
)
1289+
stream.remove(last)
1290+
} else {
1291+
stream.add(new asm.tree.InsnNode(asm.Opcodes.POP))
1292+
}
1293+
}
1294+
}
1295+
12341296
def genDefDef(dd: DefDef) {
12351297
// the only method whose implementation is not emitted: getClass()
12361298
if(definitions.isGetClass(dd.symbol)) { return }
@@ -1242,7 +1304,15 @@ abstract class GenBCode extends BCodeOptInter {
12421304

12431305
methSymbol = dd.symbol
12441306
jMethodName = methSymbol.javaSimpleName.toString
1245-
returnType = asmMethodType(dd.symbol).getReturnType
1307+
1308+
val mTypeBT = asmMethodType(dd.symbol)
1309+
1310+
val statifyAfterTheFact = {
1311+
!dd.symbol.isStaticMember &&
1312+
shouldStatifyMethod(dd.symbol, thisBT, jMethodName)
1313+
}
1314+
1315+
returnType = mTypeBT.getReturnType
12461316
isMethSymStaticCtor = methSymbol.isStaticConstructor
12471317
isMethSymBridge = methSymbol.isBridge
12481318

@@ -1256,14 +1326,15 @@ abstract class GenBCode extends BCodeOptInter {
12561326
val DefDef(_, _, _, vparamss, _, rhs) = dd
12571327
assert(vparamss.isEmpty || vparamss.tail.isEmpty, "Malformed parameter list: " + vparamss)
12581328
val params = if(vparamss.isEmpty) Nil else vparamss.head
1259-
nxtIdx = if (methSymbol.isStaticMember) 0 else 1;
1329+
nxtIdx = if (methSymbol.isStaticMember || statifyAfterTheFact) 0 else 1;
12601330
for (p <- params) { makeLocal(p.symbol) }
12611331
// debug assert((params.map(p => locals(p.symbol).tk)) == asmMethodType(methSymbol).getArgumentTypes.toList, "debug")
12621332

12631333
val isNative = methSymbol.hasAnnotation(definitions.NativeAttr)
12641334
val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface)
12651335
val flags = mkFlags(
12661336
javaFlags(methSymbol),
1337+
if(statifyAfterTheFact) asm.Opcodes.ACC_STATIC else 0,
12671338
if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0,
12681339
if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
12691340
if (isNative) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes
@@ -1425,9 +1496,22 @@ abstract class GenBCode extends BCodeOptInter {
14251496
case Assign(lhs @ Select(_, _), rhs) =>
14261497
val isStatic = lhs.symbol.isStaticMember
14271498
if (!isStatic) { genLoadQualifier(lhs) }
1428-
genLoad(rhs, symInfoTK(lhs.symbol))
1499+
val fieldTK = symInfoTK(lhs.symbol)
14291500
lineNumber(tree)
1430-
fieldStore(lhs.symbol)
1501+
val hostClassBT = exemplar(lhs.symbol.owner).c
1502+
if(!isStatic && shouldStatifyClass(hostClassBT)) {
1503+
adaptReceiverDueToStatification(hostClassBT)
1504+
genLoad(rhs, fieldTK)
1505+
val field = lhs.symbol
1506+
val owner = hostClassBT.getInternalName
1507+
val fieldJName = field.javaSimpleName.toString
1508+
val fieldDescr = fieldTK.getDescriptor
1509+
val opc = asm.Opcodes.PUTSTATIC
1510+
mnode.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
1511+
} else {
1512+
genLoad(rhs, fieldTK)
1513+
fieldStore(lhs.symbol)
1514+
}
14311515

14321516
case Assign(lhs, rhs) =>
14331517
val s = lhs.symbol
@@ -2014,7 +2098,9 @@ abstract class GenBCode extends BCodeOptInter {
20142098
assert(tree.symbol == claszSymbol || symIsModuleClass,
20152099
"Trying to access the this of another class: " +
20162100
"tree.symbol = " + tree.symbol + ", class symbol = " + claszSymbol + " compilation unit:"+ cunit)
2017-
if (symIsModuleClass && tree.symbol != claszSymbol) {
2101+
if (symIsModuleClass &&
2102+
(tree.symbol != claszSymbol || statifyThisClass)
2103+
) {
20182104
generatedType = genLoadModule(tree)
20192105
}
20202106
else {
@@ -2041,13 +2127,25 @@ abstract class GenBCode extends BCodeOptInter {
20412127
genLoadQualUnlessElidable()
20422128
genLoadModule(tree)
20432129
}
2044-
else if (sym.isStaticMember) {
2045-
genLoadQualUnlessElidable()
2046-
fieldLoad(sym, hostClass)
2047-
}
20482130
else {
2049-
genLoadQualifier(tree)
2050-
fieldLoad(sym, hostClass)
2131+
if (sym.isStaticMember) {
2132+
genLoadQualUnlessElidable()
2133+
fieldLoad(sym, hostClass)
2134+
}
2135+
else {
2136+
genLoadQualifier(tree)
2137+
val hostClassBT = exemplar(hostClass).c // hostClass shouldn't be null, but in that case field.owner will do.
2138+
if(shouldStatifyClass(hostClassBT)) {
2139+
adaptReceiverDueToStatification(hostClassBT)
2140+
val field = sym
2141+
val owner = hostClassBT.getInternalName
2142+
val fieldJName = field.javaSimpleName.toString
2143+
val fieldDescr = symInfoTK(field).getDescriptor
2144+
mnode.visitFieldInsn(asm.Opcodes.GETSTATIC, owner, fieldJName, fieldDescr)
2145+
} else {
2146+
fieldLoad(sym, hostClass)
2147+
}
2148+
}
20512149
}
20522150

20532151
case Ident(name) =>
@@ -2445,10 +2543,33 @@ abstract class GenBCode extends BCodeOptInter {
24452543

24462544
} // end of genNormalMethodCall()
24472545

2448-
genNormalMethodCall()
2546+
val symOwnerBT = exemplar(sym.owner).c
2547+
val jname = sym.javaSimpleName.toString
2548+
if(!sym.isStaticMember && shouldStatifyMethod(sym, symOwnerBT, jname)) {
2549+
genLoadQualifier(fun)
2550+
adaptReceiverDueToStatification(symOwnerBT)
2551+
genLoadArguments(args, paramTKs(app))
2552+
val jowner = symOwnerBT.getInternalName
2553+
val bmType = asmMethodType(sym)
2554+
val mdescr = bmType.getDescriptor
2555+
val callsite = new MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, mdescr)
2556+
mnode.instructions.add(callsite)
2557+
// prepare for inlining if needed
2558+
if(isIntraProgramOpt) {
2559+
val knockOuts = (isMethSymBridge || (sym == methSymbol))
2560+
if(!knockOuts && hasInline(sym)) {
2561+
val isHiO = isHigherOrderMethod(bmType)
2562+
val inlnTarget = new InlineTarget(callsite, cunit, app.pos)
2563+
if(isHiO) { cgn.hiOs ::= inlnTarget }
2564+
else { cgn.procs ::= inlnTarget }
2565+
}
2566+
}
2567+
} else {
2568+
genNormalMethodCall()
2569+
}
24492570

24502571
generatedType = asmMethodType(sym).getReturnType
2451-
}
2572+
}
24522573

24532574
}
24542575

@@ -2504,9 +2625,12 @@ abstract class GenBCode extends BCodeOptInter {
25042625
val arity = abstractFunctionArity(castToBT)
25052626

25062627
val delegateSym = fakeCallsite.symbol.asInstanceOf[MethodSymbol]
2507-
val hasStaticModuleOwner = isStaticModule(delegateSym.owner)
2628+
val delegateOwner = delegateSym.owner
2629+
val hasStaticModuleOwner = {
2630+
isStaticModule(delegateOwner) || shouldStatifyClass(delegateOwner)
2631+
}
25082632
val hasOuter = !delegateSym.isStaticMember && !hasStaticModuleOwner
2509-
val isStaticImplMethod = delegateSym.owner.isImplClass
2633+
val isStaticImplMethod = delegateOwner.isImplClass
25102634

25112635
assert(
25122636
uncurry.closureDelegates.contains(delegateSym),
@@ -2526,7 +2650,7 @@ abstract class GenBCode extends BCodeOptInter {
25262650
// checking working assumptions
25272651

25282652
// outerTK is a poor name choice because sometimes there's no outer instance yet there's always a delegateOwnerTK
2529-
val outerTK = brefType(internalName(delegateSym.owner))
2653+
val outerTK = exemplar(delegateOwner).c
25302654
val enclClassBT = brefType(cnode.name)
25312655
assert(outerTK.hasObjectSort, s"Not of object sort: $outerTK")
25322656
assert(
@@ -2935,7 +3059,11 @@ abstract class GenBCode extends BCodeOptInter {
29353059
val ultimateMT = BT.getMethodType(ultimate.desc)
29363060

29373061
// in order to invoke the delegate, load the receiver if any
2938-
if(hasStaticModuleOwner) {
3062+
val statifyOuter = shouldStatifyClass(outerTK)
3063+
if(statifyOuter) {
3064+
()
3065+
}
3066+
else if(hasStaticModuleOwner) {
29393067
// GETSTATIC the/module/Class$.MODULE$ : Lthe/module/Class;
29403068
ultimate.visitFieldInsn(
29413069
asm.Opcodes.GETSTATIC,
@@ -2974,7 +3102,8 @@ abstract class GenBCode extends BCodeOptInter {
29743102
}
29753103

29763104
val callOpc =
2977-
if(hasOuter || hasStaticModuleOwner) asm.Opcodes.INVOKEVIRTUAL
3105+
if(statifyOuter) asm.Opcodes.INVOKESTATIC
3106+
else if(hasOuter || hasStaticModuleOwner) asm.Opcodes.INVOKEVIRTUAL
29783107
else asm.Opcodes.INVOKESTATIC
29793108
ultimate.visitMethodInsn(
29803109
callOpc,
@@ -3239,7 +3368,7 @@ abstract class GenBCode extends BCodeOptInter {
32393368
}
32403369

32413370
def genLoadModule(module: Symbol) {
3242-
if (claszSymbol == module.moduleClass && jMethodName != "readResolve") {
3371+
if (claszSymbol == module.moduleClass && jMethodName != "readResolve" && !statifyThisClass) {
32433372
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
32443373
} else {
32453374
val mbt = asmClassType(module)

0 commit comments

Comments
 (0)