From 32163215b183883f5c37ae0fd3342a4ae93a0090 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Fri, 14 Jun 2013 15:01:37 +0200 Subject: SI-7344 Specialize methods in private scopes This performs method specialization inside a scope other than a {class, trait, object}: could be another method or a value. This specialization is much simpler, since there is no need to record the new members in the class signature, their signatures are only visible locally. It works according to the usual logic: - we use normalizeMember to create the specialized symbols - we leave DefDef stubs in the tree that are later filled in by tree duplication and adaptation The solution is limited by SI-7579: since the duplicator loses the sym annotations when duplicating, this expansion and rewiring can only take place in code that has not been subject to duplication. You can see the test case for an example. Review by @dragos, @paulp or @axel22. --- .../tools/nsc/transform/SpecializeTypes.scala | 114 ++++++++++++++++----- test/files/specialized/SI-7344.scala | 53 ++++++++++ 2 files changed, 144 insertions(+), 23 deletions(-) create mode 100644 test/files/specialized/SI-7344.scala diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index f43e42c027..d14fcb3eb1 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -852,7 +852,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { debuglog("%s expands to %s in %s".format(sym, specMember.name.decode, pp(env))) info(specMember) = NormalizedMember(sym) newOverload(sym, specMember, env) - owner.info.decls.enter(specMember) + // if this is a class, we insert the normalized member in scope, + // if this is a method, there's no attached scope for it (EmptyScope) + val decls = owner.info.decls + if (decls != EmptyScope) + decls.enter(specMember) specMember } } @@ -1445,6 +1449,32 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } + /** Computes residual type parameters after rewiring, like "String" in the following example: + * ``` + * def specMe[@specialized T, U](t: T, u: U) = ??? + * specMe[Int, String](1, "2") => specMe$mIc$sp[String](1, "2") + * ``` + */ + def computeResidualTypeVars(baseTree: Tree, specMember: Symbol, specTree: Tree, baseTargs: List[Tree], env: TypeEnv): Tree = { + val residualTargs = symbol.info.typeParams zip baseTargs collect { + case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ + } + // See SI-5583. Don't know why it happens now if it didn't before. + if (specMember.info.typeParams.isEmpty && residualTargs.nonEmpty) { + devWarning("Type args to be applied, but symbol says no parameters: " + ((specMember.defString, residualTargs))) + baseTree + } + else { + ifDebug(assert(residualTargs.length == specMember.info.typeParams.length, + "residual: %s, tparams: %s, env: %s".format(residualTargs, specMember.info.typeParams, env)) + ) + + val tree1 = gen.mkTypeApply(specTree, residualTargs) + debuglog("rewrote " + tree + " to " + tree1) + localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method + } + } + curTree = tree tree match { case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => @@ -1470,12 +1500,16 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } transformSuperApply + // This rewires calls to specialized methods defined in a class (which have a receiver) + // class C { + // def foo[@specialized T](t: T): T = t + // C.this.foo(3) // TypeApply(Select(This(C), foo), List(Int)) => C.this.foo$mIc$sp(3) + // } case TypeApply(sel @ Select(qual, name), targs) - if (!specializedTypeVars(symbol.info).isEmpty && name != nme.CONSTRUCTOR) => - def transformTypeApply = { + if (specializedTypeVars(symbol.info).nonEmpty && name != nme.CONSTRUCTOR) => debuglog("checking typeapp for rerouting: " + tree + " with sym.tpe: " + symbol.tpe + " tree.tpe: " + tree.tpe) val qual1 = transform(qual) - // log(">>> TypeApply: " + tree + ", qual1: " + qual1) + log(">>> TypeApply: " + tree + ", qual1: " + qual1) specSym(qual1) match { case NoSymbol => // See pos/exponential-spec.scala - can't call transform on the whole tree again. @@ -1485,26 +1519,23 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { ifDebug(assert(symbol.info.typeParams.length == targs.length, symbol.info.typeParams + " / " + targs)) val env = typeEnv(specMember) - val residualTargs = symbol.info.typeParams zip targs collect { - case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ - } - // See SI-5583. Don't know why it happens now if it didn't before. - if (specMember.info.typeParams.isEmpty && residualTargs.nonEmpty) { - devWarning("Type args to be applied, but symbol says no parameters: " + ((specMember.defString, residualTargs))) - localTyper.typed(sel) - } - else { - ifDebug(assert(residualTargs.length == specMember.info.typeParams.length, - "residual: %s, tparams: %s, env: %s".format(residualTargs, specMember.info.typeParams, env)) - ) - - val tree1 = gen.mkTypeApply(Select(qual1, specMember), residualTargs) - debuglog("rewrote " + tree + " to " + tree1) - localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method - } + computeResidualTypeVars(tree, specMember, gen.mkAttributedSelect(qual1, specMember), targs, env) } + + // This rewires calls to specialized methods defined in the local scope. For example: + // def outerMethod = { + // def foo[@specialized T](t: T): T = t + // foo(3) // TypeApply(Ident(foo), List(Int)) => foo$mIc$sp(3) + // } + case TypeApply(sel @ Ident(name), targs) if name != nme.CONSTRUCTOR => + val env = unify(symbol.tpe, tree.tpe, emptyEnv, false) + if (env.isEmpty) super.transform(tree) + else { + overloads(symbol) find (_ matchesEnv env) match { + case Some(Overload(specMember, _)) => computeResidualTypeVars(tree, specMember, Ident(specMember), targs, env) + case _ => super.transform(tree) + } } - transformTypeApply case Select(Super(_, _), _) if illegalSpecializedInheritance(currentClass) => val pos = tree.pos @@ -1621,7 +1652,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { localTyper.typed(deriveDefDef(tree)(rhs => rhs)) } } - transformDefDef + expandInnerNormalizedMembers(transformDefDef) + + case ddef @ DefDef(_, _, _, _, _, _) => + val tree1 = expandInnerNormalizedMembers(tree) + super.transform(tree1) case ValDef(_, _, _, _) if symbol.hasFlag(SPECIALIZED) && !symbol.isParamAccessor => def transformValDef = { @@ -1645,6 +1680,39 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } + /** + * This performs method specialization inside a scope other than a {class, trait, object}: could be another method + * or a value. This specialization is much simpler, since there is no need to record the new members in the class + * signature, their signatures are only visible locally. It works according to the usual logic: + * - we use normalizeMember to create the specialized symbols + * - we leave DefDef stubs in the tree that are later filled in by tree duplication and adaptation + * @see duplicateBody + */ + private def expandInnerNormalizedMembers(tree: Tree) = tree match { + case ddef @ DefDef(_, _, _, vparams :: Nil, _, rhs) + if ddef.symbol.owner.isMethod && + specializedTypeVars(ddef.symbol.info).nonEmpty && + !ddef.symbol.hasFlag(SPECIALIZED) => + + val sym = ddef.symbol + val owner = sym.owner + val norm = normalizeMember(owner, sym, emptyEnv) + + if (norm.length > 1) { + // record the body for duplication + body(sym) = rhs + parameters(sym) = vparams.map(_.symbol) + // to avoid revisiting the member, we can set the SPECIALIZED + // flag. nobody has to see this anyway :) + sym.setFlag(SPECIALIZED) + // create empty bodies for specializations + localTyper.typed(Block(norm.tail.map(sym => DefDef(sym, { vparamss => EmptyTree })), ddef)) + } else + tree + case _ => + tree + } + /** Duplicate the body of the given method `tree` to the new symbol `source`. * * Knowing that the method can be invoked only in the `castmap` type environment, diff --git a/test/files/specialized/SI-7344.scala b/test/files/specialized/SI-7344.scala new file mode 100644 index 0000000000..1040460bd1 --- /dev/null +++ b/test/files/specialized/SI-7344.scala @@ -0,0 +1,53 @@ +/* Test for SI-7344, where specialized methods inside the bodies of other + * methods are not specialized, although they might as well be. The name + * for the specialized method should not be different depending on the + * outside method/class' specialization. */ + +class Test[@specialized(Int, Double) X](val x: X) { + + def checkSpecialization[Y](@specialized(Int, Double) y: Y): X = { + + // checking the specialization using the method name, which we can + // extract from an exception's stack trace. We can match just the + // prefix, since the compiler will add a suffix to the method name + // during lambdalift, when it lifts the local methods outside. + def specMe[@specialized(Int, Double) T, N](t: T, n: N): Unit = checkNameStartsWith(n.toString) + + // expected to specialize: + specMe("x", "specMe") + specMe(123, "specMe$mIc$sp") + specMe(1.3, new { override def toString = "specMe$mDc$sp" }) + + x + } + + // name matching: + private[this] def checkNameStartsWith(prefix: String): Unit = { + val method = (new Exception).getStackTrace()(1).getMethodName() + assert(method.startsWith(prefix), method + ".startsWith(" + prefix + ") should be true") + } +} + +object Test extends App { + val t1 = new Test("x") + val t2 = new Test(123) + val t3 = new Test(1.3) + + // we want specialization to rewire these, + // that's why they're not in a for loop: + t1.checkSpecialization("x") + + // Prevented by SI-7579: + // The duplicator loses the @specialized annotation, + // so our tree transformation doesn't know it needs to + // specialize specMe inside the duplicated (and specialized) + // variants of the `checkSpecialization` method + // t1.checkSpecialization(123) + // t1.checkSpecialization(1.3) + // t2.checkSpecialization("x") + // t2.checkSpecialization(123) + // t2.checkSpecialization(1.3) + // t3.checkSpecialization("x") + // t3.checkSpecialization(123) + // t3.checkSpecialization(1.3) +} -- cgit v1.2.3