diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-10-13 09:52:08 +0200 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-10-20 15:59:03 +0200 |
commit | 62be5705b91cb233bf922021f102a08bccc95af5 (patch) | |
tree | 684f42edc5e42319910f1e6d4b98a4192e5fbaa1 | |
parent | 19ee72193b6d3d4b1dc1e3bba430d3d741db97e3 (diff) | |
download | scala-62be5705b91cb233bf922021f102a08bccc95af5.tar.gz scala-62be5705b91cb233bf922021f102a08bccc95af5.tar.bz2 scala-62be5705b91cb233bf922021f102a08bccc95af5.zip |
Allow @inline/noinline at callsites (in addition to def-site)
Allow annotating individual callsites @inline / @noinline using an
annotation ascription
c.foo(): @inline
13 files changed, 175 insertions, 41 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index a42332f7f2..c4eb6e1b42 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -16,6 +16,7 @@ import java.lang.invoke.LambdaMetafactory import scala.tools.asm import GenBCode._ import BackendReporting._ +import scala.tools.asm.tree.MethodInsnNode /* * @@ -706,6 +707,21 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } else { genCallMethod(sym, invokeStyle, app.pos, hostClass) + // Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer + // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment + // is on the Select node (not on the Apply node added by UnCurry). + def checkInlineAnnotated(t: Tree): Unit = { + if (t.hasAttachment[InlineAnnotatedAttachment]) bc.jmethod.instructions.getLast match { + case m: MethodInsnNode => + if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m + else inlineAnnotatedCallsites += m + case _ => + } else t match { + case Apply(fun, _) => checkInlineAnnotated(fun) + case _ => + } + } + checkInlineAnnotated(app) } } // end of genNormalMethodCall() diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index f1b515910f..92aaf991bf 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -82,6 +82,14 @@ abstract class BTypes { val callsitePositions: concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty) /** + * Stores callsite instructions of invocatinos annotated `f(): @inline/noinline`. + * Instructions are added during code generation (BCodeBodyBuilder). The maps are then queried + * when building the CallGraph, every Callsite object has an annotated(No)Inline field. + */ + val inlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty) + val noInlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty) + + /** * Contains the internal names of all classes that are defined in Java source files of the current * compilation run (mixed compilation). Used for more detailed error reporting. */ 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 c804c228f5..66810176a1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -161,7 +161,9 @@ class CallGraph[BT <: BTypes](val btypes: BT) { argInfos = argInfos, callsiteStackHeight = a.frameAt(call).getStackSize, receiverKnownNotNull = receiverNotNull, - callsitePosition = callsitePositions.getOrElse(call, NoPosition) + callsitePosition = callsitePositions.getOrElse(call, NoPosition), + annotatedInline = inlineAnnotatedCallsites(call), + annotatedNoInline = noInlineAnnotatedCallsites(call) ) case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) if a.frameAt(indy) != null => @@ -348,7 +350,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { */ final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, callee: Either[OptimizerWarning, Callee], argInfos: IntMap[ArgInfo], - callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position) { + callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position, + annotatedInline: Boolean, annotatedNoInline: Boolean) { /** * Contains callsites that were created during inlining by cloning this callsite. Used to find * corresponding callsites when inlining post-inline requests. 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 a7c4c27a97..4203a93f2e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -263,7 +263,9 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { argInfos = argInfos, callsiteStackHeight = invocationStackHeight, receiverKnownNotNull = true, // see below (*) - callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition) + callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition), + annotatedInline = false, + annotatedNoInline = false ) // (*) The documentation in class LambdaMetafactory says: // "if implMethod corresponds to an instance method, the first capture argument 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 261b736029..449a56fdd1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -154,10 +154,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (selfParamType.info.get.inlineInfo.sam.isEmpty) samParamTypes - 0 else samParamTypes.updated(0, selfParamType) } - val staticCallsite = Callsite( + val staticCallsite = callsite.copy( callsiteInstruction = newCallsiteInstruction, - callsiteMethod = callsite.callsiteMethod, - callsiteClass = callsite.callsiteClass, callee = Right(Callee( callee = implClassMethod, calleeDeclarationClass = implClassBType, @@ -166,11 +164,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { annotatedInline = annotatedInline, annotatedNoInline = annotatedNoInline, samParamTypes = staticCallSamParamTypes, - calleeInfoWarning = infoWarning)), - argInfos = callsite.argInfos, - callsiteStackHeight = callsite.callsiteStackHeight, - receiverKnownNotNull = callsite.receiverKnownNotNull, - callsitePosition = callsite.callsitePosition + calleeInfoWarning = infoWarning)) ) callGraph.addCallsite(staticCallsite) } @@ -501,15 +495,12 @@ class Inliner[BT <: BTypes](val btypes: BT) { callGraph.callsites(callee).valuesIterator foreach { originalCallsite => val newCallsiteIns = instructionMap(originalCallsite.callsiteInstruction).asInstanceOf[MethodInsnNode] val argInfos = originalCallsite.argInfos flatMap mapArgInfo - val newCallsite = Callsite( + val newCallsite = originalCallsite.copy( callsiteInstruction = newCallsiteIns, callsiteMethod = callsiteMethod, callsiteClass = callsiteClass, - callee = originalCallsite.callee, argInfos = argInfos, - callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight, - receiverKnownNotNull = originalCallsite.receiverKnownNotNull, - callsitePosition = originalCallsite.callsitePosition + callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight ) originalCallsite.inlinedClones += ClonedCallsite(newCallsite, callsite) callGraph.addCallsite(newCallsite) 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 52627e77e6..89a768fd9c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -41,18 +41,18 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { compilingMethods.map(methodNode => { var requests = Set.empty[InlineRequest] callGraph.callsites(methodNode).valuesIterator foreach { - case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, _, callsiteWarning)), _, _, _, pos) => + case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) => inlineRequest(callsite) match { case Some(Right(req)) => requests += req case Some(Left(w)) => - if ((annotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) { - val annotWarn = if (annotatedInline) " is annotated @inline but" else "" + if ((calleeAnnotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) || 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 (annotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) { + if (calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) { // 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). def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" @@ -69,7 +69,7 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { } } - case Callsite(ins, _, _, Left(warning), _, _, _, pos) => + case Callsite(ins, _, _, Left(warning), _, _, _, pos, _, _) => if (warning.emitWarning(compilerSettings)) backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning") } @@ -108,12 +108,11 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { else None case "default" => - if (callee.safeToInline && !callee.annotatedNoInline) { - val shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { + if (callee.safeToInline && !callee.annotatedNoInline && !callsite.annotatedNoInline) { + def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { case (index, _) => callsite.argInfos.contains(index) }) - - if (shouldInlineHO || callee.annotatedInline) Some(requestIfCanInline(callsite)) + if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) Some(requestIfCanInline(callsite)) else None } else None } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 26e04edcca..45ebbd532d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4130,6 +4130,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ann setType arg1.tpe.withAnnotation(annotInfo) } val atype = ann.tpe + // For `f(): @inline/noinline` callsites, add the InlineAnnotatedAttachment. TypeApplys + // are eliminated by erasure, so add it to the underlying function in this case. + def setInlineAttachment(t: Tree, att: InlineAnnotatedAttachment): Unit = t match { + case TypeApply(fun, _) => setInlineAttachment(fun, att) + case _ => t.updateAttachment(att) + } + if (atype.hasAnnotation(definitions.ScalaNoInlineClass)) setInlineAttachment(arg1, NoInlineCallsiteAttachment) + else if (atype.hasAnnotation(definitions.ScalaInlineClass)) setInlineAttachment(arg1, InlineCallsiteAttachment) Typed(arg1, resultingTypeTree(atype)) setPos tree.pos setType atype } } diff --git a/src/library/scala/inline.scala b/src/library/scala/inline.scala index a21cced928..dc55af301c 100644 --- a/src/library/scala/inline.scala +++ b/src/library/scala/inline.scala @@ -11,8 +11,30 @@ package scala /** - * An annotation on methods that requests that the compiler should - * try especially hard to inline the annotated method. + * An annotation on methods that requests that the compiler should try especially hard to inline the + * annotated method. The annotation can be used at definition site or at callsite. + * + * {{{ + * @inline final def f1(x: Int) = x + * @noinline final def f2(x: Int) = x + * final def f3(x: Int) = x + * + * def t1 = f1(1) // inlined if possible + * def t2 = f2(1) // not inlined + * def t3 = f3(1) // may be inlined (heuristics) + * def t4 = f1(1): @noinline // not inlined (override at callsite) + * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t6 = f3(1): @inline // inlined if possible + * def t7 = f3(1): @noinline // not inlined + * } + * }}} + * + * Note: parentheses are required when annotating a callsite withing a larger expression. + * + * {{{ + * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline + * def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined + * }}} * * @author Lex Spoon * @version 1.0, 2007-5-21 diff --git a/src/library/scala/noinline.scala b/src/library/scala/noinline.scala index 38fd4c39d6..a427e170f4 100644 --- a/src/library/scala/noinline.scala +++ b/src/library/scala/noinline.scala @@ -11,8 +11,30 @@ package scala /** - * An annotation on methods that forbids the compiler to inline the - * method, no matter how safe the inlining appears to be. + * An annotation on methods that forbids the compiler to inline the method, no matter how safe the + * inlining appears to be. The annotation can be used at definition site or at callsite. + * + * {{{ + * @inline final def f1(x: Int) = x + * @noinline final def f2(x: Int) = x + * final def f3(x: Int) = x + * + * def t1 = f1(1) // inlined if possible + * def t2 = f2(1) // not inlined + * def t3 = f3(1) // may be inlined (heuristics) + * def t4 = f1(1): @noinline // not inlined (override at callsite) + * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t6 = f3(1): @inline // inlined if possible + * def t7 = f3(1): @noinline // not inlined + * } + * }}} + * + * Note: parentheses are required when annotating a callsite withing a larger expression. + * + * {{{ + * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline + * def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined + * }}} * * @author Lex Spoon * @version 1.0, 2007-5-21 diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index cca33253be..8358c1295c 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -52,4 +52,8 @@ trait StdAttachments { /** Untyped list of subpatterns attached to selector dummy. */ case class SubpatternsAttachment(patterns: List[Tree]) + + abstract class InlineAnnotatedAttachment + case object NoInlineCallsiteAttachment extends InlineAnnotatedAttachment + case object InlineCallsiteAttachment extends InlineAnnotatedAttachment } diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 1e9a4fe8a5..0132fff17c 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -41,6 +41,8 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.ForAttachment this.SyntheticUnitAttachment this.SubpatternsAttachment + this.NoInlineCallsiteAttachment + this.InlineCallsiteAttachment this.noPrint this.typeDebug this.Range diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index f329a43b30..3857d3e8ce 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -165,12 +165,6 @@ class CallGraphTest extends ClearAfterClass { checkCallsite(fn, m, forNameMeth, classTp, safeToInline = false, atInline = false, atNoInline = false) } - /** - * NOTE: if this test fails for you when running within the IDE, it's probably because you're - * using 2.12.0-M2 for compilining within the IDE, which doesn't add SAM information to the - * InlineInfo attribute. So the InlineInfo in the classfile for Function1 doesn't say that - * it's a SAM type. The test passes when running with ant (which does a full bootstrap). - */ @Test def checkArgInfos(): Unit = { val code = diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index 37cd9431dc..1e01627969 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -1056,12 +1056,75 @@ class InlinerTest extends ClearAfterClass { assertNoInvoke(convertMethod(d)) } - /** - * NOTE: if this test fails for you when running within the IDE, it's probably because you're - * using 2.12.0-M2 for compilining within the IDE, which doesn't add SAM information to the - * InlineInfo attribute. So the InlineInfo in the classfile for Function1 doesn't say that - * it's a SAM type. The test passes when running with ant (which does a full bootstrap). - */ + @Test + def inlineAnnotatedCallsite(): Unit = { + val code = + """class C { + | final def a(x: Int, f: Int => Int): Int = f(x) + | final def b(x: Int) = x + | final def c = 1 + | final def d[T] = 2 + | final def e[T](x: T) = c + | final def f[T](x: T) = println(x) + | final def g(x: Int)(y: Int) = x + | + | def t1 = a(10, _ + 1) + | def t2 = a(10, _ + 1): @noinline + | def t3 = b(3) + | def t4 = b(3): @inline + | def t5 = c: @inline + | def t6 = d[Int]: @inline + | def t7 = e[Int](2): @inline + | def t8 = f[Int](2): @inline + | def t9 = g(1)(2): @inline + |} + """.stripMargin + + val List(c) = compile(code) + assertInvoke(getSingleMethod(c, "t1"), "C", "C$$$anonfun$1") + assertInvoke(getSingleMethod(c, "t2"), "C", "a") + assertInvoke(getSingleMethod(c, "t3"), "C", "b") + assertNoInvoke(getSingleMethod(c, "t4")) + assertNoInvoke(getSingleMethod(c, "t5")) + assertNoInvoke(getSingleMethod(c, "t6")) + assertInvoke(getSingleMethod(c, "t7"), "C", "c") + assertInvoke(getSingleMethod(c, "t8"), "scala/Predef$", "println") + assertNoInvoke(getSingleMethod(c, "t9")) + } + + @Test + def inlineNoInlineOverride(): Unit = { + val code = + """class C { + | @inline final def f1(x: Int) = x + | @noinline final def f2(x: Int) = x + | final def f3(x: Int) = x + | + | def t1 = f1(1) // inlined + | def t2 = f2(1) // not inlined + | def t3 = f1(1): @noinline // not inlined + | def t4 = f2(1): @inline // not inlined (cannot override the def-site @noinline) + | def t5 = f3(1): @inline // inlined + | def t6 = f3(1): @noinline // not inlined + | + | def t7 = f1(1) + (f3(1): @inline) // without parenthesis, the ascription encloses the entire expression.. + | def t8 = f1(1) + (f1(1): @noinline) + | def t9 = f1(1) + f1(1) : @noinline // the ascription goes on the entire expression, so on the + invocation.. both f1 are inlined + |} + """.stripMargin + + val List(c) = compile(code) + assertNoInvoke(getSingleMethod(c, "t1")) + assertInvoke(getSingleMethod(c, "t2"), "C", "f2") + assertInvoke(getSingleMethod(c, "t3"), "C", "f1") + assertInvoke(getSingleMethod(c, "t4"), "C", "f2") + assertNoInvoke(getSingleMethod(c, "t5")) + assertInvoke(getSingleMethod(c, "t6"), "C", "f3") + assertNoInvoke(getSingleMethod(c, "t7")) + assertInvoke(getSingleMethod(c, "t8"), "C", "f1") + assertNoInvoke(getSingleMethod(c, "t9")) + } + @Test def inlineHigherOrder(): Unit = { val code = |