Skip to content

Introduce @forceInline annotation #4755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def enclosingInlineds(implicit ctx: Context): List[Tree] =
ctx.property(InlinedCalls).getOrElse(Nil)

/** The source file where the symbol of the `@inline` method referred to by `call`
/** The source file where the symbol of the `inline` method referred to by `call`
* is defined
*/
def sourceFile(call: Tree)(implicit ctx: Context) = {
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -707,8 +707,8 @@ class Definitions {
def ImplicitAmbiguousAnnot(implicit ctx: Context) = ImplicitAmbiguousAnnotType.symbol.asClass
lazy val ImplicitNotFoundAnnotType = ctx.requiredClassRef("scala.annotation.implicitNotFound")
def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass
lazy val InlineAnnotType = ctx.requiredClassRef("scala.inline")
def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass
lazy val ForceInlineAnnotType = ctx.requiredClassRef("scala.forceInline")
def ForceInlineAnnot(implicit ctx: Context) = ForceInlineAnnotType.symbol.asClass
lazy val InlineParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.InlineParam")
def InlineParamAnnot(implicit ctx: Context) = InlineParamAnnotType.symbol.asClass
lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.InvariantBetween")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,6 @@ object Symbols {
override def toString: String = value.asScala.toString()
}

@inline def newMutableSymbolMap[T]: MutableSymbolMap[T] =
@forceInline def newMutableSymbolMap[T]: MutableSymbolMap[T] =
new MutableSymbolMap(new java.util.IdentityHashMap[Symbol, T]())
}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3935,7 +3935,7 @@ object Types {
abstract class VariantTraversal {
protected[core] var variance = 1

@inline protected def atVariance[T](v: Int)(op: => T): T = {
@forceInline protected def atVariance[T](v: Int)(op: => T): T = {
val saved = variance
variance = v
val res = op
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ object MarkupParsers {
}

/** Some try/catch/finally logic used by xLiteral and xLiteralPattern. */
@inline private def xLiteralCommon(f: () => Tree, ifTruncated: String => Unit): Tree = {
@forceInline private def xLiteralCommon(f: () => Tree, ifTruncated: String => Unit): Tree = {
var output: Tree = null.asInstanceOf[Tree]
try output = f()
catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ object SyntaxHighlighting {
val newBuf = new StringBuilder
var lastValDefToken = ""

@inline def keywordStart =
@forceInline def keywordStart =
prev == 0 || prev == ' ' || prev == '{' || prev == '(' ||
prev == '\n' || prev == '[' || prev == ',' || prev == ':' ||
prev == '|' || prev == '&'

@inline def numberStart(c: Char) =
@forceInline def numberStart(c: Char) =
c.isDigit && (!prev.isLetter || prev == '.' || prev == ' ' || prev == '(' || prev == '\u0000')

def takeChar(): Char = takeChars(1).head
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1748,7 +1748,7 @@ object messages {
val kind = "Syntax"
val msg = hl"no explicit ${"return"} allowed from inline $owner"
val explanation =
hl"""Methods marked with ${"@inline"} may not use ${"return"} statements.
hl"""Methods marked with ${"inline"} modifier may not use ${"return"} statements.
|Instead, you should rely on the last expression's value being
|returned from a method.
|"""
Expand Down
14 changes: 7 additions & 7 deletions compiler/src/dotty/tools/dotc/reporting/trace.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import core.Mode

object trace {

@inline
@forceInline
def onDebug[TD](question: => String)(op: => TD)(implicit ctx: Context): TD =
conditionally(ctx.settings.YdebugTrace.value, question, false)(op)

@inline
@forceInline
def conditionally[TC](cond: Boolean, question: => String, show: Boolean)(op: => TC)(implicit ctx: Context): TC =
if (Config.tracingEnabled) {
def op1 = op
if (cond) apply[TC](question, Printers.default, show)(op1)
else op1
} else op

@inline
@forceInline
def apply[T](question: => String, printer: Printers.Printer, showOp: Any => String)(op: => T)(implicit ctx: Context): T =
if (Config.tracingEnabled) {
def op1 = op
Expand All @@ -30,7 +30,7 @@ object trace {
}
else op

@inline
@forceInline
def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T =
if (Config.tracingEnabled) {
def op1 = op
Expand All @@ -39,15 +39,15 @@ object trace {
}
else op

@inline
@forceInline
def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T =
apply[T](question, printer, false)(op)

@inline
@forceInline
def apply[T](question: => String, show: Boolean)(op: => T)(implicit ctx: Context): T =
apply[T](question, Printers.default, show)(op)

@inline
@forceInline
def apply[T](question: => String)(op: => T)(implicit ctx: Context): T =
apply[T](question, Printers.default, false)(op)

Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ object Inliner {
}

/** `sym` has an inline method with a known body to inline (note: definitions coming
* from Scala2x class files might be `@inline`, but still lack that body.
* from Scala2x class files might be `@forceInline`, but still lack that body.
*/
def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean =
sym.isInlinedMethod && sym.hasAnnotation(defn.BodyAnnot) // TODO: Open this up for transparent methods as well
Expand All @@ -240,7 +240,7 @@ object Inliner {
def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree =
sym.unforcedAnnotation(defn.BodyAnnot).get.tree

/** Try to inline a call to a `@inline` method. Fail with error if the maximal
/** Try to inline a call to a `inline` method. Fail with error if the maximal
* inline depth is exceeded.
*
* @param tree The call to inline
Expand Down Expand Up @@ -281,7 +281,7 @@ object Inliner {

/** Produces an inlined version of `call` via its `inlined` method.
*
* @param call the original call to a `@inline` method
* @param call the original call to an `inline` method
* @param rhsToInline the body of the inline method that replaces the call.
*/
class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ class Namer { typer: Typer =>
if (sym.unforcedAnnotation(cls).isEmpty) {
val ann = Annotation.deferred(cls, implicit ctx => typedAheadAnnotation(annotTree))
sym.addAnnotation(ann)
if (cls == defn.InlineAnnot && sym.is(Method, butNot = Accessor))
if (cls == defn.ForceInlineAnnot && sym.is(Method, butNot = Accessor))
sym.setFlag(Inline)
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/util/Chars.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object Chars {

/** Convert a character to a backslash-u escape */
def char2uescape(c: Char): String = {
@inline def hexChar(ch: Int): Char =
@forceInline def hexChar(ch: Int): Char =
(( if (ch < 10) '0' else 'A' - 10 ) + ch).toChar

char2uescapeArray(2) = hexChar((c >> 12) )
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/util/Stats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import collection.mutable
override def default(key: String): Int = 0
}

@inline
@forceInline
def record(fn: => String, n: => Int = 1) =
if (enabled) doRecord(fn, n)

Expand All @@ -30,7 +30,7 @@ import collection.mutable
hits(name) += n
}

@inline
@forceInline
def track[T](fn: String)(op: => T) =
if (enabled) doTrack(fn)(op) else op

Expand All @@ -42,7 +42,7 @@ import collection.mutable
finally stack = stack.tail
} else op

@inline
@forceInline
def trackTime[T](fn: String)(op: => T) =
if (enabled) doTrackTime(fn)(op) else op

Expand Down
18 changes: 13 additions & 5 deletions compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,32 @@ class InlineBytecodeTests extends DottyBytecodeTest {
val source = """
|class Foo {
| inline def foo: Int = 1
| @forceInline def bar: Int = 1
|
| def meth1: Unit = foo
| def meth2: Unit = 1
| def meth2: Unit = bar
| def meth3: Unit = 1
|}
""".stripMargin

checkBCode(source) { dir =>
val clsIn = dir.lookupName("Foo.class", directory = false).input
val clsNode = loadClassNode(clsIn)
val meth1 = getMethod(clsNode, "meth1")
val meth2 = getMethod(clsNode, "meth2")
val meth1 = getMethod(clsNode, "meth1")
val meth2 = getMethod(clsNode, "meth2")
val meth3 = getMethod(clsNode, "meth3")

val instructions1 = instructionsFromMethod(meth1)
val instructions2 = instructionsFromMethod(meth2)
val instructions3 = instructionsFromMethod(meth3)

assert(instructions1 == instructions2,
assert(instructions1 == instructions3,
"`foo` was not properly inlined in `meth1`\n" +
diffInstructions(instructions1, instructions2))
diffInstructions(instructions1, instructions3))

assert(instructions2 == instructions3,
"`bar` was not properly inlined in `meth2`\n" +
diffInstructions(instructions2, instructions3))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ class ErrorMessagesTests extends ErrorMessagesTest {
@Test def noReturnInInline =
checkMessagesAfter(FrontEnd.name) {
"""class BadFunction {
| @inline def usesReturn: Int = { return 42 }
| inline def usesReturn: Int = { return 42 }
|}
""".stripMargin
}.expect { (ictx, messages) =>
Expand Down
6 changes: 6 additions & 0 deletions docs/docs/reference/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ it in backticks, i.e.

@`inline` def ...

The Dotty compiler ignores `@inline` annotated definitions. To cross
compile between both Dotty and Scalac, we introduce a new `@forceInline`
annotation which is equivalent to the new `inline` modifier. Note that
Scala 2 ignores the `@forceInLine` annotation, and one must use both
annotations to inline across the two compilers (i.e. `@forceInline @inline`).

### The definition of constant expression

Right-hand sides of inline values and of arguments for inline parameters
Expand Down
8 changes: 5 additions & 3 deletions library/src/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dotty

import scala.forceInline

object DottyPredef {

/** A class for implicit values that can serve as implicit conversions
Expand All @@ -22,18 +24,18 @@ object DottyPredef {
*/
abstract class ImplicitConverter[-T, +U] extends Function1[T, U]

@inline final def assert(assertion: Boolean, message: => Any): Unit = {
@forceInline final def assert(assertion: Boolean, message: => Any): Unit = {
if (!assertion)
assertFail(message)
}

@inline final def assert(assertion: Boolean): Unit = {
@forceInline final def assert(assertion: Boolean): Unit = {
if (!assertion)
assertFail()
}

final def assertFail(): Unit = throw new java.lang.AssertionError("assertion failed")
final def assertFail(message: => Any): Unit = throw new java.lang.AssertionError("assertion failed: " + message)

@inline final def implicitly[T](implicit ev: T): T = ev
@forceInline final def implicitly[T](implicit ev: T): T = ev
}
17 changes: 9 additions & 8 deletions library/src/dotty/runtime/LazyVals.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dotty.runtime

import scala.annotation.tailrec
import scala.forceInline

/**
* Helper methods used in thread-safe lazy vals.
Expand All @@ -24,20 +25,20 @@ object LazyVals {
final val LAZY_VAL_MASK = 3L
final val debug = false

@inline def STATE(cur: Long, ord: Int) = {
@forceInline def STATE(cur: Long, ord: Int) = {
val r = (cur >> (ord * BITS_PER_LAZY_VAL)) & LAZY_VAL_MASK
if (debug)
println(s"STATE($cur, $ord) = $r")
r
}
@inline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = {
@forceInline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = {
if (debug)
println(s"CAS($t, $offset, $e, $v, $ord)")
val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL)
val n = (e & mask) | (v.toLong << (ord * BITS_PER_LAZY_VAL))
compareAndSet(t, offset, e, n)
}
@inline def setFlag(t: Object, offset: Long, v: Int, ord: Int) = {
@forceInline def setFlag(t: Object, offset: Long, v: Int, ord: Int) = {
if (debug)
println(s"setFlag($t, $offset, $v, $ord)")
var retry = true
Expand All @@ -56,7 +57,7 @@ object LazyVals {
}
}
}
@inline def wait4Notification(t: Object, offset: Long, cur: Long, ord: Int) = {
@forceInline def wait4Notification(t: Object, offset: Long, cur: Long, ord: Int) = {
if (debug)
println(s"wait4Notification($t, $offset, $cur, $ord)")
var retry = true
Expand All @@ -74,8 +75,8 @@ object LazyVals {
}
}

@inline def compareAndSet(t: Object, off: Long, e: Long, v: Long) = unsafe.compareAndSwapLong(t, off, e, v)
@inline def get(t: Object, off: Long) = {
@forceInline def compareAndSet(t: Object, off: Long, e: Long, v: Long) = unsafe.compareAndSwapLong(t, off, e, v)
@forceInline def get(t: Object, off: Long) = {
if (debug)
println(s"get($t, $off)")
unsafe.getLongVolatile(t, off)
Expand All @@ -87,7 +88,7 @@ object LazyVals {
x => new Object()
}.toArray

@inline def getMonitor(obj: Object, fieldId: Int = 0) = {
@forceInline def getMonitor(obj: Object, fieldId: Int = 0) = {
var id = (
/*java.lang.System.identityHashCode(obj) + */ // should be here, but #548
fieldId) % base
Expand All @@ -96,7 +97,7 @@ object LazyVals {
monitors(id)
}

@inline def getOffset(clz: Class[_], name: String) = {
@forceInline def getOffset(clz: Class[_], name: String) = {
val r = unsafe.objectFieldOffset(clz.getDeclaredField(name))
if (debug)
println(s"getOffset($clz, $name) = $r")
Expand Down
16 changes: 16 additions & 0 deletions library/src/scala/forceInline.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package scala

/** An annotation on methods that is equivalent to Dotty `inline` modifier.
*
* The annotation should be used instead of the `inline` modifier in code
* that needs to cross compile between Scala 2 and Dotty.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also mention that @forceInline will be ignored by Scala 2. Maybe hint at the need of @forceInline @inline for cross compilation with the optimization in both versions.

*
* Note that Scala 2 ignores the `@forceInLine` annotation, and one must use
* both the `@inline` and `@forceInline` annotation to inline across the
* two compilers. E.g.
*
* ```scala
* @inline @forceInline def foo = ...
* ```
*/
class forceInline extends scala.annotation.StaticAnnotation
2 changes: 1 addition & 1 deletion library/src/scala/tasty/util/ShowSourceCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty
case Annotation(annot, _) =>
annot.tpe match {
case Type.TypeRef(_, Type.SymRef(PackageDef("internal", _), Type.ThisType(Type.SymRef(PackageDef("annotation", _), NoPrefix())))) => false
case Type.TypeRef("inline", Types.ScalaPackage()) => false
case Type.TypeRef("forceInline", Types.ScalaPackage()) => false
case _ => true
}
case x => throw new MatchError(x.show)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
object B {
@inline def getInline: Int =
inline def getInline: Int =
A.get
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
object B {
@inline def getInline: Double =
inline def getInline: Double =
A.get
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
object B {
@inline def getInline: Int =
inline def getInline: Int =
sys.error("This is an expected failure when running C")
}
Loading