Skip to content

Fix #4590: Reintroduce old inline accessor scheme as a fallback #4634

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
Jun 11, 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
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ object NameKinds {
val LiftedTreeName = new UniqueNameKind("liftedTree")
val SuperArgName = new UniqueNameKind("$superArg$")
val DocArtifactName = new UniqueNameKind("$doc")
val UniqueInlineName = new UniqueNameKind("$i")

/** A kind of unique extension methods; Unlike other unique names, these can be
* unmangled.
Expand Down
49 changes: 36 additions & 13 deletions compiler/src/dotty/tools/dotc/transform/AccessProxies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,33 @@ abstract class AccessProxies {
/** accessor -> accessed */
private val accessedBy = newMutableSymbolMap[Symbol]

/** Given the name of an accessor, is the receiver of the call to accessed obtained
* as a parameterer?
*/
protected def passReceiverAsArg(accessorName: Name)(implicit ctx: Context) = false

/** The accessor definitions that need to be added to class `cls`
* As a side-effect, this method removes entries from the `accessedBy` map.
* So a second call of the same method will yield the empty list.
*/
private def accessorDefs(cls: Symbol)(implicit ctx: Context): Iterator[DefDef] =
for (accessor <- cls.info.decls.iterator; accessed <- accessedBy.remove(accessor)) yield
polyDefDef(accessor.asTerm, tps => argss => {
val accessRef = ref(TermRef(cls.thisType, accessed))
def numTypeParams = accessed.info match {
case info: PolyType => info.paramNames.length
case _ => 0
}
val (accessRef, forwardedTypes, forwardedArgss) =
if (passReceiverAsArg(accessor.name))
(argss.head.head.select(accessed), tps.takeRight(numTypeParams), argss.tail)
else
(ref(TermRef(cls.thisType, accessed)), tps, argss)
val rhs =
if (accessor.name.isSetterName &&
argss.nonEmpty && argss.head.nonEmpty) // defensive conditions
accessRef.becomes(argss.head.head)
forwardedArgss.nonEmpty && forwardedArgss.head.nonEmpty) // defensive conditions
accessRef.becomes(forwardedArgss.head.head)
else
accessRef.appliedToTypes(tps).appliedToArgss(argss)
accessRef.appliedToTypes(forwardedTypes).appliedToArgss(forwardedArgss)
rhs.withPos(accessed.pos)
})

Expand All @@ -65,7 +78,7 @@ abstract class AccessProxies {
}

/** An accessor symbol, create a fresh one unless one exists already */
private def accessorSymbol(owner: Symbol, accessorName: TermName, accessorInfo: Type, accessed: Symbol)(implicit ctx: Context) = {
protected def accessorSymbol(owner: Symbol, accessorName: TermName, accessorInfo: Type, accessed: Symbol)(implicit ctx: Context) = {
def refersToAccessed(sym: Symbol) = accessedBy.get(sym).contains(accessed)
owner.info.decl(accessorName).suchThat(refersToAccessed).symbol.orElse {
val acc = newAccessorSymbol(owner, accessorName, accessorInfo, accessed.pos)
Expand All @@ -83,14 +96,24 @@ abstract class AccessProxies {
}.withPos(reference.pos)

/** Given a reference to a getter accessor, the corresponding setter reference */
def useSetter(getterRef: RefTree)(implicit ctx: Context): Tree = {
val getter = getterRef.symbol
val accessed = accessedBy(getter)
val accessedName = accessed.name.asTermName
val setterName = accessorNameKind(accessedName.setterName)
val setterInfo = MethodType(getter.info.widenExpr :: Nil, defn.UnitType)
val setter = accessorSymbol(getter.owner, setterName, setterInfo, accessed)
rewire(getterRef, setter)
def useSetter(getterRef: Tree)(implicit ctx: Context): Tree = getterRef match {
case getterRef: RefTree =>
val getter = getterRef.symbol.asTerm
val accessed = accessedBy(getter)
val setterName = getter.name.setterName
def toSetterInfo(getterInfo: Type): Type = getterInfo match {
case getterInfo: LambdaType =>
getterInfo.derivedLambdaType(resType = toSetterInfo(getterInfo.resType))
case _ =>
MethodType(getterInfo :: Nil, defn.UnitType)
}
val setterInfo = toSetterInfo(getter.info.widenExpr)
val setter = accessorSymbol(getter.owner, setterName, setterInfo, accessed)
rewire(getterRef, setter)
case Apply(fn, args) =>
cpy.Apply(getterRef)(useSetter(fn), args)
case TypeApply(fn, args) =>
cpy.TypeApply(getterRef)(useSetter(fn), args)
}

/** Create an accessor unless one exists already, and replace the original
Expand Down
150 changes: 134 additions & 16 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import StdNames.nme
import Contexts.Context
import Names.{Name, TermName, EmptyTermName}
import NameOps._
import NameKinds.{ClassifiedNameKind, InlineAccessorName}
import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName}
import ProtoTypes.selectionProto
import SymDenotations.SymDenotation
import Annotations._
Expand All @@ -33,10 +33,18 @@ object Inliner {

class InlineAccessors extends AccessProxies {

/** A tree map which inserts accessors for all non-public term members accessed
* from inlined code. Accessors are collected in the `accessors` buffer.
*/
class MakeInlineable(inlineSym: Symbol) extends TreeMap with Insert {
/** If an inline accessor name wraps a unique inline name, this is taken as indication
* that the inline accessor takes its receiver as first parameter. Such accessors
* are created by MakeInlineablePassing.
*/
override def passReceiverAsArg(name: Name)(implicit ctx: Context) = name match {
case InlineAccessorName(UniqueInlineName(_, _)) => true
case _ => false
}

/** A tree map which inserts accessors for non-public term members accessed from inlined code.
*/
abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert {
def accessorNameKind = InlineAccessorName

/** A definition needs an accessor if it is private, protected, or qualified private
Expand All @@ -48,18 +56,126 @@ object Inliner {
(sym.is(AccessFlags) || sym.privateWithin.exists) &&
!sym.isContainedIn(inlineSym)

def preTransform(tree: Tree)(implicit ctx: Context): Tree

def postTransform(tree: Tree)(implicit ctx: Context) = tree match {
case Assign(lhs, rhs) if lhs.symbol.name.is(InlineAccessorName) =>
cpy.Apply(tree)(useSetter(lhs), rhs :: Nil)
case _ =>
tree
}

// TODO: Also handle references to non-public types.
// This is quite tricky, as such types can appear anywhere, including as parts
// of types of other things. For the moment we do nothing and complain
// at the implicit expansion site if there's a reference to an inaccessible type.
override def transform(tree: Tree)(implicit ctx: Context): Tree =
super.transform(accessorIfNeeded(tree)) match {
case tree1 @ Assign(lhs: RefTree, rhs) if lhs.symbol.name.is(InlineAccessorName) =>
cpy.Apply(tree1)(useSetter(lhs), rhs :: Nil)
case tree1 =>
tree1
}
postTransform(super.transform(preTransform(tree)))
}

/** Direct approach: place the accessor with the accessed symbol. This has the
* advantage that we can re-use the receiver as is. But it is only
* possible if the receiver is essentially this or an outer this, which is indicated
* by the test that we can find a host for the accessor.
*/
class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) {
def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case tree: RefTree if needsAccessor(tree.symbol) =>
if (tree.symbol.isConstructor) {
ctx.error("Implementation restriction: cannot use private constructors in inline methods", tree.pos)
tree // TODO: create a proper accessor for the private constructor
}
else if (AccessProxies.hostForAccessorOf(tree.symbol).exists) useAccessor(tree)
else tree
case _ =>
tree
}
}

/** Fallback approach if the direct approach does not work: Place the accessor method
* in the same class as the inlined method, and let it take the receiver as parameter.
* This is tricky, since we have to find a suitable type for the parameter, which might
* require additional type parameters for the inline accessor. An example is in the
* `TestPassing` class in test `run/inline/inlines_1`:
*
* class C[T](x: T) {
* private[inlines] def next[U](y: U): (T, U) = (x, y)
Copy link
Member

Choose a reason for hiding this comment

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

Is all of this code needed just to create accessors to private-qualified methods? Since they'll end up being public method in the bytecode anyway, maybe we can just relax the accessibility rules inside inlined trees for them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It could also be package private methods in Java classes. Also, in the future we would like to avoid widening protected to public, so it would apply to Scala-defined classes as well.

* }
* class TestPassing {
* inline def foo[A](x: A): (A, Int) = {
* val c = new C[A](x)
* c.next(1)
* }
* inline def bar[A](x: A): (A, String) = {
* val c = new C[A](x)
* c.next("")
* }
*
* `C` could be compiled separately, so we cannot place the inline accessor in it.
* Instead, the inline accessor goes into `TestPassing` and takes the actual receiver
* type as argument:
*
* def inline$next$i1[A, U](x$0: C[A])(y: U): (A, U) =
* x$0.next[U](y)
*
* Since different calls might have different receiver types, we need to generate one
* such accessor per call, so they need to have unique names.
*/
class MakeInlineablePassing(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) {

def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case _: Apply | _: TypeApply | _: RefTree
if needsAccessor(tree.symbol) && tree.isTerm && !tree.symbol.isConstructor =>
val (refPart, targs, argss) = decomposeCall(tree)
val qual = qualifier(refPart)
inlining.println(i"adding receiver passing inline accessor for $tree -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))")

// Need to dealias in order to cagtch all possible references to abstracted over types in
// substitutions
val dealiasMap = new TypeMap {
def apply(t: Type) = mapOver(t.dealias)
}
val qualType = dealiasMap(qual.tpe.widen)

// The types that are local to the inlined method, and that therefore have
// to be abstracted out in the accessor, which is external to the inlined method
val localRefs = qualType.namedPartsWith(ref =>
ref.isType && ref.symbol.isContainedIn(inlineSym)).toList

// Add qualifier type as leading method argument to argument `tp`
def addQualType(tp: Type): Type = tp match {
case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, addQualType(tp.resultType))
case tp: ExprType => addQualType(tp.resultType)
case tp => MethodType(qualType :: Nil, tp)
}

// Abstract accessed type over local refs
def abstractQualType(mtpe: Type): Type =
if (localRefs.isEmpty) mtpe
else PolyType.fromParams(localRefs.map(_.symbol.asType), mtpe)
.asInstanceOf[PolyType].flatten

val accessed = refPart.symbol.asTerm
val accessedType = refPart.tpe.widen
val accessor = accessorSymbol(
owner = inlineSym.owner,
accessorName = InlineAccessorName(UniqueInlineName.fresh(accessed.name)),
accessorInfo = abstractQualType(addQualType(dealiasMap(accessedType))),
accessed = accessed)

ref(accessor)
.appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs)
.appliedToArgss((qual :: Nil) :: argss)
.withPos(tree.pos)

// TODO: Handle references to non-public types.
// This is quite tricky, as such types can appear anywhere, including as parts
// of types of other things. For the moment we do nothing and complain
// at the implicit expansion site if there's a reference to an inaccessible type.
// Draft code (incomplete):
//
// val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType
// myAccessors += TypeDef(accessor).withPos(tree.pos.focus)
// ref(accessor).withPos(tree.pos)
//
case _ => tree
}
}

/** Adds accessors for all non-public term members accessed
Expand All @@ -76,7 +192,9 @@ object Inliner {
// Inline methods in local scopes can only be called in the scope they are defined,
// so no accessors are needed for them.
tree
else new MakeInlineable(inlineSym).transform(tree)
else
new MakeInlineablePassing(inlineSym).transform(
new MakeInlineableDirect(inlineSym).transform(tree))
}
}

Expand Down
19 changes: 19 additions & 0 deletions tests/pos/i4590.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package test

class ArrayDeque {
inline def isResizeNecessary(len: Int) = len > ArrayDeque.StableSize
}

object ArrayDeque {
private val StableSize = 256
}

class List {
inline def foo(x: List.Cons): Unit = {
x.next = this
}
}
object List {
case class Cons(head: Int, private[List] var next: List)
}

2 changes: 2 additions & 0 deletions tests/run/inline.check
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ Outer.f Inner
Inner
Outer.f
Outer.f Inner
(hi,1)
(true,)
6 changes: 4 additions & 2 deletions tests/run/inline/Test_2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ object Test {

val o = new Outer
val i = new o.Inner
val p = new TestPassing
println(i.m)
println(i.g)
println(i.h)
println(o.inner.m)
println(o.inner.g)
println(o.inner.h)
println(p.foo("hi"))
println(p.bar(true))
}

}
}
18 changes: 18 additions & 0 deletions tests/run/inline/inlines_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,22 @@ object inlines {
}
val inner = new Inner
}

class C[T](private[inlines] val x: T) {
private[inlines] def next[U](y: U): (T, U) = (xx, y)
private[inlines] var xx: T = _
}

class TestPassing {
inline def foo[A](x: A): (A, Int) = {
val c = new C[A](x)
c.xx = c.x
c.next(1)
}
inline def bar[A](x: A): (A, String) = {
val c = new C[A](x)
c.xx = c.x
c.next("")
}
}
}