Skip to content

Fix #4322: Avoid generating multiple inline accessors for the same ac… #4468

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 6 commits into from
May 23, 2018
Merged
Show file tree
Hide file tree
Changes from 3 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
23 changes: 19 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,26 @@ object Annotations {
def deferredResolve(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation =
deferred(atp.classSymbol, implicit ctx => resolveConstructor(atp, args))

def makeAlias(sym: TermSymbol)(implicit ctx: Context) =
apply(defn.AliasAnnot, List(
ref(TermRef(sym.owner.thisType, sym.name, sym))))
class TermRefAnnotExtractor(annotFn: Context => Symbol) {

/** Extractor for child annotations */
def apply(sym: TermSymbol)(implicit ctx: Context): Annotation =
Annotation(annotFn(ctx).typeRef, List(ref(TermRef(sym.owner.thisType, sym.name, sym))))

def unapply(ann: Annotation)(implicit ctx: Context): Option[Symbol] =
if (ann.symbol == annotFn(ctx)) {
val ast.Trees.Apply(fn, arg :: Nil) = ann.tree
Some(arg.symbol)
}
else None
}

/** Extractor for "accessed" annotations */
object Accessed extends TermRefAnnotExtractor(implicit ctx => defn.AccessedAnnot)

/** Extractor for "aliased" annotations */
object Alias extends TermRefAnnotExtractor(implicit ctx => defn.AliasAnnot)

/** Extractor for "child" annotations */
object Child {

/** A deferred annotation to the result of a given child computation */
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,8 @@ class Definitions {
// Annotation classes
lazy val AliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.Alias")
def AliasAnnot(implicit ctx: Context) = AliasAnnotType.symbol.asClass
lazy val AccessedAnnotType = ctx.requiredClassRef("scala.annotation.internal.Accessed")
def AccessedAnnot(implicit ctx: Context) = AccessedAnnotType.symbol.asClass
lazy val AnnotationDefaultAnnotType = ctx.requiredClassRef("scala.annotation.internal.AnnotationDefault")
def AnnotationDefaultAnnot(implicit ctx: Context) = AnnotationDefaultAnnotType.symbol.asClass
lazy val BodyAnnotType = ctx.requiredClassRef("scala.annotation.internal.Body")
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ object NameKinds {
}

/** Other unique names */
val InlineAccessorName = new UniqueNameKind("$_inlineAccessor_$")
val TempResultName = new UniqueNameKind("ev$")
val EvidenceParamName = new UniqueNameKind("evidence$")
val DepParamName = new UniqueNameKind("(param)")
Expand Down Expand Up @@ -356,6 +355,9 @@ object NameKinds {
val InitializerName = new PrefixNameKind(INITIALIZER, "initial$")
val ProtectedAccessorName = new PrefixNameKind(PROTECTEDACCESSOR, "protected$")
val ProtectedSetterName = new PrefixNameKind(PROTECTEDSETTER, "protected$set") // dubious encoding, kept for Scala2 compatibility
val InlineGetterName = new PrefixNameKind(INLINEGETTER, "inline_get$")
val InlineSetterName = new PrefixNameKind(INLINESETTER, "inline_set$")

val AvoidClashName = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$")
val DirectMethodName = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true }
val FieldName = new SuffixNameKind(FIELD, "$$local") {
Expand Down
14 changes: 7 additions & 7 deletions compiler/src/dotty/tools/dotc/core/NameTags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,23 @@ object NameTags extends TastyFormat.NameTags {
// outer accessor that will be filled in by ExplicitOuter.
// <num> indicates the number of hops needed to select the outer field.

final val INITIALIZER = 24 // A mixin initializer method
final val INITIALIZER = 26 // A mixin initializer method

final val AVOIDCLASH = 25 // Adds a suffix to avoid a name clash;
final val AVOIDCLASH = 27 // Adds a suffix to avoid a name clash;
// Used in FirstTransform for synthesized companion objects of classes
// if they would clash with another value.

final val DIRECT = 26 // Used by ShortCutImplicits for the name of methods that
final val DIRECT = 28 // Used by ShortCutImplicits for the name of methods that
// implement implicit function result types directly.

final val FIELD = 27 // Used by Memoize to tag the name of a class member field.
final val FIELD = 29 // Used by Memoize to tag the name of a class member field.

final val EXTMETH = 28 // Used by ExtensionMethods for the name of an extension method
final val EXTMETH = 30 // Used by ExtensionMethods for the name of an extension method
// implementing a value class method.

final val ADAPTEDCLOSURE = 29 // Used in Erasure to adapt closures over primitive types.
final val ADAPTEDCLOSURE = 31 // Used in Erasure to adapt closures over primitive types.

final val IMPLMETH = 30 // Used to define methods in implementation classes
final val IMPLMETH = 32 // Used to define methods in implementation classes
// (can probably be removed).

def nameTagToString(tag: Int): String = tag match {
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ object TastyFormat {

final val header = Array(0x5C, 0xA1, 0xAB, 0x1F)
val MajorVersion = 7
val MinorVersion = 0
val MinorVersion = 1

/** Tags used to serialize names */
class NameTags {
Expand Down Expand Up @@ -260,6 +260,10 @@ object TastyFormat {

final val OBJECTCLASS = 23 // The name of an object class (or: module class) `<name>$`.

final val INLINEGETTER = 24 // The name of an inline getter `inline_get$name`

final val INLINESETTER = 25 // The name of an inline setter `inline_set$name`

final val SIGNED = 63 // A pair of a name and a signature, used to idenitfy
// possibly overloaded methods.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
}
}
val alias = readDisambiguatedSymbolRef(disambiguate).asTerm
denot.addAnnotation(Annotation.makeAlias(alias))
denot.addAnnotation(Annotation.Alias(alias))
}
}
// println(s"unpickled ${denot.debugString}, info = ${denot.info}") !!! DEBUG
Expand Down
187 changes: 57 additions & 130 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.InlineAccessorName
import NameKinds.{ClassifiedNameKind, InlineGetterName, InlineSetterName}
import ProtoTypes.selectionProto
import SymDenotations.SymDenotation
import Annotations._
Expand Down Expand Up @@ -47,7 +47,6 @@ object Inliner {
* from inlined code. Accessors are collected in the `accessors` buffer.
*/
object addAccessors extends TreeMap {
val accessors = new mutable.ListBuffer[MemberDef]

/** A definition needs an accessor if it is private, protected, or qualified private
* and it is not part of the tree that gets inlined. The latter test is implemented
Expand All @@ -58,131 +57,64 @@ object Inliner {
(sym.is(AccessFlags) || sym.privateWithin.exists) &&
!sym.owner.isContainedIn(inlineMethod)

/** The name of the next accessor to be generated */
def accessorName(implicit ctx: Context) = InlineAccessorName.fresh(inlineMethod.name.asTermName)

/** A fresh accessor symbol.
*
* @param tree The tree representing the original access to the non-public member
* @param accessorInfo The type of the accessor
*/
def accessorSymbol(tree: Tree, accessorInfo: Type)(implicit ctx: Context): Symbol =
ctx.newSymbol(
owner = inlineMethod.owner,
name = if (tree.isTerm) accessorName.toTermName else accessorName.toTypeName,
flags = if (tree.isTerm) Synthetic | Method else Synthetic,
info = accessorInfo,
coord = tree.pos).entered

/** Add an accessor to a non-public method and replace the original access with a
* call to the accessor.
def newAccessorSymbol(accessed: TermSymbol, name: TermName, info: Type)(implicit ctx: Context): TermSymbol =
ctx.newSymbol(accessed.owner, name, Synthetic | Method, info, coord = accessed.pos).entered

/** Create an inline accessor unless one exists already, and replace the original
* access with a reference to the accessor.
*
* @param tree The original access to the non-public symbol
* @param refPart The part that refers to the method or field of the original access
* @param targs All type arguments passed in the access, if any
* @param argss All value arguments passed in the access, if any
* @param accessedType The type of the accessed method or field, as seen from the access site.
* @param rhs A function that builds the right-hand side of the accessor,
* given a reference to the accessed symbol and any type and
* value arguments the need to be integrated.
* @return The call to the accessor method that replaces the original access.
* @param reference The original reference to the non-public symbol
* @param onLHS The reference is on the left-hand side of an assignment
*/
def addAccessor(tree: Tree, refPart: Tree, targs: List[Tree], argss: List[List[Tree]],
accessedType: Type, rhs: (Tree, List[Type], List[List[Tree]]) => Tree)(implicit ctx: Context): Tree = {
val qual = qualifier(refPart)
def refIsLocal = qual match {
case qual: This => qual.symbol == refPart.symbol.owner
def useAccessor(reference: RefTree, onLHS: Boolean)(implicit ctx: Context): Tree = {

def nameKind = if (onLHS) InlineSetterName else InlineGetterName
val accessed = reference.symbol.asTerm

def refersToAccessed(sym: Symbol) = sym.getAnnotation(defn.AccessedAnnot) match {
case Some(Annotation.Accessed(sym)) => sym `eq` accessed
case _ => false
}
val (accessorDef, accessorRef) =
if (refPart.symbol.isStatic || refIsLocal) {
// Easy case: Reference to a static symbol or a symbol referenced via `this.`
val accessorType = accessedType.ensureMethodic
val accessor = accessorSymbol(tree, accessorType).asTerm
val accessorDef = polyDefDef(accessor, tps => argss =>
rhs(refPart, tps, argss).withPos(tree.pos.focus))
val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss).withPos(tree.pos)
(accessorDef, accessorRef)
} else {
// Hard case: Reference needs to go via a dynamic prefix
inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))")

// Need to dealias in order to catch 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)

// 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)
val accessorInfo =
if (onLHS) MethodType(accessed.info :: Nil, defn.UnitType)
else accessed.info.ensureMethodic
val accessorName = nameKind(accessed.name)
val accessorSymbol =
accessed.owner.info.decl(accessorName).suchThat(refersToAccessed).symbol
.orElse {
val acc = newAccessorSymbol(accessed, accessorName, accessorInfo)
acc.addAnnotation(Annotation.Accessed(accessed))
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably go in newAccessorSymbol

acc
}

// 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(inlineMethod)).toList

// 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 accessorType = abstractQualType(addQualType(dealiasMap(accessedType)))
val accessor = accessorSymbol(tree, accessorType).asTerm

val accessorDef = polyDefDef(accessor, tps => argss =>
rhs(argss.head.head.select(refPart.symbol), tps.drop(localRefs.length), argss.tail)
.withPos(tree.pos.focus)
)

val accessorRef = ref(accessor)
.appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs)
.appliedToArgss((qual :: Nil) :: argss)
.withPos(tree.pos)
(accessorDef, accessorRef)
{ reference match {
case Select(qual, _) => qual.select(accessorSymbol)
case Ident(name) => ref(accessorSymbol)
}
accessors += accessorDef
inlining.println(i"added inline accessor: $accessorDef")
accessorRef
}.withPos(reference.pos)
}

// 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 {
tree match {
case _: Apply | _: TypeApply | _: RefTree if needsAccessor(tree.symbol) =>
if (tree.isTerm) {
val (methPart, targs, argss) = decomposeCall(tree)
if (methPart.symbol.isConstructor && needsAccessor(methPart.symbol)) {
ctx.error("Cannot use private constructors in inline methods", tree.pos)
tree // TODO: create a proper accessor for the private constructor
} else {
addAccessor(tree, methPart, targs, argss,
accessedType = methPart.tpe.widen,
rhs = (qual, tps, argss) => qual.appliedToTypes(tps).appliedToArgss(argss))
}
} else {
// 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)
//
tree
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 useAccessor(tree, onLHS = false)
case Assign(lhs: RefTree, rhs) if needsAccessor(lhs.symbol) =>
addAccessor(tree, lhs, Nil, (rhs :: Nil) :: Nil,
accessedType = MethodType(rhs.tpe.widen :: Nil, defn.UnitType),
rhs = (lhs, tps, argss) => lhs.becomes(argss.head.head))
case _ => tree
cpy.Apply(tree)(useAccessor(lhs, onLHS = true), List(rhs))
case _ =>
tree
}
}
}
Expand All @@ -191,12 +123,23 @@ 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 {
val tree1 = addAccessors.transform(tree)
flatTree(tree1 :: addAccessors.accessors.toList)
}
else addAccessors.transform(tree)
}

/** The inline accessor definitions that need to be added to class `cls` */
def accessorDefs(cls: Symbol)(implicit ctx: Context): List[DefDef] =
for (accessor <- cls.info.decls.filter(sym => sym.name.is(InlineGetterName) || sym.name.is(InlineSetterName)))
yield polyDefDef(accessor.asTerm, tps => argss => {
val Annotation.Accessed(accessed) = accessor.getAnnotation(defn.AccessedAnnot).get
val rhs =
if (accessor.name.is(InlineSetterName) &&
argss.nonEmpty && argss.head.nonEmpty) // defensive conditions
ref(accessed).becomes(argss.head.head)
else
ref(accessed).appliedToTypes(tps).appliedToArgss(argss)
rhs.withPos(accessed.pos)
})

/** Register inline info for given inline method `sym`.
*
* @param sym The symbol denotatioon of the inline method for which info is registered
Expand Down Expand Up @@ -228,27 +171,11 @@ object Inliner {
def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean =
sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot)

private def bodyAndAccessors(sym: SymDenotation)(implicit ctx: Context): (Tree, List[MemberDef]) =
sym.unforcedAnnotation(defn.BodyAnnot).get.tree match {
case Thicket(body :: accessors) => (body, accessors.asInstanceOf[List[MemberDef]])
case body => (body, Nil)
}

/** The body to inline for method `sym`.
* @pre hasBodyToInline(sym)
*/
def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree =
bodyAndAccessors(sym)._1

/** The accessors to non-public members needed by the inlinable body of `sym`.
* These accessors are dropped as a side effect of calling this method.
* @pre hasBodyToInline(sym)
*/
def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = {
val (body, accessors) = bodyAndAccessors(sym)
if (accessors.nonEmpty) sym.updateAnnotation(ConcreteBodyAnnotation(body))
accessors
}
sym.unforcedAnnotation(defn.BodyAnnot).get.tree

/** Try to inline a call to a `@inline` method. Fail with error if the maximal
* inline depth is exceeded.
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ReTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class ReTyper extends Typer with ReChecking {
override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult =
Implicits.NoMatchingImplicitsFailure
override def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = ()
override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil

override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): Tree = mdef
override protected def addInlineAccessorDefs(cls: Symbol, body: List[Tree])(implicit ctx: Context): List[Tree] = body
override protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(implicit ctx: Context): Unit = ()
}
Loading