Skip to content

Commit 62be570

Browse files
committed
Allow @inline/noinline at callsites (in addition to def-site)
Allow annotating individual callsites @inline / @noinline using an annotation ascription c.foo(): @inline
1 parent 19ee721 commit 62be570

File tree

13 files changed

+175
-41
lines changed

13 files changed

+175
-41
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import java.lang.invoke.LambdaMetafactory
1616
import scala.tools.asm
1717
import GenBCode._
1818
import BackendReporting._
19+
import scala.tools.asm.tree.MethodInsnNode
1920

2021
/*
2122
*
@@ -706,6 +707,21 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
706707
}
707708
else {
708709
genCallMethod(sym, invokeStyle, app.pos, hostClass)
710+
// Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer
711+
// for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment
712+
// is on the Select node (not on the Apply node added by UnCurry).
713+
def checkInlineAnnotated(t: Tree): Unit = {
714+
if (t.hasAttachment[InlineAnnotatedAttachment]) bc.jmethod.instructions.getLast match {
715+
case m: MethodInsnNode =>
716+
if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m
717+
else inlineAnnotatedCallsites += m
718+
case _ =>
719+
} else t match {
720+
case Apply(fun, _) => checkInlineAnnotated(fun)
721+
case _ =>
722+
}
723+
}
724+
checkInlineAnnotated(app)
709725
}
710726

711727
} // end of genNormalMethodCall()

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ abstract class BTypes {
8181
*/
8282
val callsitePositions: concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
8383

84+
/**
85+
* Stores callsite instructions of invocatinos annotated `f(): @inline/noinline`.
86+
* Instructions are added during code generation (BCodeBodyBuilder). The maps are then queried
87+
* when building the CallGraph, every Callsite object has an annotated(No)Inline field.
88+
*/
89+
val inlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty)
90+
val noInlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty)
91+
8492
/**
8593
* Contains the internal names of all classes that are defined in Java source files of the current
8694
* compilation run (mixed compilation). Used for more detailed error reporting.

src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
161161
argInfos = argInfos,
162162
callsiteStackHeight = a.frameAt(call).getStackSize,
163163
receiverKnownNotNull = receiverNotNull,
164-
callsitePosition = callsitePositions.getOrElse(call, NoPosition)
164+
callsitePosition = callsitePositions.getOrElse(call, NoPosition),
165+
annotatedInline = inlineAnnotatedCallsites(call),
166+
annotatedNoInline = noInlineAnnotatedCallsites(call)
165167
)
166168

167169
case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) if a.frameAt(indy) != null =>
@@ -348,7 +350,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
348350
*/
349351
final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType,
350352
callee: Either[OptimizerWarning, Callee], argInfos: IntMap[ArgInfo],
351-
callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position) {
353+
callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position,
354+
annotatedInline: Boolean, annotatedNoInline: Boolean) {
352355
/**
353356
* Contains callsites that were created during inlining by cloning this callsite. Used to find
354357
* corresponding callsites when inlining post-inline requests.

src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,9 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
263263
argInfos = argInfos,
264264
callsiteStackHeight = invocationStackHeight,
265265
receiverKnownNotNull = true, // see below (*)
266-
callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition)
266+
callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition),
267+
annotatedInline = false,
268+
annotatedNoInline = false
267269
)
268270
// (*) The documentation in class LambdaMetafactory says:
269271
// "if implMethod corresponds to an instance method, the first capture argument

src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
154154
if (selfParamType.info.get.inlineInfo.sam.isEmpty) samParamTypes - 0
155155
else samParamTypes.updated(0, selfParamType)
156156
}
157-
val staticCallsite = Callsite(
157+
val staticCallsite = callsite.copy(
158158
callsiteInstruction = newCallsiteInstruction,
159-
callsiteMethod = callsite.callsiteMethod,
160-
callsiteClass = callsite.callsiteClass,
161159
callee = Right(Callee(
162160
callee = implClassMethod,
163161
calleeDeclarationClass = implClassBType,
@@ -166,11 +164,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
166164
annotatedInline = annotatedInline,
167165
annotatedNoInline = annotatedNoInline,
168166
samParamTypes = staticCallSamParamTypes,
169-
calleeInfoWarning = infoWarning)),
170-
argInfos = callsite.argInfos,
171-
callsiteStackHeight = callsite.callsiteStackHeight,
172-
receiverKnownNotNull = callsite.receiverKnownNotNull,
173-
callsitePosition = callsite.callsitePosition
167+
calleeInfoWarning = infoWarning))
174168
)
175169
callGraph.addCallsite(staticCallsite)
176170
}
@@ -501,15 +495,12 @@ class Inliner[BT <: BTypes](val btypes: BT) {
501495
callGraph.callsites(callee).valuesIterator foreach { originalCallsite =>
502496
val newCallsiteIns = instructionMap(originalCallsite.callsiteInstruction).asInstanceOf[MethodInsnNode]
503497
val argInfos = originalCallsite.argInfos flatMap mapArgInfo
504-
val newCallsite = Callsite(
498+
val newCallsite = originalCallsite.copy(
505499
callsiteInstruction = newCallsiteIns,
506500
callsiteMethod = callsiteMethod,
507501
callsiteClass = callsiteClass,
508-
callee = originalCallsite.callee,
509502
argInfos = argInfos,
510-
callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight,
511-
receiverKnownNotNull = originalCallsite.receiverKnownNotNull,
512-
callsitePosition = originalCallsite.callsitePosition
503+
callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight
513504
)
514505
originalCallsite.inlinedClones += ClonedCallsite(newCallsite, callsite)
515506
callGraph.addCallsite(newCallsite)

src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
4141
compilingMethods.map(methodNode => {
4242
var requests = Set.empty[InlineRequest]
4343
callGraph.callsites(methodNode).valuesIterator foreach {
44-
case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, _, callsiteWarning)), _, _, _, pos) =>
44+
case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) =>
4545
inlineRequest(callsite) match {
4646
case Some(Right(req)) => requests += req
4747
case Some(Left(w)) =>
48-
if ((annotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) {
49-
val annotWarn = if (annotatedInline) " is annotated @inline but" else ""
48+
if ((calleeAnnotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) {
49+
val annotWarn = if (calleeAnnotatedInline) " is annotated @inline but" else ""
5050
val msg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)}$annotWarn could not be inlined:\n$w"
5151
backendReporting.inlinerWarning(callsite.callsitePosition, msg)
5252
}
5353

5454
case None =>
55-
if (annotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) {
55+
if (calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) {
5656
// if the callsite is annotated @inline, we report an inline warning even if the underlying
5757
// reason is, for example, mixed compilation (which has a separate -Yopt-warning flag).
5858
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) {
6969
}
7070
}
7171

72-
case Callsite(ins, _, _, Left(warning), _, _, _, pos) =>
72+
case Callsite(ins, _, _, Left(warning), _, _, _, pos, _, _) =>
7373
if (warning.emitWarning(compilerSettings))
7474
backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning")
7575
}
@@ -108,12 +108,11 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
108108
else None
109109

110110
case "default" =>
111-
if (callee.safeToInline && !callee.annotatedNoInline) {
112-
val shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists {
111+
if (callee.safeToInline && !callee.annotatedNoInline && !callsite.annotatedNoInline) {
112+
def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists {
113113
case (index, _) => callsite.argInfos.contains(index)
114114
})
115-
116-
if (shouldInlineHO || callee.annotatedInline) Some(requestIfCanInline(callsite))
115+
if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) Some(requestIfCanInline(callsite))
117116
else None
118117
} else None
119118
}

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4130,6 +4130,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
41304130
ann setType arg1.tpe.withAnnotation(annotInfo)
41314131
}
41324132
val atype = ann.tpe
4133+
// For `f(): @inline/noinline` callsites, add the InlineAnnotatedAttachment. TypeApplys
4134+
// are eliminated by erasure, so add it to the underlying function in this case.
4135+
def setInlineAttachment(t: Tree, att: InlineAnnotatedAttachment): Unit = t match {
4136+
case TypeApply(fun, _) => setInlineAttachment(fun, att)
4137+
case _ => t.updateAttachment(att)
4138+
}
4139+
if (atype.hasAnnotation(definitions.ScalaNoInlineClass)) setInlineAttachment(arg1, NoInlineCallsiteAttachment)
4140+
else if (atype.hasAnnotation(definitions.ScalaInlineClass)) setInlineAttachment(arg1, InlineCallsiteAttachment)
41334141
Typed(arg1, resultingTypeTree(atype)) setPos tree.pos setType atype
41344142
}
41354143
}

src/library/scala/inline.scala

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,30 @@
1111
package scala
1212

1313
/**
14-
* An annotation on methods that requests that the compiler should
15-
* try especially hard to inline the annotated method.
14+
* An annotation on methods that requests that the compiler should try especially hard to inline the
15+
* annotated method. The annotation can be used at definition site or at callsite.
16+
*
17+
* {{{
18+
* @inline final def f1(x: Int) = x
19+
* @noinline final def f2(x: Int) = x
20+
* final def f3(x: Int) = x
21+
*
22+
* def t1 = f1(1) // inlined if possible
23+
* def t2 = f2(1) // not inlined
24+
* def t3 = f3(1) // may be inlined (heuristics)
25+
* def t4 = f1(1): @noinline // not inlined (override at callsite)
26+
* def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition)
27+
* def t6 = f3(1): @inline // inlined if possible
28+
* def t7 = f3(1): @noinline // not inlined
29+
* }
30+
* }}}
31+
*
32+
* Note: parentheses are required when annotating a callsite withing a larger expression.
33+
*
34+
* {{{
35+
* def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline
36+
* def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined
37+
* }}}
1638
*
1739
* @author Lex Spoon
1840
* @version 1.0, 2007-5-21

src/library/scala/noinline.scala

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,30 @@
1111
package scala
1212

1313
/**
14-
* An annotation on methods that forbids the compiler to inline the
15-
* method, no matter how safe the inlining appears to be.
14+
* An annotation on methods that forbids the compiler to inline the method, no matter how safe the
15+
* inlining appears to be. The annotation can be used at definition site or at callsite.
16+
*
17+
* {{{
18+
* @inline final def f1(x: Int) = x
19+
* @noinline final def f2(x: Int) = x
20+
* final def f3(x: Int) = x
21+
*
22+
* def t1 = f1(1) // inlined if possible
23+
* def t2 = f2(1) // not inlined
24+
* def t3 = f3(1) // may be inlined (heuristics)
25+
* def t4 = f1(1): @noinline // not inlined (override at callsite)
26+
* def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition)
27+
* def t6 = f3(1): @inline // inlined if possible
28+
* def t7 = f3(1): @noinline // not inlined
29+
* }
30+
* }}}
31+
*
32+
* Note: parentheses are required when annotating a callsite withing a larger expression.
33+
*
34+
* {{{
35+
* def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline
36+
* def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined
37+
* }}}
1638
*
1739
* @author Lex Spoon
1840
* @version 1.0, 2007-5-21

src/reflect/scala/reflect/internal/StdAttachments.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ trait StdAttachments {
5252

5353
/** Untyped list of subpatterns attached to selector dummy. */
5454
case class SubpatternsAttachment(patterns: List[Tree])
55+
56+
abstract class InlineAnnotatedAttachment
57+
case object NoInlineCallsiteAttachment extends InlineAnnotatedAttachment
58+
case object InlineCallsiteAttachment extends InlineAnnotatedAttachment
5559
}

0 commit comments

Comments
 (0)