summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2012-02-22 19:54:36 -0800
committerPaul Phillips <paulp@improving.org>2012-02-22 20:09:40 -0800
commit06384c052ec31db4bd094b949bed0f3cb3fb644b (patch)
tree221fe6e2905ebfdf186cc7c828c729ea65450789
parenta983f2b30c352f3d62f0ac615044dcd45b1b1618 (diff)
downloadscala-06384c052ec31db4bd094b949bed0f3cb3fb644b.tar.gz
scala-06384c052ec31db4bd094b949bed0f3cb3fb644b.tar.bz2
scala-06384c052ec31db4bd094b949bed0f3cb3fb644b.zip
Reworked and restored elidable.
Found a better elidable implementation which is robust against other parts of the compiler doing their things. Calls to elidable methods are replaced with zero of the same type. Elidable methods themselves remain in place, but with their body replaced with a zero of the method return type. Thus is everything to be found where it is expected to be found, but nothing will be found where nothing ought to be found. Nothing of course will never be found.
-rw-r--r--src/compiler/scala/reflect/internal/TreeGen.scala27
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala190
-rw-r--r--test/files/neg/elide-to-nothing.check4
-rw-r--r--test/files/neg/elide-to-nothing.flags1
-rw-r--r--test/files/neg/elide-to-nothing.scala31
-rw-r--r--test/files/run/elidable-opt.check14
-rw-r--r--test/files/run/elidable-opt.flags1
-rw-r--r--test/files/run/elidable-opt.scala85
-rw-r--r--test/files/run/elidable.check15
-rw-r--r--test/files/run/elidable.scala73
10 files changed, 323 insertions, 118 deletions
diff --git a/src/compiler/scala/reflect/internal/TreeGen.scala b/src/compiler/scala/reflect/internal/TreeGen.scala
index e537c6b83f..cc882ad5ed 100644
--- a/src/compiler/scala/reflect/internal/TreeGen.scala
+++ b/src/compiler/scala/reflect/internal/TreeGen.scala
@@ -250,20 +250,19 @@ abstract class TreeGen {
* var x: T = _
* which is appropriate to the given Type.
*/
- def mkZero(tp: Type): Tree = {
- val tree = tp.typeSymbol match {
- case UnitClass => Literal(Constant())
- case BooleanClass => Literal(Constant(false))
- case FloatClass => Literal(Constant(0.0f))
- case DoubleClass => Literal(Constant(0.0d))
- case ByteClass => Literal(Constant(0.toByte))
- case ShortClass => Literal(Constant(0.toShort))
- case IntClass => Literal(Constant(0))
- case LongClass => Literal(Constant(0L))
- case CharClass => Literal(Constant(0.toChar))
- case _ => Literal(Constant(null))
- }
- tree setType tp
+ def mkZero(tp: Type): Tree = Literal(mkConstantZero(tp)) setType tp
+
+ def mkConstantZero(tp: Type): Constant = tp.typeSymbol match {
+ case UnitClass => Constant(())
+ case BooleanClass => Constant(false)
+ case FloatClass => Constant(0.0f)
+ case DoubleClass => Constant(0.0d)
+ case ByteClass => Constant(0.toByte)
+ case ShortClass => Constant(0.toShort)
+ case IntClass => Constant(0)
+ case LongClass => Constant(0L)
+ case CharClass => Constant(0.toChar)
+ case _ => Constant(null)
}
def mkZeroContravariantAfterTyper(tp: Type): Tree = {
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index b458d305a5..69bf006036 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -452,16 +452,17 @@ abstract class UnCurry extends InfoTransform
}
}
- /** For removing calls to specially designated methods.
+ /** Called if a tree's symbol is elidable. If it's a DefDef,
+ * replace only the body/rhs with 0/false/()/null; otherwise replace
+ * the whole tree with it.
*/
- def elideIntoUnit(tree: Tree): Tree = Literal(Constant()) setPos tree.pos setType UnitClass.tpe
- def isElidable(tree: Tree) = {
- val sym = treeInfo.methPart(tree).symbol
- // XXX settings.noassertions.value temporarily retained to avoid
- // breakage until a reasonable interface is settled upon.
- sym != null && sym.elisionLevel.exists(x => x < settings.elidebelow.value || settings.noassertions.value) && {
- log("Eliding call from " + tree.symbol.owner + " to " + sym + " based on its elision threshold of " + sym.elisionLevel.get)
- true
+ private def replaceElidableTree(tree: Tree): Tree = {
+ tree match {
+ case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
+ val newRhs = Block(Nil, gen.mkZero(rhs.tpe)) setType rhs.tpe
+ treeCopy.DefDef(tree, mods, name, tparams, vparamss, tpt, newRhs) setSymbol tree.symbol setType tree.tpe
+ case _ =>
+ gen.mkZero(tree.tpe) setType tree.tpe
}
}
@@ -502,109 +503,96 @@ abstract class UnCurry extends InfoTransform
finally this.inConstructorFlag = saved
}
- if (isElidable(tree)) elideIntoUnit(tree)
- else tree match {
- case dd @ DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
- if (dd.symbol hasAnnotation VarargsClass) saveRepeatedParams(dd)
- withNeedLift(false) {
- if (tree.symbol.isClassConstructor) {
- atOwner(tree.symbol) {
- val rhs1 = (rhs: @unchecked) match {
- case Block(stats, expr) =>
- def transformInConstructor(stat: Tree) =
- withInConstructorFlag(INCONSTRUCTOR) { transform(stat) }
- val presupers = treeInfo.preSuperFields(stats) map transformInConstructor
- val rest = stats drop presupers.length
- val supercalls = rest take 1 map transformInConstructor
- val others = rest drop 1 map transform
- treeCopy.Block(rhs, presupers ::: supercalls ::: others, transform(expr))
+ val sym = tree.symbol
+ val result = (
+ // TODO - settings.noassertions.value temporarily retained to avoid
+ // breakage until a reasonable interface is settled upon.
+ if ((sym ne null) && (sym.elisionLevel.exists (_ < settings.elidebelow.value || settings.noassertions.value)))
+ super.transform(replaceElidableTree(tree))
+ else tree match {
+ case dd @ DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
+ if (dd.symbol hasAnnotation VarargsClass) saveRepeatedParams(dd)
+ withNeedLift(false) {
+ if (dd.symbol.isClassConstructor) {
+ atOwner(sym) {
+ val rhs1 = (rhs: @unchecked) match {
+ case Block(stats, expr) =>
+ def transformInConstructor(stat: Tree) =
+ withInConstructorFlag(INCONSTRUCTOR) { transform(stat) }
+ val presupers = treeInfo.preSuperFields(stats) map transformInConstructor
+ val rest = stats drop presupers.length
+ val supercalls = rest take 1 map transformInConstructor
+ val others = rest drop 1 map transform
+ treeCopy.Block(rhs, presupers ::: supercalls ::: others, transform(expr))
+ }
+ treeCopy.DefDef(
+ tree, mods, name, transformTypeDefs(tparams),
+ transformValDefss(vparamss), transform(tpt), rhs1)
}
- treeCopy.DefDef(
- tree, mods, name, transformTypeDefs(tparams),
- transformValDefss(vparamss), transform(tpt), rhs1)
+ } else {
+ super.transform(tree)
}
- } else {
- super.transform(tree)
- }
- }
- case ValDef(_, _, _, rhs) =>
- val sym = tree.symbol
- if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit)
- // a local variable that is mutable and free somewhere later should be lifted
- // as lambda lifting (coming later) will wrap 'rhs' in an Ref object.
- if (!sym.owner.isSourceMethod)
- withNeedLift(true) { super.transform(tree) }
- else
- super.transform(tree)
-/*
- case Apply(Select(Block(List(), Function(vparams, body)), nme.apply), args) =>
- // perform beta-reduction; this helps keep view applications small
- println("beta-reduce1: "+tree)
- withNeedLift(true) {
- mainTransform(new TreeSubstituter(vparams map (_.symbol), args).transform(body))
- }
-
- case Apply(Select(Function(vparams, body), nme.apply), args) =>
-// if (List.forall2(vparams, args)((vparam, arg) => treeInfo.isAffineIn(body) ||
-// treeInfo.isExprSafeToInline(arg))) =>
- // perform beta-reduction; this helps keep view applications small
- println("beta-reduce2: "+tree)
- withNeedLift(true) {
- mainTransform(new TreeSubstituter(vparams map (_.symbol), args).transform(body))
- }
-*/
- case UnApply(fn, args) =>
- val fn1 = withInPattern(false)(transform(fn))
- val args1 = transformTrees(fn.symbol.name match {
- case nme.unapply => args
- case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeListFromReturnTypeSeq(fn.tpe))
- case _ => sys.error("internal error: UnApply node has wrong symbol")
- })
- treeCopy.UnApply(tree, fn1, args1)
-
- case Apply(fn, args) =>
- if (fn.symbol == Object_synchronized && shouldBeLiftedAnyway(args.head))
- transform(treeCopy.Apply(tree, fn, List(liftTree(args.head))))
- else
- withNeedLift(true) {
- val formals = fn.tpe.paramTypes
- treeCopy.Apply(tree, transform(fn), transformTrees(transformArgs(tree.pos, fn.symbol, args, formals)))
}
+ case ValDef(_, _, _, rhs) =>
+ if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit)
+ // a local variable that is mutable and free somewhere later should be lifted
+ // as lambda lifting (coming later) will wrap 'rhs' in an Ref object.
+ if (!sym.owner.isSourceMethod)
+ withNeedLift(true) { super.transform(tree) }
+ else
+ super.transform(tree)
+ case UnApply(fn, args) =>
+ val fn1 = withInPattern(false)(transform(fn))
+ val args1 = transformTrees(fn.symbol.name match {
+ case nme.unapply => args
+ case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeListFromReturnTypeSeq(fn.tpe))
+ case _ => sys.error("internal error: UnApply node has wrong symbol")
+ })
+ treeCopy.UnApply(tree, fn1, args1)
+
+ case Apply(fn, args) =>
+ if (fn.symbol == Object_synchronized && shouldBeLiftedAnyway(args.head))
+ transform(treeCopy.Apply(tree, fn, List(liftTree(args.head))))
+ else
+ withNeedLift(true) {
+ val formals = fn.tpe.paramTypes
+ treeCopy.Apply(tree, transform(fn), transformTrees(transformArgs(tree.pos, fn.symbol, args, formals)))
+ }
- case Assign(Select(_, _), _) =>
- withNeedLift(true) { super.transform(tree) }
+ case Assign(Select(_, _), _) =>
+ withNeedLift(true) { super.transform(tree) }
- case Assign(lhs, _) if lhs.symbol.owner != currentMethod || lhs.symbol.hasFlag(LAZY | ACCESSOR) =>
- withNeedLift(true) { super.transform(tree) }
+ case Assign(lhs, _) if lhs.symbol.owner != currentMethod || lhs.symbol.hasFlag(LAZY | ACCESSOR) =>
+ withNeedLift(true) { super.transform(tree) }
- case Try(block, catches, finalizer) =>
- if (needTryLift || shouldBeLiftedAnyway(tree)) transform(liftTree(tree))
- else super.transform(tree)
+ case Try(block, catches, finalizer) =>
+ if (needTryLift || shouldBeLiftedAnyway(tree)) transform(liftTree(tree))
+ else super.transform(tree)
- case CaseDef(pat, guard, body) =>
- val pat1 = withInPattern(true)(transform(pat))
- treeCopy.CaseDef(tree, pat1, transform(guard), transform(body))
+ case CaseDef(pat, guard, body) =>
+ val pat1 = withInPattern(true)(transform(pat))
+ treeCopy.CaseDef(tree, pat1, transform(guard), transform(body))
- case fun @ Function(_, _) =>
- mainTransform(transformFunction(fun))
+ case fun @ Function(_, _) =>
+ mainTransform(transformFunction(fun))
- case Template(_, _, _) =>
- withInConstructorFlag(0) { super.transform(tree) }
+ case Template(_, _, _) =>
+ withInConstructorFlag(0) { super.transform(tree) }
- case _ =>
- val tree1 = super.transform(tree)
- if (isByNameRef(tree1)) {
- val tree2 = tree1 setType functionType(Nil, tree1.tpe)
- return {
- if (noApply contains tree2) tree2
- else localTyper.typedPos(tree1.pos)(Apply(Select(tree2, nme.apply), Nil))
+ case _ =>
+ val tree1 = super.transform(tree)
+ if (isByNameRef(tree1)) {
+ val tree2 = tree1 setType functionType(Nil, tree1.tpe)
+ return {
+ if (noApply contains tree2) tree2
+ else localTyper.typedPos(tree1.pos)(Apply(Select(tree2, nme.apply), Nil))
+ }
}
- }
- tree1
- }
- } setType {
- assert(tree.tpe != null, tree + " tpe is null")
- uncurryTreeType(tree.tpe)
+ tree1
+ }
+ )
+ assert(result.tpe != null, result + " tpe is null")
+ result setType uncurryTreeType(result.tpe)
}
def postTransform(tree: Tree): Tree = atPhase(phase.next) {
diff --git a/test/files/neg/elide-to-nothing.check b/test/files/neg/elide-to-nothing.check
new file mode 100644
index 0000000000..3ef05aac9a
--- /dev/null
+++ b/test/files/neg/elide-to-nothing.check
@@ -0,0 +1,4 @@
+elide-to-nothing.scala:14: error: Cannot elide where Nothing is required.
+ val b: Nothing = unimplemented()
+ ^
+one error found
diff --git a/test/files/neg/elide-to-nothing.flags b/test/files/neg/elide-to-nothing.flags
new file mode 100644
index 0000000000..59a512e547
--- /dev/null
+++ b/test/files/neg/elide-to-nothing.flags
@@ -0,0 +1 @@
+-Xelide-below 500
diff --git a/test/files/neg/elide-to-nothing.scala b/test/files/neg/elide-to-nothing.scala
new file mode 100644
index 0000000000..5008e8bc1d
--- /dev/null
+++ b/test/files/neg/elide-to-nothing.scala
@@ -0,0 +1,31 @@
+
+/** Test which should fail compilation */
+class ElysianFailed {
+
+ import ElysianField._
+
+ // fine
+ val a: Int = myInt
+
+ // fine
+ unimplemented()
+
+ // not fine
+ val b: Nothing = unimplemented()
+
+}
+
+object ElysianField {
+
+ import annotation.elidable
+
+ @elidable(100) def unimplemented(): Nothing = throw new UnsupportedOperationException
+
+ @elidable(100) def myInt: Int = 17
+
+}
+
+
+
+
+
diff --git a/test/files/run/elidable-opt.check b/test/files/run/elidable-opt.check
new file mode 100644
index 0000000000..88cf98e0d1
--- /dev/null
+++ b/test/files/run/elidable-opt.check
@@ -0,0 +1,14 @@
+Good for me, I was not elided. Test.f3
+Good for me, I was not elided. O.f3
+Good for me, I was not elided. C.f1
+Good for me, I was not elided. C.f2
+()
+false
+0
+0
+0
+0
+0
+0.0
+0.0
+null
diff --git a/test/files/run/elidable-opt.flags b/test/files/run/elidable-opt.flags
new file mode 100644
index 0000000000..62897ff218
--- /dev/null
+++ b/test/files/run/elidable-opt.flags
@@ -0,0 +1 @@
+-optimise -Xelide-below 900
diff --git a/test/files/run/elidable-opt.scala b/test/files/run/elidable-opt.scala
new file mode 100644
index 0000000000..a2f29d2caf
--- /dev/null
+++ b/test/files/run/elidable-opt.scala
@@ -0,0 +1,85 @@
+import annotation._
+import elidable._
+
+trait T {
+ @elidable(FINEST) def f1()
+ @elidable(SEVERE) def f2()
+ @elidable(FINEST) def f3() = assert(false, "Should have been elided.")
+ def f4()
+}
+
+class C extends T {
+ def f1() = println("Good for me, I was not elided. C.f1")
+ def f2() = println("Good for me, I was not elided. C.f2")
+ @elidable(FINEST) def f4() = assert(false, "Should have been elided.")
+}
+
+object O {
+ @elidable(FINEST) def f1() = assert(false, "Should have been elided.")
+ @elidable(INFO) def f2() = assert(false, "Should have been elided.")
+ @elidable(SEVERE) def f3() = println("Good for me, I was not elided. O.f3")
+ @elidable(INFO) def f4 = assert(false, "Should have been elided (no parens).")
+}
+
+object Test {
+ @elidable(FINEST) def f1() = assert(false, "Should have been elided.")
+ @elidable(INFO) def f2() = assert(false, "Should have been elided.")
+ @elidable(SEVERE) def f3() = println("Good for me, I was not elided. Test.f3")
+ @elidable(INFO) def f4 = assert(false, "Should have been elided (no parens).")
+
+ @elidable(FINEST) def f5() = {}
+ @elidable(FINEST) def f6() = true
+ @elidable(FINEST) def f7() = 1:Byte
+ @elidable(FINEST) def f8() = 1:Short
+ @elidable(FINEST) def f9() = 1:Char
+ @elidable(FINEST) def fa() = 1
+ @elidable(FINEST) def fb() = 1l
+ @elidable(FINEST) def fc() = 1.0f
+ @elidable(FINEST) def fd() = 1.0
+ @elidable(FINEST) def fe() = "s"
+
+ def main(args: Array[String]): Unit = {
+ f1()
+ f2()
+ f3()
+ f4
+ O.f1()
+ O.f2()
+ O.f3()
+ O.f4
+
+ val c = new C
+ c.f1()
+ c.f2()
+ c.f3()
+ c.f4()
+
+ // make sure a return value is still available when eliding a call
+ println(f5())
+ println(f6())
+ println(f7())
+ println(f8())
+ println(f9().toInt)
+ println(fa())
+ println(fb())
+ println(fc())
+ println(fd())
+ println(fe())
+
+ // this one won't show up in the output because a call to f1 is elidable when accessed through T
+ (c:T).f1()
+
+ // Test whether the method definitions are still available.
+ List("Test", "Test$", "O", "O$", "C", "T") foreach { className =>
+ List("f1", "f2", "f3", "f4") foreach { methodName =>
+ Class.forName(className).getMethod(methodName)
+ }
+ }
+ List("Test", "Test$") foreach { className =>
+ List("f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe") foreach { methodName =>
+ Class.forName(className).getMethod(methodName)
+ }
+ }
+ Class.forName("T$class").getMethod("f3", classOf[T])
+ }
+}
diff --git a/test/files/run/elidable.check b/test/files/run/elidable.check
index 4ce04f0040..88cf98e0d1 100644
--- a/test/files/run/elidable.check
+++ b/test/files/run/elidable.check
@@ -1 +1,14 @@
-Good for me, I was not elided.
+Good for me, I was not elided. Test.f3
+Good for me, I was not elided. O.f3
+Good for me, I was not elided. C.f1
+Good for me, I was not elided. C.f2
+()
+false
+0
+0
+0
+0
+0
+0.0
+0.0
+null
diff --git a/test/files/run/elidable.scala b/test/files/run/elidable.scala
index 264efbad59..a2f29d2caf 100644
--- a/test/files/run/elidable.scala
+++ b/test/files/run/elidable.scala
@@ -1,16 +1,85 @@
import annotation._
import elidable._
+trait T {
+ @elidable(FINEST) def f1()
+ @elidable(SEVERE) def f2()
+ @elidable(FINEST) def f3() = assert(false, "Should have been elided.")
+ def f4()
+}
+
+class C extends T {
+ def f1() = println("Good for me, I was not elided. C.f1")
+ def f2() = println("Good for me, I was not elided. C.f2")
+ @elidable(FINEST) def f4() = assert(false, "Should have been elided.")
+}
+
+object O {
+ @elidable(FINEST) def f1() = assert(false, "Should have been elided.")
+ @elidable(INFO) def f2() = assert(false, "Should have been elided.")
+ @elidable(SEVERE) def f3() = println("Good for me, I was not elided. O.f3")
+ @elidable(INFO) def f4 = assert(false, "Should have been elided (no parens).")
+}
+
object Test {
@elidable(FINEST) def f1() = assert(false, "Should have been elided.")
@elidable(INFO) def f2() = assert(false, "Should have been elided.")
- @elidable(SEVERE) def f3() = println("Good for me, I was not elided.")
+ @elidable(SEVERE) def f3() = println("Good for me, I was not elided. Test.f3")
@elidable(INFO) def f4 = assert(false, "Should have been elided (no parens).")
-
+
+ @elidable(FINEST) def f5() = {}
+ @elidable(FINEST) def f6() = true
+ @elidable(FINEST) def f7() = 1:Byte
+ @elidable(FINEST) def f8() = 1:Short
+ @elidable(FINEST) def f9() = 1:Char
+ @elidable(FINEST) def fa() = 1
+ @elidable(FINEST) def fb() = 1l
+ @elidable(FINEST) def fc() = 1.0f
+ @elidable(FINEST) def fd() = 1.0
+ @elidable(FINEST) def fe() = "s"
+
def main(args: Array[String]): Unit = {
f1()
f2()
f3()
f4
+ O.f1()
+ O.f2()
+ O.f3()
+ O.f4
+
+ val c = new C
+ c.f1()
+ c.f2()
+ c.f3()
+ c.f4()
+
+ // make sure a return value is still available when eliding a call
+ println(f5())
+ println(f6())
+ println(f7())
+ println(f8())
+ println(f9().toInt)
+ println(fa())
+ println(fb())
+ println(fc())
+ println(fd())
+ println(fe())
+
+ // this one won't show up in the output because a call to f1 is elidable when accessed through T
+ (c:T).f1()
+
+ // Test whether the method definitions are still available.
+ List("Test", "Test$", "O", "O$", "C", "T") foreach { className =>
+ List("f1", "f2", "f3", "f4") foreach { methodName =>
+ Class.forName(className).getMethod(methodName)
+ }
+ }
+ List("Test", "Test$") foreach { className =>
+ List("f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe") foreach { methodName =>
+ Class.forName(className).getMethod(methodName)
+ }
+ }
+ Class.forName("T$class").getMethod("f3", classOf[T])
}
}