diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm')
20 files changed, 438 insertions, 348 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 5d152ef0e8..55fe47bde6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -11,10 +11,10 @@ package jvm import scala.annotation.switch import scala.reflect.internal.Flags - import scala.tools.asm import GenBCode._ import BackendReporting._ +import scala.tools.asm.Opcodes import scala.tools.asm.tree.MethodInsnNode import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp} @@ -637,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind) - bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos) generatedType = boxResultType(fun.symbol) case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => @@ -645,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val boxType = unboxResultType(fun.symbol) generatedType = boxType val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType) - bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos) case app @ Apply(fun, args) => val sym = fun.symbol @@ -1058,31 +1058,40 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits - val receiverName = internalName(receiverClass) - - // super calls are only allowed to direct parents - if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) { - thisBType.info.get.inlineInfo.lateInterfaces += receiverName - cnode.interfaces.add(receiverName) - } + val receiverBType = classBTypeFromSymbol(receiverClass) + val receiverName = receiverBType.internalName def needsInterfaceCall(sym: Symbol) = { sym.isTraitOrInterface || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) } - val jname = method.javaSimpleName.toString - val bmType = methodBTypeFromSymbol(method) - val mdescr = bmType.descriptor + val jname = method.javaSimpleName.toString + val bmType = methodBTypeFromSymbol(method) + val mdescr = bmType.descriptor + val isInterface = receiverBType.isInterface.get import InvokeStyle._ - style match { - case Static => bc.invokestatic (receiverName, jname, mdescr, pos) - case Special => bc.invokespecial (receiverName, jname, mdescr, pos) - case Virtual => - if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos) - else bc.invokevirtual (receiverName, jname, mdescr, pos) - case Super => bc.invokespecial (receiverName, jname, mdescr, pos) + if (style == Super) { + assert(receiverClass == methodOwner, s"for super call, expecting $receiverClass == $methodOwner") + if (receiverClass.isTrait && !receiverClass.isJavaDefined) { + val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor + val staticName = traitImplMethodName(method).toString + bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos) + } else { + if (receiverClass.isTraitOrInterface) { + // An earlier check in Mixin reports an error in this case, so it doesn't reach the backend + assert(cnode.interfaces.contains(receiverName), s"cannot invokespecial $receiverName.$jname, the interface is not a direct parent.") + } + bc.invokespecial(receiverName, jname, mdescr, isInterface, pos) + } + } else { + val opc = style match { + case Static => Opcodes.INVOKESTATIC + case Special => Opcodes.INVOKESPECIAL + case Virtual => if (isInterface) Opcodes.INVOKEINTERFACE else Opcodes.INVOKEVIRTUAL + } + bc.emitInvoke(opc, receiverName, jname, mdescr, isInterface, pos) } bmType.returnType @@ -1110,22 +1119,19 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } /* Emit code to compare the two top-most stack values using the 'op' operator. */ - private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) { - if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump) + private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) { + if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated) else { if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT bc.emitIF_ICMP(op, success) } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) bc.emitIF_ACMP(op, success) } else { + def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE (tk: @unchecked) match { case LONG => emit(asm.Opcodes.LCMP) - case FLOAT => - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG) - else emit(asm.Opcodes.FCMPL) - case DOUBLE => - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) - else emit(asm.Opcodes.DCMPL) + case FLOAT => emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL) + case DOUBLE => emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL) } bc.emitIF(op, success) } @@ -1134,8 +1140,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } /* Emits code to compare (and consume) stack-top and zero using the 'op' operator */ - private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) { - if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump) + private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) { + if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated) else { if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT bc.emitIF(op, success) @@ -1145,18 +1151,17 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case TestOp.NE => bc emitIFNONNULL success } } else { + def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE (tk: @unchecked) match { case LONG => emit(asm.Opcodes.LCONST_0) emit(asm.Opcodes.LCMP) case FLOAT => emit(asm.Opcodes.FCONST_0) - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG) - else emit(asm.Opcodes.FCMPL) + emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL) case DOUBLE => emit(asm.Opcodes.DCONST_0) - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) - else emit(asm.Opcodes.DCMPL) + emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL) } bc.emitIF(op, success) } @@ -1171,8 +1176,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case scalaPrimitives.NE => TestOp.NE case scalaPrimitives.LT => TestOp.LT case scalaPrimitives.LE => TestOp.LE - case scalaPrimitives.GE => TestOp.GE case scalaPrimitives.GT => TestOp.GT + case scalaPrimitives.GE => TestOp.GE } /** Some useful equality helpers. */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index a32c21795d..df3c2cb3d5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -11,6 +11,7 @@ import scala.tools.asm import scala.tools.nsc.io.AbstractFile import GenBCode._ import BackendReporting._ +import scala.reflect.internal.Flags /* * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. @@ -49,6 +50,14 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } } + def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type] + + final def traitImplMethodName(sym: Symbol): Name = { + val name = sym.javaSimpleName + if (sym.isMixinConstructor) name + else name.append(nme.NAME_JOIN_STRING) + } + /** * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a * member class. This method is used to decide if we should emit an EnclosingMethod attribute. @@ -76,7 +85,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { val origOwner = sym.originalOwner // phase travel necessary: after flatten, the name includes the name of outer classes. // if some outer name contains $anon, a non-anon class is considered anon. - if (delambdafyInline() && sym.rawowner.isAnonymousFunction) { + if (delambdafyInline() && exitingPickler(sym.rawowner.isAnonymousFunction)) { // SI-9105: special handling for anonymous functions under delambdafy:inline. // // class C { def t = () => { def f { class Z } } } @@ -230,58 +239,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { sym.isErroneous } - /** - * Build the [[InlineInfo]] for a class symbol. - */ - def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = { - val isEffectivelyFinal = classSym.isEffectivelyFinal - - val sam = { - if (classSym.isEffectivelyFinal) None - else { - // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an - // empty parameter list in later phases and would therefore be picked as SAM. - val samSym = exitingPickler(definitions.samOf(classSym.tpe)) - if (samSym == NoSymbol) None - else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) - } - } - - var warning = Option.empty[ClassSymbolInfoFailureSI9111] - - // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some - // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. - val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ - case methodSym => - if (completeSilentlyAndCheckErroneous(methodSym)) { - // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. - if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") - warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) - None - } else { - val name = methodSym.javaSimpleName.toString // same as in genDefDef - val signature = name + methodSymToDescriptor(methodSym) - - // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the - // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded - // so they are not marked final in the InlineInfo attribute. - // - // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not - // work, the abstract accessor for O will be marked effectivelyFinal. - val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred - - val info = MethodInlineInfo( - effectivelyFinal = effectivelyFinal, - annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), - annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) - ) - Some((signature, info)) - } - }).toMap - - InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning) - } - /* * must-single-thread */ @@ -568,15 +525,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { /** * The class internal name for a given class symbol. */ - final def internalName(sym: Symbol): String = { - // For each java class, the scala compiler creates a class and a module (thus a module class). - // If the `sym` is a java module class, we use the java class instead. This ensures that the - // ClassBType is created from the main class (instead of the module class). - // The two symbols have the same name, so the resulting internalName is the same. - // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) - val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym - classBTypeFromSymbol(classSym).internalName - } + final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName } // end of trait BCInnerClassGen trait BCAnnotGen extends BCInnerClassGen { @@ -1336,6 +1285,7 @@ object BCodeHelpers { } object TestOp { + // the order here / op numbers are important to get the correct result when calling opcodeIF val EQ = new TestOp(0) val NE = new TestOp(1) val LT = new TestOp(2) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index 0a95bc5e39..e3d45a9b3e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -190,6 +190,7 @@ abstract class BCodeIdiomatic extends SubComponent { JavaStringBuilderClassName, INSTANCE_CONSTRUCTOR_NAME, "()V", + itf = false, pos ) } @@ -373,29 +374,26 @@ abstract class BCodeIdiomatic extends SubComponent { final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread // can-multi-thread - final def invokespecial(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos) + final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos) } // can-multi-thread - final def invokestatic(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos) + final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos) } // can-multi-thread - final def invokeinterface(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos) + final def invokeinterface(owner: String, name: String, desc: String, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos) } // can-multi-thread - final def invokevirtual(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos) + final def invokevirtual(owner: String, name: String, desc: String, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos) } - private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = { + def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = { val node = new MethodInsnNode(opcode, owner, name, desc, itf) jmethod.instructions.add(node) - if (settings.YoptInlinerEnabled) callsitePositions(node) = pos - } - final def invokedynamic(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc) + if (settings.optInlinerEnabled) callsitePositions(node) = pos } // can-multi-thread diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index bddc41e5c6..1bff8519ec 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -488,7 +488,22 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()` - case dd : DefDef => genDefDef(dd) + case dd : DefDef => + val sym = dd.symbol + if (needsStaticImplMethod(sym)) { + val staticDefDef = global.gen.mkStatic(dd, traitImplMethodName(sym), _.cloneSymbol) + val forwarderDefDef = { + val forwarderBody = Apply(global.gen.mkAttributedRef(staticDefDef.symbol), This(sym.owner).setType(sym.owner.typeConstructor) :: dd.vparamss.head.map(p => global.gen.mkAttributedIdent(p.symbol))).setType(sym.info.resultType) + // we don't want to the optimizer to inline the static method into the forwarder. Instead, + // the backend has a special case to transitively inline into a callsite of the forwarder + // when the forwarder itself is inlined. + forwarderBody.updateAttachment(NoInlineCallsiteAttachment) + deriveDefDef(dd)(_ => global.atPos(dd.pos)(forwarderBody)) + } + genDefDef(staticDefDef) + if (!sym.isMixinConstructor) + genDefDef(forwarderDefDef) + } else genDefDef(dd) case Template(_, _, body) => body foreach gen diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 2637d21050..7b2686e7a9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -225,8 +225,7 @@ abstract class BTypes { val inlineInfo = inlineInfoFromClassfile(classNode) - val classfileInterfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) - val interfaces = classfileInterfaces.filterNot(i => inlineInfo.lateInterfaces.contains(i.internalName)) + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType @@ -271,7 +270,7 @@ abstract class BTypes { // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT // being compiled. For those classes, the info is only needed if the inliner is enabled, othewise // we can save the memory. - if (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo + if (!compilerSettings.optInlinerEnabled) BTypes.EmptyInlineInfo else fromClassfileAttribute getOrElse fromClassfileWithoutAttribute } @@ -1147,25 +1146,6 @@ object BTypes { sam: Option[String], methodInfos: Map[String, MethodInlineInfo], warning: Option[ClassInlineInfoWarning]) { - /** - * A super call (invokespecial) to a default method T.m is only allowed if the interface T is - * a direct parent of the class. Super calls are introduced for example in Mixin when generating - * forwarder methods: - * - * trait T { override def clone(): Object = "hi" } - * trait U extends T - * class C extends U - * - * The class C gets a forwarder that invokes T.clone(). During code generation the interface T - * is added as direct parent to class C. Note that T is not a (direct) parent in the frontend - * type of class C. - * - * All interfaces that are added to a class during code generation are added to this buffer and - * stored in the InlineInfo classfile attribute. This ensures that the ClassBTypes for a - * specific class is the same no matter if it's constructed from a Symbol or from a classfile. - * This is tested in BTypesFromClassfileTest. - */ - val lateInterfaces: ListBuffer[InternalName] = ListBuffer.empty } val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index d10b6c8dba..1a4590e7d1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -97,11 +97,19 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes * in the classfile method signature. */ - final def classBTypeFromSymbol(classSym: Symbol): ClassBType = { + final def classBTypeFromSymbol(sym: Symbol): ClassBType = { + // For each java class, the scala compiler creates a class and a module (thus a module class). + // If the `sym` is a java module class, we use the java class instead. This ensures that the + // ClassBType is created from the main class (instead of the module class). + // The two symbols have the same name, so the resulting internalName is the same. + // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) + val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym + assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") assertClassNotArrayNotPrimitive(classSym) assert(!primitiveTypeToBType.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym") + if (classSym == NothingClass) srNothingRef else if (classSym == NullClass) srNullRef else { @@ -234,12 +242,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation) + val minimizedParents = if (classSym.isJavaDefined) allParents else erasure.minimizeParents(allParents) // We keep the superClass when computing minimizeParents to eliminate more interfaces. // Example: T can be eliminated from D // trait T // class C extends T // class D extends C with T - val interfaces = erasure.minimizeParents(allParents) match { + val interfaces = minimizedParents match { case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) => ifs case ifs => @@ -508,13 +517,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * classfile attribute. */ private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = { - def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor) + def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym) // phase travel required, see implementation of `compiles`. for nested classes, it checks if the // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level, // so `compiles` would return `false`. if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol // InlineInfo required for classes being compiled, we have to create the classfile attribute - else if (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled. + else if (!compilerSettings.optInlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled. else { // For classes not being compiled, the InlineInfo is read from the classfile attribute. This // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class @@ -530,6 +539,74 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } /** + * Build the [[InlineInfo]] for a class symbol. + */ + def buildInlineInfoFromClassSymbol(classSym: Symbol): InlineInfo = { + val isEffectivelyFinal = classSym.isEffectivelyFinal + + val sam = { + if (classSym.isEffectivelyFinal) None + else { + // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an + // empty parameter list in later phases and would therefore be picked as SAM. + val samSym = exitingPickler(definitions.samOf(classSym.tpe)) + if (samSym == NoSymbol) None + else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor) + } + } + + var warning = Option.empty[ClassSymbolInfoFailureSI9111] + + // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some + // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. + val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ + case methodSym => + if (completeSilentlyAndCheckErroneous(methodSym)) { + // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. + if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") + warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) + Nil + } else { + val name = methodSym.javaSimpleName.toString // same as in genDefDef + val signature = name + methodBTypeFromSymbol(methodSym).descriptor + + // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the + // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded + // so they are not marked final in the InlineInfo attribute. + // + // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not + // work, the abstract accessor for O will be marked effectivelyFinal. + val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred + + val info = MethodInlineInfo( + effectivelyFinal = effectivelyFinal, + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)) + + if (needsStaticImplMethod(methodSym)) { + val staticName = traitImplMethodName(methodSym).toString + val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF) + val staticMethodType = methodSym.info match { + case mt @ MethodType(params, res) => copyMethodType(mt, selfParam :: params, res) + } + val staticMethodSignature = staticName + methodBTypeFromMethodType(staticMethodType, isConstructor = false) + val staticMethodInfo = MethodInlineInfo( + effectivelyFinal = true, + annotatedInline = info.annotatedInline, + annotatedNoInline = info.annotatedNoInline) + if (methodSym.isMixinConstructor) + List((staticMethodSignature, staticMethodInfo)) + else + List((signature, info), (staticMethodSignature, staticMethodInfo)) + } else + List((signature, info)) + } + }).toMap + + InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning) + } + + /** * For top-level objects without a companion class, the compiler generates a mirror class with * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a * ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes). diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index fd558587e9..7b640ac54f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -108,15 +108,15 @@ object BackendReporting { def emitWarning(settings: ScalaSettings): Boolean = this match { case ClassNotFound(_, javaDefined) => - if (javaDefined) settings.YoptWarningNoInlineMixed - else settings.YoptWarningNoInlineMissingBytecode + if (javaDefined) settings.optWarningNoInlineMixed + else settings.optWarningNoInlineMissingBytecode case m @ MethodNotFound(_, _, _, missing) => if (m.isArrayMethod) false - else settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + else settings.optWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings)) case FieldNotFound(_, _, _, missing) => - settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + settings.optWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings)) } } @@ -137,7 +137,7 @@ object BackendReporting { def emitWarning(settings: ScalaSettings): Boolean = this match { case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings) - case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.YoptWarningNoInlineMissingBytecode + case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.optWarningNoInlineMissingBytecode } } @@ -170,7 +170,7 @@ object BackendReporting { case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings) case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings) - case MethodInlineInfoMissing(_, _, _, None) => settings.YoptWarningNoInlineMissingBytecode + case MethodInlineInfoMissing(_, _, _, None) => settings.optWarningNoInlineMissingBytecode case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings) } @@ -216,7 +216,7 @@ object BackendReporting { def emitWarning(settings: ScalaSettings): Boolean = this match { case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod | _: StrictfpMismatch | _: ResultingMethodTooLarge => - settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed) + settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) case IllegalAccessCheckFailed(_, _, _, _, _, cause) => cause.emitWarning(settings) @@ -238,7 +238,7 @@ object BackendReporting { // but at the place where it's created (in findIllegalAccess) we don't have the necessary data (calleeName, calleeDescriptor). case object UnknownInvokeDynamicInstruction extends OptimizerWarning { override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)." - def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed) + def emitWarning(settings: ScalaSettings): Boolean = settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) } /** @@ -250,7 +250,7 @@ object BackendReporting { override def emitWarning(settings: ScalaSettings): Boolean = this match { case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings) - case RewriteClosureIllegalAccess(_, _) => settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed) + case RewriteClosureIllegalAccess(_, _) => settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) } override def toString: String = this match { @@ -282,10 +282,10 @@ object BackendReporting { } def emitWarning(settings: ScalaSettings): Boolean = this match { - case NoInlineInfoAttribute(_) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr + case NoInlineInfoAttribute(_) => settings.optWarningNoInlineMissingScalaInlineInfoAttr case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings) - case ClassSymbolInfoFailureSI9111(_) => settings.YoptWarningNoInlineMissingBytecode - case UnknownScalaInlineInfoVersion(_, _) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr + case ClassSymbolInfoFailureSI9111(_) => settings.optWarningNoInlineMissingBytecode + case UnknownScalaInlineInfoVersion(_, _) => settings.optWarningNoInlineMissingScalaInlineInfoAttr } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 1feca56923..d65380aa1f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -217,26 +217,6 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { nonOverloadedConstructors(tupleClassSymbols) } - // enumeration of specialized classes is temporary, while we still use the java-defined JFunctionN. - // once we switch to ordinary FunctionN, we can use specializedSubclasses just like for tuples. - private def specializedJFunctionSymbols(base: String): Seq[Symbol] = { - def primitives = Seq("B", "S", "I", "J", "C", "F", "D", "Z", "V") - def ijfd = Iterator("I", "J", "F", "D") - def ijfdzv = Iterator("I", "J", "F", "D", "Z", "V") - def ijd = Iterator("I", "J", "D") - val classNames = { - primitives.map(base + "0$mc" + _ + "$sp") // Function0 - } ++ { - // return type specializations appear first in the name string (alphabetical sorting) - for (r <- ijfdzv; a <- ijfd) yield base + "1$mc" + r + a + "$sp" // Function1 - } ++ { - for (r <- ijfdzv; a <- ijd; b <- ijd) yield base + "2$mc" + r + a + b + "$sp" // Function2 - } - classNames map getRequiredClass - } - - lazy val functionRefs: Set[InternalName] = (FunctionClass.seq ++ specializedJFunctionSymbols("scala.runtime.java8.JFunction")).map(classBTypeFromSymbol(_).internalName).toSet - lazy val typeOfArrayOp: Map[Int, BType] = { import scalaPrimitives._ Map( @@ -342,8 +322,6 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { def srRefConstructors : Map[InternalName, MethodNameAndType] def tupleClassConstructors : Map[InternalName, MethodNameAndType] - def functionRefs: Set[InternalName] - def lambdaMetaFactoryBootstrapHandle : asm.Handle def lambdaDeserializeBootstrapHandle : asm.Handle } @@ -410,8 +388,6 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: def srRefConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefConstructors def tupleClassConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.tupleClassConstructors - def functionRefs: Set[InternalName] = _coreBTypes.functionRefs - def srSymbolLiteral : ClassBType = _coreBTypes.srSymbolLiteral def srStructuralCallSite : ClassBType = _coreBTypes.srStructuralCallSite def srLambdaDeserialize : ClassBType = _coreBTypes.srLambdaDeserialize diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 3520d57599..584b11d4ed 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -77,15 +77,16 @@ abstract class GenBCode extends BCodeSyncAndTry { /* ---------------- q2 ---------------- */ - case class Item2(arrivalPos: Int, - mirror: asm.tree.ClassNode, - plain: asm.tree.ClassNode, - bean: asm.tree.ClassNode, - outFolder: scala.tools.nsc.io.AbstractFile) { + case class Item2(arrivalPos: Int, + mirror: asm.tree.ClassNode, + plain: asm.tree.ClassNode, + bean: asm.tree.ClassNode, + sourceFilePath: String, + outFolder: scala.tools.nsc.io.AbstractFile) { def isPoison = { arrivalPos == Int.MaxValue } } - private val poison2 = Item2(Int.MaxValue, null, null, null, null) + private val poison2 = Item2(Int.MaxValue, null, null, null, null, null) private val q2 = new _root_.java.util.LinkedList[Item2] /* ---------------- q3 ---------------- */ @@ -205,6 +206,7 @@ abstract class GenBCode extends BCodeSyncAndTry { val item2 = Item2(arrivalPos, mirrorC, plainC, beanC, + cunit.source.file.canonicalPath, outF) q2 add item2 // at the very end of this method so that no Worker2 thread starts mutating before we're done. @@ -225,19 +227,20 @@ abstract class GenBCode extends BCodeSyncAndTry { // add classes to the bytecode repo before building the call graph: the latter needs to // look up classes and methods in the code repo. - if (settings.YoptAddToBytecodeRepository) q2.asScala foreach { - case Item2(_, mirror, plain, bean, _) => - if (mirror != null) byteCodeRepository.add(mirror, ByteCodeRepository.CompilationUnit) - if (plain != null) byteCodeRepository.add(plain, ByteCodeRepository.CompilationUnit) - if (bean != null) byteCodeRepository.add(bean, ByteCodeRepository.CompilationUnit) + if (settings.optAddToBytecodeRepository) q2.asScala foreach { + case Item2(_, mirror, plain, bean, sourceFilePath, _) => + val someSourceFilePath = Some(sourceFilePath) + if (mirror != null) byteCodeRepository.add(mirror, someSourceFilePath) + if (plain != null) byteCodeRepository.add(plain, someSourceFilePath) + if (bean != null) byteCodeRepository.add(bean, someSourceFilePath) } - if (settings.YoptBuildCallGraph) q2.asScala foreach { item => + if (settings.optBuildCallGraph) q2.asScala foreach { item => // skip call graph for mirror / bean: wd don't inline into tem, and they are not used in the plain class if (item.plain != null) callGraph.addClass(item.plain) } - if (settings.YoptInlinerEnabled) + if (settings.optInlinerEnabled) bTypes.inliner.runInliner() - if (settings.YoptClosureInvocations) + if (settings.optClosureInvocations) closureOptimizer.rewriteClosureApplyInvocations() } @@ -286,7 +289,7 @@ abstract class GenBCode extends BCodeSyncAndTry { cw.toByteArray } - val Item2(arrivalPos, mirror, plain, bean, outFolder) = item + val Item2(arrivalPos, mirror, plain, bean, _, outFolder) = item val mirrorC = if (mirror == null) null else SubItem3(mirror.name, getByteArray(mirror)) val plainC = SubItem3(plain.name, getByteArray(plain)) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala index f94642389d..83615abc31 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -2,17 +2,18 @@ package scala.tools.nsc package backend.jvm package analysis +import java.lang.invoke.LambdaMetafactory + import scala.annotation.switch -import scala.tools.asm.{Handle, Type} +import scala.collection.JavaConverters._ +import scala.collection.mutable import scala.tools.asm.Opcodes._ import scala.tools.asm.tree._ -import scala.tools.asm.tree.analysis.{Frame, BasicInterpreter, Analyzer, Value} -import GenBCode._ +import scala.tools.asm.tree.analysis._ +import scala.tools.asm.{Handle, Type} import scala.tools.nsc.backend.jvm.BTypes._ +import scala.tools.nsc.backend.jvm.GenBCode._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ -import java.lang.invoke.LambdaMetafactory -import scala.collection.mutable -import scala.collection.JavaConverters._ /** * This component hosts tools and utilities used in the backend that require access to a `BTypes` @@ -32,7 +33,12 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { */ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) { computeMaxLocalsMaxStack(methodNode) - analyzer.analyze(classInternalName, methodNode) + try { + analyzer.analyze(classInternalName, methodNode) + } catch { + case ae: AnalyzerException => + throw new AnalyzerException(null, "While processing " + classInternalName + "." + methodNode.name, ae) + } def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) } @@ -98,7 +104,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { * a boolean indicating if the instruction list contains an instantiation of a serializable SAM * type. */ - def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], Boolean) = { + def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], keepLineNumbers: Boolean): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], Boolean) = { val javaLabelMap = labelMap.asJava val result = new InsnList var map = Map.empty[AbstractInsnNode, AbstractInsnNode] @@ -112,18 +118,19 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { } case _ => } - val cloned = ins.clone(javaLabelMap) - result add cloned - map += ((ins, cloned)) + if (keepLineNumbers || !ins.isInstanceOf[LineNumberNode]) { + val cloned = ins.clone(javaLabelMap) + result add cloned + map += ((ins, cloned)) + } } (result, map, hasSerializableClosureInstantiation) } def getBoxedUnit: FieldInsnNode = new FieldInsnNode(GETSTATIC, srBoxedUnitRef.internalName, "UNIT", srBoxedUnitRef.descriptor) - private val anonfunAdaptedName = """.*\$anonfun\$\d+\$adapted""".r + private val anonfunAdaptedName = """.*\$anonfun\$.*\$\d+\$adapted""".r def hasAdaptedImplMethod(closureInit: ClosureInstantiation): Boolean = { - isBuiltinFunctionType(Type.getReturnType(closureInit.lambdaMetaFactoryCall.indy.desc).getInternalName) && anonfunAdaptedName.pattern.matcher(closureInit.lambdaMetaFactoryCall.implMethod.getName).matches } @@ -248,8 +255,6 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { } } - def isBuiltinFunctionType(internalName: InternalName): Boolean = functionRefs(internalName) - /** * Visit the class node and collect all referenced nested classes. */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala index 30e73f8ac2..01afd0d2ef 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala @@ -5,8 +5,8 @@ package analysis import java.util import scala.annotation.switch -import scala.tools.asm.{Type, Opcodes} -import scala.tools.asm.tree.{MethodInsnNode, LdcInsnNode, AbstractInsnNode} +import scala.tools.asm.{Opcodes, Type} +import scala.tools.asm.tree.{AbstractInsnNode, LdcInsnNode, MethodInsnNode, MethodNode} import scala.tools.asm.tree.analysis._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils import BytecodeUtils._ @@ -63,7 +63,7 @@ object NullnessValue { def unknown(insn: AbstractInsnNode) = if (BytecodeUtils.instructionResultSize(insn) == 2) UnknownValue2 else UnknownValue1 } -final class NullnessInterpreter(bTypes: BTypes) extends Interpreter[NullnessValue](Opcodes.ASM5) { +final class NullnessInterpreter(bTypes: BTypes, method: MethodNode) extends Interpreter[NullnessValue](Opcodes.ASM5) { def newValue(tp: Type): NullnessValue = { // ASM loves giving semantics to null. The behavior here is the same as in SourceInterpreter, // which is provided by the framework. @@ -80,7 +80,13 @@ final class NullnessInterpreter(bTypes: BTypes) extends Interpreter[NullnessValu override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): NullnessValue = { // For instance methods, the `this` parameter is known to be not null. - if (isInstanceMethod && local == 0) NotNullValue + val isThis = local == 0 && (isInstanceMethod || { + method.parameters != null && !method.parameters.isEmpty && { + val p = method.parameters.get(0) + (p.access & Opcodes.ACC_SYNTHETIC) != 0 && p.name == "$this" + } + }) + if (isThis) NotNullValue else super.newParameterValue(isInstanceMethod, local, tp) } @@ -197,7 +203,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal * This class is required to override the `newFrame` methods, which makes makes sure the analyzer * uses NullnessFrames. */ -class NullnessAnalyzer(bTypes: BTypes) extends Analyzer[NullnessValue](new NullnessInterpreter(bTypes)) { +class NullnessAnalyzer(bTypes: BTypes, method: MethodNode) extends Analyzer[NullnessValue](new NullnessInterpreter(bTypes, method)) { override def newFrame(nLocals: Int, nStack: Int): NullnessFrame = new NullnessFrame(nLocals, nStack) override def newFrame(src: Frame[_ <: NullnessValue]): NullnessFrame = new NullnessFrame(src) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index 16590ec75c..78acd72dba 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -15,7 +15,6 @@ import scala.tools.asm.Attribute import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.util.ClassPath import BytecodeUtils._ -import ByteCodeRepository._ import BTypes.InternalName import java.util.concurrent.atomic.AtomicLong @@ -29,9 +28,10 @@ class ByteCodeRepository[BT <: BTypes](val classPath: ClassPath, val btypes: BT) import btypes._ /** - * ClassNodes for classes being compiled in the current compilation run. + * Contains ClassNodes and the canonical path of the source file path of classes being compiled in + * the current compilation run. */ - val compilingClasses: concurrent.Map[InternalName, ClassNode] = recordPerRunCache(concurrent.TrieMap.empty) + val compilingClasses: concurrent.Map[InternalName, (ClassNode, String)] = recordPerRunCache(concurrent.TrieMap.empty) /** * Cache for parsed ClassNodes. @@ -67,20 +67,35 @@ class ByteCodeRepository[BT <: BTypes](val classPath: ClassPath, val btypes: BT) } } - def add(classNode: ClassNode, source: Source) = { - if (source == CompilationUnit) compilingClasses(classNode.name) = classNode - else parsedClasses(classNode.name) = Right((classNode, lruCounter.incrementAndGet())) + def add(classNode: ClassNode, sourceFilePath: Option[String]) = sourceFilePath match { + case Some(path) if path != "<no file>" => compilingClasses(classNode.name) = (classNode, path) + case _ => parsedClasses(classNode.name) = Right((classNode, lruCounter.incrementAndGet())) + } + + private def parsedClassNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = { + val r = parsedClasses.get(internalName) match { + case Some(l @ Left(_)) => l + case Some(r @ Right((classNode, _))) => + parsedClasses(internalName) = Right((classNode, lruCounter.incrementAndGet())) + r + case None => + limitCacheSize() + val res = parseClass(internalName).map((_, lruCounter.incrementAndGet())) + parsedClasses(internalName) = res + res + } + r.map(_._1) } /** - * The class node and source for an internal name. If the class node is not yet available, it is - * parsed from the classfile on the compile classpath. + * The class node and source file path (if the class is being compiled) for an internal name. If + * the class node is not yet available, it is parsed from the classfile on the compile classpath. */ - def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = { - classNode(internalName) map (n => { - val source = if (compilingClasses contains internalName) CompilationUnit else Classfile - (n, source) - }) + def classNodeAndSourceFilePath(internalName: InternalName): Either[ClassNotFound, (ClassNode, Option[String])] = { + compilingClasses.get(internalName) match { + case Some((c, p)) => Right((c, Some(p))) + case _ => parsedClassNode(internalName).map((_, None)) + } } /** @@ -88,19 +103,9 @@ class ByteCodeRepository[BT <: BTypes](val classPath: ClassPath, val btypes: BT) * the classfile on the compile classpath. */ def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = { - compilingClasses.get(internalName).map(Right(_)) getOrElse { - val r = parsedClasses.get(internalName) match { - case Some(l @ Left(_)) => l - case Some(r @ Right((classNode, _))) => - parsedClasses(internalName) = Right((classNode, lruCounter.incrementAndGet())) - r - case None => - limitCacheSize() - val res = parseClass(internalName).map((_, lruCounter.incrementAndGet())) - parsedClasses(internalName) = res - res - } - r.map(_._1) + compilingClasses.get(internalName) match { + case Some((c, _)) => Right(c) + case None => parsedClassNode(internalName) } } @@ -289,13 +294,3 @@ class ByteCodeRepository[BT <: BTypes](val classPath: ClassPath, val btypes: BT) } } } - -object ByteCodeRepository { - /** - * The source of a ClassNode in the ByteCodeRepository. Can be either [[CompilationUnit]] if the - * class is being compiled or [[Classfile]] if the class was parsed from the compilation classpath. - */ - sealed trait Source - object CompilationUnit extends Source - object Classfile extends Source -} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 63906d80e5..e21c46dbe9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -93,6 +93,15 @@ object BytecodeUtils { op == INVOKESPECIAL || op == INVOKESTATIC } + def isVirtualCall(instruction: AbstractInsnNode): Boolean = { + val op = instruction.getOpcode + op == INVOKEVIRTUAL || op == INVOKEINTERFACE + } + + def isCall(instruction: AbstractInsnNode): Boolean = { + isNonVirtualCall(instruction) || isVirtualCall(instruction) + } + def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0 def isConstructor(methodNode: MethodNode): Boolean = { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index d241acf7b1..5248183337 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -9,14 +9,13 @@ package opt import scala.collection.immutable.IntMap import scala.reflect.internal.util.{NoPosition, Position} -import scala.tools.asm.{Opcodes, Type, Handle} +import scala.tools.asm.{Handle, Opcodes, Type} import scala.tools.asm.tree._ import scala.collection.{concurrent, mutable} import scala.collection.JavaConverters._ -import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo} import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.backend.jvm.analysis._ -import ByteCodeRepository.{Source, CompilationUnit} import BytecodeUtils._ class CallGraph[BT <: BTypes](val btypes: BT) { @@ -68,6 +67,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { } def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction + def findCallSite(method: MethodNode, call: MethodInsnNode): Option[Callsite] = callsites.getOrElse(method, Map.empty).get(call) def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = { val methodClosureInits = closureInstantiations(methodNode) @@ -102,8 +102,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // It is also used to get the stack height at the call site. val analyzer = { - if (compilerSettings.YoptNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) { - Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer(btypes))) + if (compilerSettings.optNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) { + Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer(btypes, methodNode))) } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) { Some(new AsmAnalyzer(methodNode, definingClass.internalName)) } else None @@ -128,17 +128,17 @@ class CallGraph[BT <: BTypes](val btypes: BT) { methodNode.instructions.iterator.asScala foreach { case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code val callee: Either[OptimizerWarning, Callee] = for { - (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] - (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] + (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + (declarationClassNode, calleeSourceFilePath) <- byteCodeRepository.classNodeAndSourceFilePath(declarationClass): Either[OptimizerWarning, (ClassNode, Option[String])] } yield { val declarationClassBType = classBTypeFromClassNode(declarationClassNode) - val info = analyzeCallsite(method, declarationClassBType, call, source) + val info = analyzeCallsite(method, declarationClassBType, call, calleeSourceFilePath) import info._ Callee( callee = method, calleeDeclarationClass = declarationClassBType, safeToInline = safeToInline, - canInlineFromSource = canInlineFromSource, + sourceFilePath = sourceFilePath, annotatedInline = annotatedInline, annotatedNoInline = annotatedNoInline, samParamTypes = info.samParamTypes, @@ -256,7 +256,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { /** * Just a named tuple used as return type of `analyzeCallsite`. */ - private case class CallsiteInfo(safeToInline: Boolean, canInlineFromSource: Boolean, + private case class CallsiteInfo(safeToInline: Boolean, sourceFilePath: Option[String], annotatedInline: Boolean, annotatedNoInline: Boolean, samParamTypes: IntMap[ClassBType], warning: Option[CalleeInfoWarning]) @@ -264,7 +264,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { /** * Analyze a callsite and gather meta-data that can be used for inlining decisions. */ - private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSource: Source): CallsiteInfo = { + private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSourceFilePath: Option[String]): CallsiteInfo = { val methodSignature = calleeMethodNode.name + calleeMethodNode.desc try { @@ -273,8 +273,6 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // callee, we only check there for the methodInlineInfo, we should find it there. calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { case Some(methodInlineInfo) => - val canInlineFromSource = compilerSettings.YoptInlineGlobal || calleeSource == CompilationUnit - val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode) val receiverType = classBTypeFromParsedClassfile(call.owner) @@ -308,13 +306,13 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // static impl method first (safeToRewrite). CallsiteInfo( safeToInline = - canInlineFromSource && + inlinerHeuristics.canInlineFromSource(calleeSourceFilePath) && isStaticallyResolved && // (1) !isAbstract && !BytecodeUtils.isConstructor(calleeMethodNode) && !BytecodeUtils.isNativeMethod(calleeMethodNode) && !BytecodeUtils.hasCallerSensitiveAnnotation(calleeMethodNode), - canInlineFromSource = canInlineFromSource, + sourceFilePath = calleeSourceFilePath, annotatedInline = methodInlineInfo.annotatedInline, annotatedNoInline = methodInlineInfo.annotatedNoInline, samParamTypes = samParamTypes(calleeMethodNode, receiverType), @@ -322,12 +320,12 @@ class CallGraph[BT <: BTypes](val btypes: BT) { case None => val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning) - CallsiteInfo(false, false, false, false, IntMap.empty, Some(warning)) + CallsiteInfo(false, None, false, false, IntMap.empty, Some(warning)) } } catch { case Invalid(noInfo: NoClassBTypeInfo) => val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo) - CallsiteInfo(false, false, false, false, IntMap.empty, Some(warning)) + CallsiteInfo(false, None, false, false, IntMap.empty, Some(warning)) } } @@ -359,7 +357,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { "Invocation of" + s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" + s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" + - s" in ${callsiteClass.internalName}.${callsiteMethod.name}" + s" in ${callsiteClass.internalName}.${callsiteMethod.name}${callsiteMethod.desc}" } final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite) @@ -389,7 +387,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * gathering the information about this callee. */ final case class Callee(callee: MethodNode, calleeDeclarationClass: btypes.ClassBType, - safeToInline: Boolean, canInlineFromSource: Boolean, + safeToInline: Boolean, sourceFilePath: Option[String], annotatedInline: Boolean, annotatedNoInline: Boolean, samParamTypes: IntMap[btypes.ClassBType], calleeInfoWarning: Option[CalleeInfoWarning]) { @@ -432,7 +430,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { def unapply(insn: AbstractInsnNode): Option[(InvokeDynamicInsnNode, Type, Handle, Type)] = insn match { case indy: InvokeDynamicInsnNode if indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle => indy.bsmArgs match { - case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) => // xs binding because IntelliJ gets confused about _@_* + case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, _@_*) => // LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda // implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc). // diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index 93dc40f318..081830d61d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -17,7 +17,6 @@ import scala.tools.nsc.backend.jvm.BTypes.InternalName import BytecodeUtils._ import BackendReporting._ import Opcodes._ -import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.CompilationUnit import scala.collection.JavaConverters._ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { @@ -354,16 +353,15 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // the method node is needed for building the call graph entry val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc) - def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false) + val sourceFilePath = byteCodeRepository.compilingClasses.get(lambdaBodyHandle.getOwner).map(_._2) val callee = bodyMethod.map({ case (bodyMethodNode, bodyMethodDeclClass) => val bodyDeclClassType = classBTypeFromParsedClassfile(bodyMethodDeclClass) - val canInlineFromSource = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled Callee( callee = bodyMethodNode, calleeDeclarationClass = bodyDeclClassType, - safeToInline = canInlineFromSource, - canInlineFromSource = canInlineFromSource, + safeToInline = inlinerHeuristics.canInlineFromSource(sourceFilePath), + sourceFilePath = sourceFilePath, annotatedInline = false, annotatedNoInline = false, samParamTypes = callGraph.samParamTypes(bodyMethodNode, bodyDeclClassType), diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala index 4163d62df7..b05669ce89 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala @@ -295,18 +295,12 @@ class CopyProp[BT <: BTypes](val btypes: BT) { } /** - * Eliminate the closure value produced by `indy`. If the SAM type is known to construct - * without side-effects (e.g. scala/FunctionN), the `indy` and its inputs - * are eliminated, otherwise a POP is inserted. + * Eliminate LMF `indy` and its inputs. */ def handleClosureInst(indy: InvokeDynamicInsnNode): Unit = { - if (isBuiltinFunctionType(Type.getReturnType(indy.desc).getInternalName)) { - toRemove += indy - callGraph.removeClosureInstantiation(indy, method) - handleInputs(indy, Type.getArgumentTypes(indy.desc).length) - } else { - toInsertAfter(indy) = getPop(1) - } + toRemove += indy + callGraph.removeClosureInstantiation(indy, method) + handleInputs(indy, Type.getArgumentTypes(indy.desc).length) } def runQueue(): Unit = while (queue.nonEmpty) { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala index 79d26b0b4e..5ce7072c60 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala @@ -51,7 +51,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI if (inlineInfo.isEffectivelyFinal) flags |= 1 // flags |= 2 // no longer written if (inlineInfo.sam.isDefined) flags |= 4 - if (inlineInfo.lateInterfaces.nonEmpty) flags |= 8 result.putByte(flags) for (samNameDesc <- inlineInfo.sam) { @@ -79,9 +78,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI result.putByte(inlineInfo) } - result.putShort(inlineInfo.lateInterfaces.length) - for (i <- inlineInfo.lateInterfaces) result.putShort(cw.newUTF8(i)) - result } @@ -105,7 +101,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI val isFinal = (flags & 1) != 0 val hasSelf = (flags & 2) != 0 val hasSam = (flags & 4) != 0 - val hasLateInterfaces = (flags & 8) != 0 if (hasSelf) nextUTF8() // no longer used @@ -128,13 +123,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI (name + desc, MethodInlineInfo(isFinal, isInline, isNoInline)) }).toMap - val lateInterfaces = if (!hasLateInterfaces) Nil else { - val numLateInterfaces = nextShort() - (0 until numLateInterfaces).map(_ => nextUTF8()) - } - val info = InlineInfo(isFinal, sam, infos, None) - info.lateInterfaces ++= lateInterfaces InlineInfoAttribute(info) } else { val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version) @@ -161,8 +150,6 @@ object InlineInfoAttribute { * [u2] name (reference) * [u2] descriptor (reference) * [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3) - * [u2]? numLateInterfaces - * [u2] lateInterface (reference) */ final val VERSION: Byte = 1 diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index f35eaa45e9..9c5a1a9f98 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -25,6 +25,9 @@ class Inliner[BT <: BTypes](val btypes: BT) { import inlinerHeuristics._ import backendUtils._ + case class InlineLog(request: InlineRequest, sizeBefore: Int, sizeAfter: Int, sizeInlined: Int, warning: Option[CannotInlineWarning]) + var inlineLog: List[InlineLog] = Nil + def runInliner(): Unit = { for (request <- collectAndOrderInlineRequests) { val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee @@ -35,13 +38,36 @@ class Inliner[BT <: BTypes](val btypes: BT) { val warnings = inline(request) for (warning <- warnings) { - if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { + if ((callee.annotatedInline && btypes.compilerSettings.optWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" backendReporting.inlinerWarning(request.callsite.callsitePosition, msg) } } } + + if (compilerSettings.YoptLogInline.isSetByUser) { + val methodPrefix = { val p = compilerSettings.YoptLogInline.value; if (p == "_") "" else p } + val byCallsiteMethod = inlineLog.groupBy(_.request.callsite.callsiteMethod).toList.sortBy(_._2.head.request.callsite.callsiteClass.internalName) + for ((m, mLogs) <- byCallsiteMethod) { + val initialSize = mLogs.minBy(_.sizeBefore).sizeBefore + val firstLog = mLogs.head + val methodName = s"${firstLog.request.callsite.callsiteClass.internalName}.${m.name}" + if (methodName.startsWith(methodPrefix)) { + println(s"Inlining into $methodName (initially $initialSize instructions, ultimately ${m.instructions.size}):") + val byCallee = mLogs.groupBy(_.request.callsite.callee.get).toList.sortBy(_._2.length).reverse + for ((c, cLogs) <- byCallee) { + val first = cLogs.head + if (first.warning.isEmpty) { + val num = if (cLogs.tail.isEmpty) "" else s" ${cLogs.length} times" + println(s" - Inlined ${c.calleeDeclarationClass.internalName}.${c.callee.name} (${first.sizeInlined} instructions)$num: ${first.request.reason}") + } else + println(s" - Failed to inline ${c.calleeDeclarationClass.internalName}.${c.callee.name} (${first.request.reason}): ${first.warning.get}") + } + println() + } + } + } } /** @@ -80,6 +106,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { val elided = mutable.Set.empty[InlineRequest] def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided + def allCallees(r: InlineRequest): Set[MethodNode] = r.post.flatMap(allCallees).toSet + r.callsite.callee.get.callee + /** * Break cycles in the inline request graph by removing callsites. * @@ -88,20 +116,20 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def breakInlineCycles: List[InlineRequest] = { // is there a path of inline requests from start to goal? - def isReachable(start: MethodNode, goal: MethodNode): Boolean = { - @tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match { - case x :: xs => + def isReachable(start: Set[MethodNode], goal: MethodNode): Boolean = { + @tailrec def reachableImpl(check: Set[MethodNode], visited: Set[MethodNode]): Boolean = { + if (check.isEmpty) false + else { + val x = check.head if (x == goal) true - else if (visited(x)) reachableImpl(xs, visited) + else if (visited(x)) reachableImpl(check - x, visited) else { - val callees = nonElidedRequests(x).map(_.callsite.callee.get.callee) - reachableImpl(xs ::: callees.toList, visited + x) + val callees = nonElidedRequests(x).flatMap(allCallees) + reachableImpl(check - x ++ callees, visited + x) } - - case Nil => - false + } } - reachableImpl(List(start), Set.empty) + reachableImpl(start, Set.empty) } val result = new mutable.ListBuffer[InlineRequest]() @@ -110,7 +138,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { java.util.Arrays.sort(requests, callsiteOrdering) for (r <- requests) { // is there a chain of inlining requests that would inline the callsite method into the callee? - if (isReachable(r.callsite.callee.get.callee, r.callsite.callsiteMethod)) + if (isReachable(allCallees(r), r.callsite.callsiteMethod)) elided += r else result += r @@ -124,8 +152,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (requests.isEmpty) Nil else { val (leaves, others) = requests.partition(r => { - val inlineRequestsForCallee = nonElidedRequests(r.callsite.callee.get.callee) - inlineRequestsForCallee.forall(visited) + val inlineRequestsForCallees = allCallees(r).flatMap(nonElidedRequests) + inlineRequestsForCallees.forall(visited) }) assert(leaves.nonEmpty, requests) leaves ::: leavesFirst(others, visited ++ leaves) @@ -184,7 +212,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { def impl(post: InlineRequest, at: Callsite): List[InlineRequest] = { post.callsite.inlinedClones.find(_.clonedWhenInlining == at) match { case Some(clonedCallsite) => - List(InlineRequest(clonedCallsite.callsite, post.post)) + List(InlineRequest(clonedCallsite.callsite, post.post, post.reason)) case None => post.post.flatMap(impl(_, post.callsite)).flatMap(impl(_, at)) } @@ -199,9 +227,17 @@ class Inliner[BT <: BTypes](val btypes: BT) { * @return An inliner warning for each callsite that could not be inlined. */ def inline(request: InlineRequest): List[CannotInlineWarning] = canInlineBody(request.callsite) match { - case Some(w) => List(w) + case Some(w) => + if (compilerSettings.YoptLogInline.isSetByUser) { + val size = request.callsite.callsiteMethod.instructions.size + inlineLog ::= InlineLog(request, size, size, 0, Some(w)) + } + List(w) case None => + val sizeBefore = request.callsite.callsiteMethod.instructions.size inlineCallsite(request.callsite) + if (compilerSettings.YoptLogInline.isSetByUser) + inlineLog ::= InlineLog(request, sizeBefore, request.callsite.callsiteMethod.instructions.size, request.callsite.callee.get.callee.instructions.size, None) val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite)) postRequests flatMap inline } @@ -219,7 +255,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { def inlineCallsite(callsite: Callsite): Unit = { import callsite.{callsiteClass, callsiteMethod, callsiteInstruction, receiverKnownNotNull, callsiteStackHeight} val Right(callsiteCallee) = callsite.callee - import callsiteCallee.{callee, calleeDeclarationClass} + import callsiteCallee.{callee, calleeDeclarationClass, sourceFilePath} // Inlining requires the callee not to have unreachable code, the analyzer used below should not // return any `null` frames. Note that inlining a method can create unreachable code. Example: @@ -234,11 +270,14 @@ class Inliner[BT <: BTypes](val btypes: BT) { // New labels for the cloned instructions val labelsMap = cloneLabels(callee) - val (clonedInstructions, instructionMap, hasSerializableClosureInstantiation) = cloneInstructions(callee, labelsMap) - val keepLineNumbers = callsiteClass == calleeDeclarationClass - if (!keepLineNumbers) { - removeLineNumberNodes(clonedInstructions) + val sameSourceFile = sourceFilePath match { + case Some(calleeSource) => byteCodeRepository.compilingClasses.get(callsiteClass.internalName) match { + case Some((_, `calleeSource`)) => true + case _ => false + } + case _ => false } + val (clonedInstructions, instructionMap, hasSerializableClosureInstantiation) = cloneInstructions(callee, labelsMap, keepLineNumbers = sameSourceFile) // local vars in the callee are shifted by the number of locals at the callsite val localVarShift = callsiteMethod.maxLocals diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala index 6aaf9734d3..79e74f3eb7 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -7,21 +7,23 @@ package scala.tools.nsc package backend.jvm package opt -import scala.tools.asm.tree.MethodNode -import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.collection.JavaConverters._ +import scala.tools.asm.Opcodes +import scala.tools.asm.tree.{MethodInsnNode, MethodNode} +import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { import bTypes._ - import inliner._ import callGraph._ - case class InlineRequest(callsite: Callsite, post: List[InlineRequest]) { + case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) { // invariant: all post inline requests denote callsites in the callee of the main callsite for (pr <- post) assert(pr.callsite.callsiteMethod == callsite.callee.get.callee, s"Callsite method mismatch: main $callsite - post ${pr.callsite}") } + def canInlineFromSource(sourceFilePath: Option[String]) = compilerSettings.optInlineGlobal || sourceFilePath.isDefined + /** * Select callsites from the call graph that should be inlined, grouped by the containing method. * Cyclic inlining requests are allowed, the inliner will eliminate requests to break cycles. @@ -32,27 +34,27 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { // classpath. In order to get only the callsites being compiled, we start at the map of // compilingClasses in the byteCodeRepository. val compilingMethods = for { - classNode <- byteCodeRepository.compilingClasses.valuesIterator - methodNode <- classNode.methods.iterator.asScala + (classNode, _) <- byteCodeRepository.compilingClasses.valuesIterator + methodNode <- classNode.methods.iterator.asScala } yield methodNode compilingMethods.map(methodNode => { var requests = Set.empty[InlineRequest] callGraph.callsites(methodNode).valuesIterator foreach { - case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, canInlineFromSource, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) => - inlineRequest(callsite) match { + case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, sourceFilePath, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) => + inlineRequest(callsite, requests) match { case Some(Right(req)) => requests += req case Some(Left(w)) => - if ((calleeAnnotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) { + if ((calleeAnnotatedInline && bTypes.compilerSettings.optWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) { val annotWarn = if (calleeAnnotatedInline) " is annotated @inline but" else "" val msg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)}$annotWarn could not be inlined:\n$w" backendReporting.inlinerWarning(callsite.callsitePosition, msg) } case None => - if (canInlineFromSource && calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) { + if (canInlineFromSource(sourceFilePath) && calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.optWarningEmitAtInlineFailed) { // if the callsite is annotated @inline, we report an inline warning even if the underlying - // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). + // reason is, for example, mixed compilation (which has a separate -opt-warning flag). def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" def warnMsg = callsiteWarning.map(" Possible reason:\n" + _).getOrElse("") if (!safeToInline) @@ -87,20 +89,49 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { * InlineRequest for the original callsite? new subclass of OptimizerWarning. * `Some(Right)` if the callsite should be and can be inlined */ - def inlineRequest(callsite: Callsite): Option[Either[OptimizerWarning, InlineRequest]] = { + def inlineRequest(callsite: Callsite, selectedRequestsForCallee: Set[InlineRequest]): Option[Either[OptimizerWarning, InlineRequest]] = { val callee = callsite.callee.get - def requestIfCanInline(callsite: Callsite): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match { + def requestIfCanInline(callsite: Callsite, reason: String): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match { case Some(w) => Left(w) - case None => Right(InlineRequest(callsite, Nil)) + case None => + val callee = callsite.callee.get + val postInlineRequest: List[InlineRequest] = callee.calleeDeclarationClass.isInterface match { + case Right(true) => + // Treat the pair of trait interface method and static method as one for the purposes of inlining: + // if we inline invokeinterface, invoke the invokestatic, too. + val calls = callee.callee.instructions.iterator().asScala.filter(BytecodeUtils.isCall).take(2).toList + calls match { + case List(x: MethodInsnNode) if x.getOpcode == Opcodes.INVOKESTATIC && x.name == (callee.callee.name + "$") => + callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass) + val maybeNodeToCallsite1 = callGraph.findCallSite(callee.callee, x) + maybeNodeToCallsite1.toList.flatMap(x => requestIfCanInline(x, reason).right.toOption) + case _ => + Nil + + } + case _ => Nil + } + + Right(InlineRequest(callsite, postInlineRequest, reason)) + } compilerSettings.YoptInlineHeuristics.value match { case "everything" => - if (callee.safeToInline) Some(requestIfCanInline(callsite)) + if (callee.safeToInline) { + val reason = if (compilerSettings.YoptLogInline.isSetByUser) "the inline strategy is \"everything\"" else null + Some(requestIfCanInline(callsite, reason)) + } else None case "at-inline-annotated" => - if (callee.safeToInline && callee.annotatedInline) Some(requestIfCanInline(callsite)) + if (callee.safeToInline && callee.annotatedInline) { + val reason = if (compilerSettings.YoptLogInline.isSetByUser) { + val what = if (callee.safeToInline) "callee" else "callsite" + s"the $what is annotated `@inline`" + } else null + Some(requestIfCanInline(callsite, reason)) + } else None case "default" => @@ -108,7 +139,30 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { case (index, _) => callsite.argInfos.contains(index) }) - if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) Some(requestIfCanInline(callsite)) + if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) { + val reason = if (compilerSettings.YoptLogInline.isSetByUser) { + if (callee.annotatedInline || callsite.annotatedInline) { + val what = if (callee.safeToInline) "callee" else "callsite" + s"the $what is annotated `@inline`" + } else { + val paramNames = Option(callee.callee.parameters).map(_.asScala.map(_.name).toVector) + def param(i: Int) = { + def syn = s"<param $i>" + paramNames.fold(syn)(v => v.applyOrElse(i, (_: Int) => syn)) + } + def samInfo(i: Int, sam: String, arg: String) = s"the argument for parameter (${param(i)}: $sam) is a $arg" + val argInfos = for ((i, sam) <- callee.samParamTypes; info <- callsite.argInfos.get(i)) yield { + val argKind = info match { + case FunctionLiteral => "function literal" + case ForwardedParam(_) => "parameter of the callsite method" + } + samInfo(i, sam.internalName.split('/').last, argKind) + } + s"the callee is a higher-order method, ${argInfos.mkString(", ")}" + } + } else null + Some(requestIfCanInline(callsite, reason)) + } else None } else None } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index 4e1349257e..447ee209b5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -191,7 +191,7 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { * @return `true` if unreachable code was eliminated in some method, `false` otherwise. */ def methodOptimizations(clazz: ClassNode): Boolean = { - !compilerSettings.YoptNone && clazz.methods.asScala.foldLeft(false) { + !compilerSettings.optNone && clazz.methods.asScala.foldLeft(false) { case (changed, method) => methodOptimizations(method, clazz.name) || changed } } @@ -231,7 +231,8 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { // for local variables in dead blocks. Maybe that's a bug in the ASM framework. var currentTrace: String = null - val doTrace = compilerSettings.YoptTrace.isSetByUser && compilerSettings.YoptTrace.value == ownerClassName + "." + method.name + val methodPrefix = {val p = compilerSettings.YoptTrace.value; if (p == "_") "" else p } + val doTrace = compilerSettings.YoptTrace.isSetByUser && s"$ownerClassName.${method.name}".startsWith(methodPrefix) def traceIfChanged(optName: String): Unit = if (doTrace) { val after = AsmUtils.textify(method) if (currentTrace != after) { @@ -261,46 +262,46 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { traceIfChanged("beforeMethodOpt") // NULLNESS OPTIMIZATIONS - val runNullness = compilerSettings.YoptNullnessTracking && requestNullness + val runNullness = compilerSettings.optNullnessTracking && requestNullness val nullnessOptChanged = runNullness && nullnessOptimizations(method, ownerClassName) traceIfChanged("nullness") // UNREACHABLE CODE // Both AliasingAnalyzer (used in copyProp) and ProdConsAnalyzer (used in eliminateStaleStores, // boxUnboxElimination) require not having unreachable instructions (null frames). - val runDCE = (compilerSettings.YoptUnreachableCode && (requestDCE || nullnessOptChanged)) || - compilerSettings.YoptBoxUnbox || - compilerSettings.YoptCopyPropagation + val runDCE = (compilerSettings.optUnreachableCode && (requestDCE || nullnessOptChanged)) || + compilerSettings.optBoxUnbox || + compilerSettings.optCopyPropagation val (codeRemoved, liveLabels) = if (runDCE) removeUnreachableCodeImpl(method, ownerClassName) else (false, Set.empty[LabelNode]) traceIfChanged("dce") // BOX-UNBOX - val runBoxUnbox = compilerSettings.YoptBoxUnbox && (requestBoxUnbox || nullnessOptChanged) + val runBoxUnbox = compilerSettings.optBoxUnbox && (requestBoxUnbox || nullnessOptChanged) val boxUnboxChanged = runBoxUnbox && boxUnboxElimination(method, ownerClassName) traceIfChanged("boxUnbox") // COPY PROPAGATION - val runCopyProp = compilerSettings.YoptCopyPropagation && (firstIteration || boxUnboxChanged) + val runCopyProp = compilerSettings.optCopyPropagation && (firstIteration || boxUnboxChanged) val copyPropChanged = runCopyProp && copyPropagation(method, ownerClassName) traceIfChanged("copyProp") // STALE STORES - val runStaleStores = compilerSettings.YoptCopyPropagation && (requestStaleStores || nullnessOptChanged || codeRemoved || boxUnboxChanged || copyPropChanged) + val runStaleStores = compilerSettings.optCopyPropagation && (requestStaleStores || nullnessOptChanged || codeRemoved || boxUnboxChanged || copyPropChanged) val storesRemoved = runStaleStores && eliminateStaleStores(method, ownerClassName) traceIfChanged("staleStores") // REDUNDANT CASTS - val runRedundantCasts = compilerSettings.YoptRedundantCasts && (firstIteration || boxUnboxChanged) + val runRedundantCasts = compilerSettings.optRedundantCasts && (firstIteration || boxUnboxChanged) val castRemoved = runRedundantCasts && eliminateRedundantCasts(method, ownerClassName) traceIfChanged("redundantCasts") // PUSH-POP - val runPushPop = compilerSettings.YoptCopyPropagation && (requestPushPop || firstIteration || storesRemoved || castRemoved) + val runPushPop = compilerSettings.optCopyPropagation && (requestPushPop || firstIteration || storesRemoved || castRemoved) val pushPopRemoved = runPushPop && eliminatePushPop(method, ownerClassName) traceIfChanged("pushPop") // STORE-LOAD PAIRS - val runStoreLoad = compilerSettings.YoptCopyPropagation && (requestStoreLoad || boxUnboxChanged || copyPropChanged || pushPopRemoved) + val runStoreLoad = compilerSettings.optCopyPropagation && (requestStoreLoad || boxUnboxChanged || copyPropChanged || pushPopRemoved) val storeLoadRemoved = runStoreLoad && eliminateStoreLoad(method) traceIfChanged("storeLoadPairs") @@ -312,7 +313,7 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { // SIMPLIFY JUMPS // almost all of the above optimizations enable simplifying more jumps, so we just run it in every iteration - val runSimplifyJumps = compilerSettings.YoptSimplifyJumps + val runSimplifyJumps = compilerSettings.optSimplifyJumps val jumpsChanged = runSimplifyJumps && simplifyJumps(method) traceIfChanged("simplifyJumps") @@ -358,21 +359,21 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { requestPushPop = true, requestStoreLoad = true, firstIteration = true) - if (compilerSettings.YoptUnreachableCode) unreachableCodeEliminated += method + if (compilerSettings.optUnreachableCode) unreachableCodeEliminated += method r } else (false, false) // (*) Removing stale local variable descriptors is required for correctness, see comment in `methodOptimizations` val localsRemoved = - if (compilerSettings.YoptCompactLocals) compactLocalVariables(method) // also removes unused + if (compilerSettings.optCompactLocals) compactLocalVariables(method) // also removes unused else if (requireEliminateUnusedLocals) removeUnusedLocalVariableNodes(method)() // (*) else false traceIfChanged("localVariables") - val lineNumbersRemoved = if (compilerSettings.YoptUnreachableCode) removeEmptyLineNumbers(method) else false + val lineNumbersRemoved = if (compilerSettings.optUnreachableCode) removeEmptyLineNumbers(method) else false traceIfChanged("lineNumbers") - val labelsRemoved = if (compilerSettings.YoptUnreachableCode) removeEmptyLabelNodes(method) else false + val labelsRemoved = if (compilerSettings.optUnreachableCode) removeEmptyLabelNodes(method) else false traceIfChanged("labels") // assert that local variable annotations are empty (we don't emit them) - otherwise we'd have @@ -396,7 +397,7 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { */ def nullnessOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean = { AsmAnalyzer.sizeOKForNullness(method) && { - lazy val nullnessAnalyzer = new AsmAnalyzer(method, ownerClassName, new NullnessAnalyzer(btypes)) + lazy val nullnessAnalyzer = new AsmAnalyzer(method, ownerClassName, new NullnessAnalyzer(btypes, method)) // When running nullness optimizations the method may still have unreachable code. Analyzer // frames of unreachable instructions are `null`. |