Skip to content

Commit

Permalink
Merge pull request #4755 from dotty-staging/forceInline
Browse files Browse the repository at this point in the history
Introduce `@forceInline` annotation
  • Loading branch information
odersky authored Jul 5, 2018
2 parents d761815 + 6d7dd2c commit 31f9a1b
Show file tree
Hide file tree
Showing 40 changed files with 101 additions and 74 deletions.
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.
*
* 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

0 comments on commit 31f9a1b

Please sign in to comment.