Skip to content

Commit 8ff3676

Browse files
committed
Use invokedynamic for structural calls, symbol literals, lamba ser.
The previous encodings eagerly created static fields (for caches) in the enclosing class, but this isn't an option once we start to emit code in interface default methods. Generate static forwarders for object members in companion interface We used to disable generation of static forwarders when a object had a trait as a companion, as one could not add methods with bodies to an interface in JVM 6. The JVM lifted this restriction to support default methods in interfaces, so we can lift the restriction on static forwarders, too. I've only commited the test using the default backend (GenBCode), but I've also made the change and manaully verified the test works under the soon-to-be-removed GenASM. ``` % qscalac -Ybackend:GenASM test/files/run/trait-static-forwarder/forwarders.scala && javac -d . -classpath . test/files/run/trait-static-forwarder/Test.java && qscala Test ./T.class: warning: Cannot find annotation method 'bytes()' in type 'ScalaSignature': class file for scala.reflect.ScalaSignature not found 1 warning 42 ``` Fixes scala/scala-dev#59
1 parent 3a72a2d commit 8ff3676

File tree

16 files changed

+185
-150
lines changed

16 files changed

+185
-150
lines changed

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,20 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
310310
case app : Apply =>
311311
generatedType = genApply(app, expectedType)
312312

313+
case app @ ApplyDynamic(qual, Literal(Constant(reflectiveMethodType: MethodType)) :: Nil) if qual.symbol == MethodCacheIndy =>
314+
val returnAsmType = typeToBType(qual.symbol.info.resultType).toASMType
315+
val invokedType = asm.Type.getMethodType(returnAsmType)
316+
val reflectiveParams = reflectiveMethodType.params.map(p => typeToBType(p.info))
317+
val reflectiveParamsDescriptor = MethodBType(reflectiveParams, ObjectRef).toASMType.getDescriptor
318+
mnode.visitInvokeDynamicInsn(qual.symbol.name.encoded, invokedType.getDescriptor, structuralCallSite_BoostrapHandle, reflectiveParamsDescriptor)
319+
320+
case app @ ApplyDynamic(qual, Literal(Constant(symname: String)) :: Nil) if qual.symbol == Symbol_apply =>
321+
val symbol = app.symbol
322+
val returnAsmType = typeToBType(symbol.info.resultType).toASMType
323+
val invokedType = asm.Type.getMethodType(returnAsmType)
324+
val name = "apply"
325+
mnode.visitInvokeDynamicInsn(name, invokedType.getDescriptor, symbolLiteralBoostrapHandle, symname)
326+
313327
case ApplyDynamic(qual, args) => sys.error("No invokedynamic support yet.")
314328

315329
case This(qual) =>
@@ -1030,7 +1044,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
10301044
|| hostSymbol.isBottomClass
10311045
|| methodOwner == definitions.ObjectClass
10321046
)
1033-
val receiver = if (useMethodOwner) methodOwner else hostSymbol
1047+
val receiver = if (useMethodOwner) {
1048+
if (methodOwner.isTrait && style.isSuper && siteSymbol != methodOwner && !siteSymbol.parentSymbols.contains(methodOwner))
1049+
siteSymbol.parentSymbols.find(_.isSubClass(methodOwner)).get
1050+
else methodOwner
1051+
} else hostSymbol
10341052
val jowner = internalName(receiver)
10351053
val jname = method.javaSimpleName.toString
10361054
val bmType = methodBTypeFromSymbol(method)
@@ -1313,7 +1331,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
13131331
def asmType(sym: Symbol) = classBTypeFromSymbol(sym).toASMType
13141332

13151333
val implMethodHandle =
1316-
new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else asm.Opcodes.H_INVOKEVIRTUAL,
1334+
new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else if (lambdaTarget.isTrait) asm.Opcodes.H_INVOKEINTERFACE else asm.Opcodes.H_INVOKEVIRTUAL,
13171335
classBTypeFromSymbol(lambdaTarget.owner).internalName,
13181336
lambdaTarget.name.toString,
13191337
methodBTypeFromSymbol(lambdaTarget).descriptor)
@@ -1354,4 +1372,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
13541372
ArrayBType(ObjectRef)),
13551373
coreBTypes.jliCallSiteRef
13561374
).descriptor)
1375+
1376+
lazy val symbolLiteralBoostrapHandle =
1377+
new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
1378+
"scala/runtime/SymbolLiteral", "bootstrap",
1379+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;")
1380+
1381+
lazy val structuralCallSite_BoostrapHandle =
1382+
new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
1383+
"scala/runtime/StructuralCallSite", "bootstrap",
1384+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;")
13571385
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
169169

170170
} else {
171171

172-
val skipStaticForwarders = (claszSymbol.isInterface || settings.noForwarders)
172+
val skipStaticForwarders = settings.noForwarders
173173
if (!skipStaticForwarders) {
174174
val lmoc = claszSymbol.companionModule
175175
// add static forwarders if there are no name conflicts; see bugs #363 and #1735

src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,20 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
5454

5555
class ProdConsAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new InitialProducerSourceInterpreter)) with ProdConsAnalyzerImpl
5656

57+
lazy val lambdaDeserializeBoostrapHandle =
58+
new scala.tools.asm.Handle(scala.tools.asm.Opcodes.H_INVOKESTATIC,
59+
"scala/runtime/LambdaDeserialize", "bootstrap",
60+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;")
61+
5762
/**
5863
* Add:
59-
* private static java.util.Map $deserializeLambdaCache$ = null
6064
* private static Object $deserializeLambda$(SerializedLambda l) {
61-
* var cache = $deserializeLambdaCache$
62-
* if (cache eq null) {
63-
* cache = new java.util.HashMap()
64-
* $deserializeLambdaCache$ = cache
65-
* }
66-
* return scala.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l);
65+
* return indy[scala.runtime.LambdaDeserialize.bootstrap](l)
6766
* }
67+
*
68+
* We use invokedynamic here to enable caching within the deserializer without needing to
69+
* host a static field in the enclosing class. This allows us to add this method to interfaces
70+
* that define lambdas in default methods.
6871
*/
6972
def addLambdaDeserialize(classNode: ClassNode): Unit = {
7073
val cw = classNode
@@ -77,37 +80,14 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
7780
// stack map frames and invokes the `getCommonSuperClass` method. This method expects all
7881
// ClassBTypes mentioned in the source code to exist in the map.
7982

80-
val mapDesc = juMapRef.descriptor
8183
val nilLookupDesc = MethodBType(Nil, jliMethodHandlesLookupRef).descriptor
8284
val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor
83-
val lookupMapSerlamObjDesc = MethodBType(jliMethodHandlesLookupRef :: juMapRef :: jliSerializedLambdaRef :: Nil, ObjectRef).descriptor
84-
85-
{
86-
val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", mapDesc, null, null)
87-
fv.visitEnd()
88-
}
8985

9086
{
9187
val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null)
9288
mv.visitCode()
93-
// javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol.
94-
mv.visitFieldInsn(GETSTATIC, classNode.name, "$deserializeLambdaCache$", mapDesc)
95-
mv.visitVarInsn(ASTORE, 1)
96-
mv.visitVarInsn(ALOAD, 1)
97-
val l0 = new Label()
98-
mv.visitJumpInsn(IFNONNULL, l0)
99-
mv.visitTypeInsn(NEW, juHashMapRef.internalName)
100-
mv.visitInsn(DUP)
101-
mv.visitMethodInsn(INVOKESPECIAL, juHashMapRef.internalName, "<init>", "()V", false)
102-
mv.visitVarInsn(ASTORE, 1)
103-
mv.visitVarInsn(ALOAD, 1)
104-
mv.visitFieldInsn(PUTSTATIC, classNode.name, "$deserializeLambdaCache$", mapDesc)
105-
mv.visitLabel(l0)
106-
mv.visitFieldInsn(GETSTATIC, srLambdaDeserializerRef.internalName, "MODULE$", srLambdaDeserializerRef.descriptor)
107-
mv.visitMethodInsn(INVOKESTATIC, jliMethodHandlesRef.internalName, "lookup", nilLookupDesc, false)
108-
mv.visitVarInsn(ALOAD, 1)
10989
mv.visitVarInsn(ALOAD, 0)
110-
mv.visitMethodInsn(INVOKEVIRTUAL, srLambdaDeserializerRef.internalName, "deserializeLambda", lookupMapSerlamObjDesc, false)
90+
mv.visitInvokeDynamicInsn("lambdaDeserialize", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", lambdaDeserializeBoostrapHandle)
11191
mv.visitInsn(ARETURN)
11292
mv.visitEnd()
11393
}
@@ -204,7 +184,8 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
204184
def visitAnnotationss(annotss: Array[java.util.List[AnnotationNode]]) = if (annotss != null) annotss foreach visitAnnotations
205185

206186
def visitHandle(handle: Handle): Unit = {
207-
visitInternalNameOrArrayReference(handle.getOwner)
187+
if (handle.getOwner != "scala/runtime/LambdaDeserialize" && handle.getOwner != "scala/runtime/SymbolLiteral")
188+
visitInternalNameOrArrayReference(handle.getOwner)
208189
visitDescriptor(handle.getDesc)
209190
}
210191

src/compiler/scala/tools/nsc/transform/CleanUp.scala

Lines changed: 21 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -87,24 +87,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
8787

8888
/* ### CREATING THE METHOD CACHE ### */
8989

90-
def addStaticVariableToClass(forName: TermName, forType: Type, forInit: Tree, isFinal: Boolean): Symbol = {
91-
val flags = PRIVATE | STATIC | SYNTHETIC | (
92-
if (isFinal) FINAL else 0
93-
)
94-
95-
val varSym = currentClass.newVariable(mkTerm("" + forName), ad.pos, flags.toLong) setInfoAndEnter forType
96-
if (!isFinal)
97-
varSym.addAnnotation(VolatileAttr)
98-
99-
val varDef = typedPos(ValDef(varSym, forInit))
100-
newStaticMembers append transform(varDef)
101-
102-
val varInit = typedPos( REF(varSym) === forInit )
103-
newStaticInits append transform(varInit)
104-
105-
varSym
106-
}
107-
10890
def addStaticMethodToClass(forBody: (Symbol, Symbol) => Tree): Symbol = {
10991
val methSym = currentClass.newMethod(mkTerm(nme.reflMethodName.toString), ad.pos, STATIC | SYNTHETIC)
11092
val params = methSym.newSyntheticValueParams(List(ClassClass.tpe))
@@ -115,9 +97,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
11597
methSym
11698
}
11799

118-
def fromTypesToClassArrayLiteral(paramTypes: List[Type]): Tree =
119-
ArrayValue(TypeTree(ClassClass.tpe), paramTypes map LIT)
120-
121100
def reflectiveMethodCache(method: String, paramTypes: List[Type]): Symbol = {
122101
/* Implementation of the cache is as follows for method "def xyz(a: A, b: B)"
123102
(SoftReference so that it does not interfere with classloader garbage collection,
@@ -128,7 +107,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
128107
var reflPoly$Cache: SoftReference[scala.runtime.MethodCache] = new SoftReference(new EmptyMethodCache())
129108
130109
def reflMethod$Method(forReceiver: JClass[_]): JMethod = {
131-
var methodCache: MethodCache = reflPoly$Cache.find(forReceiver)
110+
var methodCache: StructuralCallSite = indy[StructuralCallSite.bootstrap, "(LA;LB;)Ljava/lang/Object;]
132111
if (methodCache eq null) {
133112
methodCache = new EmptyMethodCache
134113
reflPoly$Cache = new SoftReference(methodCache)
@@ -137,41 +116,32 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
137116
if (method ne null)
138117
return method
139118
else {
140-
method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", reflParams$Cache))
141-
reflPoly$Cache = new SoftReference(methodCache.add(forReceiver, method))
119+
method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", methodCache.parameterTypes()))
120+
methodCache.add(forReceiver, method)
142121
return method
143122
}
144123
}
145-
*/
146-
147-
val reflParamsCacheSym: Symbol =
148-
addStaticVariableToClass(nme.reflParamsCacheName, arrayType(ClassClass.tpe), fromTypesToClassArrayLiteral(paramTypes), true)
149-
150-
def mkNewPolyCache = gen.mkSoftRef(NEW(TypeTree(EmptyMethodCacheClass.tpe)))
151-
val reflPolyCacheSym: Symbol = addStaticVariableToClass(nme.reflPolyCacheName, SoftReferenceClass.tpe, mkNewPolyCache, false)
152124
153-
def getPolyCache = gen.mkCast(fn(REF(reflPolyCacheSym), nme.get), MethodCacheClass.tpe)
125+
invokedynamic is used rather than a static field for the cache to support emitting bodies of methods
126+
in Java 8 interfaces, which don't support static fields.
127+
*/
154128

155129
addStaticMethodToClass((reflMethodSym, forReceiverSym) => {
156-
val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo MethodCacheClass.tpe
130+
val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo StructuralCallSite.tpe
157131
val methodSym = reflMethodSym.newVariable(mkTerm("method"), ad.pos) setInfo MethodClass.tpe
158132

133+
val dummyMethodType = MethodType(NoSymbol.newSyntheticValueParams(paramTypes), AnyTpe)
159134
BLOCK(
160-
ValDef(methodCache, getPolyCache),
161-
IF (REF(methodCache) OBJ_EQ NULL) THEN BLOCK(
162-
REF(methodCache) === NEW(TypeTree(EmptyMethodCacheClass.tpe)),
163-
REF(reflPolyCacheSym) === gen.mkSoftRef(REF(methodCache))
164-
) ENDIF,
165-
166-
ValDef(methodSym, (REF(methodCache) DOT methodCache_find)(REF(forReceiverSym))),
135+
ValDef(methodCache, ApplyDynamic(gen.mkAttributedIdent(MethodCacheIndy), LIT(dummyMethodType) :: Nil).setType(StructuralCallSite.tpe)),
136+
ValDef(methodSym, (REF(methodCache) DOT StructuralCallSite_find)(REF(forReceiverSym))),
167137
IF (REF(methodSym) OBJ_NE NULL) .
168138
THEN (Return(REF(methodSym)))
169139
ELSE {
170-
def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), REF(reflParamsCacheSym)))
171-
def cacheRHS = ((REF(methodCache) DOT methodCache_add)(REF(forReceiverSym), REF(methodSym)))
140+
def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), (REF(methodCache) DOT StructuralCallSite_getParameterTypes)()))
141+
def cacheAdd = ((REF(methodCache) DOT StructuralCallSite_add)(REF(forReceiverSym), REF(methodSym)))
172142
BLOCK(
173143
REF(methodSym) === (REF(currentRun.runDefinitions.ensureAccessibleMethod) APPLY (methodSymRHS)),
174-
REF(reflPolyCacheSym) === gen.mkSoftRef(cacheRHS),
144+
cacheAdd,
175145
Return(REF(methodSym))
176146
)
177147
}
@@ -371,6 +341,8 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
371341
reporter.error(ad.pos, "Cannot resolve overload.")
372342
(Nil, NoType)
373343
}
344+
case NoType =>
345+
abort(ad.symbol.toString)
374346
}
375347
typedPos {
376348
val sym = currentOwner.newValue(mkTerm("qual"), ad.pos) setInfo qual0.tpe
@@ -448,7 +420,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
448420
* refinement, where the refinement defines a parameter based on a
449421
* type variable. */
450422

451-
case tree: ApplyDynamic =>
423+
case tree: ApplyDynamic if tree.symbol.owner.isRefinementClass =>
452424
transformApplyDynamic(tree)
453425

454426
/* Some cleanup transformations add members to templates (classes, traits, etc).
@@ -478,62 +450,23 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
478450

479451
/*
480452
* This transformation should identify Scala symbol invocations in the tree and replace them
481-
* with references to a static member. Also, whenever a class has at least a single symbol invocation
482-
* somewhere in its methods, a new static member should be created and initialized for that symbol.
483-
* For instance, say we have a Scala class:
484-
*
485-
* class Cls {
486-
* def someSymbol1 = 'Symbolic1
487-
* def someSymbol2 = 'Symbolic2
488-
* def sameSymbol1 = 'Symbolic1
489-
* val someSymbol3 = 'Symbolic3
490-
* }
491-
*
492-
* After transformation, this class looks like this:
493-
*
494-
* class Cls {
495-
* private <static> var symbol$1: scala.Symbol
496-
* private <static> var symbol$2: scala.Symbol
497-
* private <static> var symbol$3: scala.Symbol
498-
* private val someSymbol3: scala.Symbol
499-
*
500-
* private <static> def <clinit> = {
501-
* symbol$1 = Symbol.apply("Symbolic1")
502-
* symbol$2 = Symbol.apply("Symbolic2")
503-
* }
504-
*
505-
* private def <init> = {
506-
* someSymbol3 = symbol$3
507-
* }
508-
*
509-
* def someSymbol1 = symbol$1
510-
* def someSymbol2 = symbol$2
511-
* def sameSymbol1 = symbol$1
512-
* val someSymbol3 = someSymbol3
513-
* }
453+
* with references to a statically cached instance.
514454
*
515455
* The reasoning behind this transformation is the following. Symbols get interned - they are stored
516456
* in a global map which is protected with a lock. The reason for this is making equality checks
517457
* quicker. But calling Symbol.apply, although it does return a unique symbol, accesses a locked object,
518458
* making symbol access slow. To solve this, the unique symbol from the global symbol map in Symbol
519-
* is accessed only once during class loading, and after that, the unique symbol is in the static
520-
* member. Hence, it is cheap to both reach the unique symbol and do equality checks on it.
459+
* is accessed only once during class loading, and after that, the unique symbol is in the statically
460+
* initialized call site returned by invokedynamic. Hence, it is cheap to both reach the unique symbol
461+
* and do equality checks on it.
521462
*
522463
* And, finally, be advised - Scala's Symbol literal (scala.Symbol) and the Symbol class of the compiler
523464
* have little in common.
524465
*/
525466
case Apply(fn @ Select(qual, _), (arg @ Literal(Constant(symname: String))) :: Nil)
526467
if treeInfo.isQualifierSafeToElide(qual) && fn.symbol == Symbol_apply && !currentClass.isTrait =>
527468

528-
def transformApply = {
529-
// add the symbol name to a map if it's not there already
530-
val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil)
531-
val staticFieldSym = getSymbolStaticField(tree.pos, symname, rhs, tree)
532-
// create a reference to a static field
533-
val ntree = typedWithPos(tree.pos)(REF(staticFieldSym))
534-
super.transform(ntree)
535-
}
536-
transformApply
469+
super.transform(treeCopy.ApplyDynamic(tree, fn, arg :: Nil))
537470

538471
// Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), <tag>)`
539472
// with just `ArrayValue(...).$asInstanceOf[...]`
@@ -550,32 +483,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
550483
super.transform(tree)
551484
}
552485

553-
/* Returns the symbol and the tree for the symbol field interning a reference to a symbol 'synmname'.
554-
* If it doesn't exist, i.e. the symbol is encountered the first time,
555-
* it creates a new static field definition and initialization and returns it.
556-
*/
557-
private def getSymbolStaticField(pos: Position, symname: String, rhs: Tree, tree: Tree): Symbol = {
558-
symbolsStoredAsStatic.getOrElseUpdate(symname, {
559-
val theTyper = typer.atOwner(tree, currentClass)
560-
561-
// create a symbol for the static field
562-
val stfieldSym = (
563-
currentClass.newVariable(mkTerm("symbol$"), pos, PRIVATE | STATIC | SYNTHETIC | FINAL)
564-
setInfoAndEnter SymbolClass.tpe
565-
)
566-
567-
// create field definition and initialization
568-
val stfieldDef = theTyper.typedPos(pos)(ValDef(stfieldSym, rhs))
569-
val stfieldInit = theTyper.typedPos(pos)(REF(stfieldSym) === rhs)
570-
571-
// add field definition to new defs
572-
newStaticMembers append stfieldDef
573-
newStaticInits append stfieldInit
574-
575-
stfieldSym
576-
})
577-
}
578-
579486
} // CleanUpTransformer
580487

581488
}

0 commit comments

Comments
 (0)