From 4aeaf9015befc4f16a962896daa7deda99e11605 Mon Sep 17 00:00:00 2001 From: Dominik Gruntz Date: Wed, 11 Jul 2012 20:10:30 +0200 Subject: SI-6061 adds weakly conformance for number types to resolveOverloaded This fix changes resolveOverloaded in scala.reflect.internal so that numeric widening on the actual argument types is also considered for primitive number types. Needs changes in functions applicable and mostSpecific. --- src/reflect/scala/reflect/internal/Symbols.scala | 16 ++++++++--- .../run/reflect-resolveoverload-invalid.scala | 2 +- test/files/run/reflect-resolveoverload2.scala | 31 +++++++++++++++------- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 77c27126c5..47e84b4918 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -93,6 +93,16 @@ trait Symbols extends api.Symbols { self: SymbolTable => // Begin Correlation Helpers + def weaklyConforms(a: Type, f: Type): Boolean = (a =:= f) || { + if(a =:= typeOf[Byte]) weaklyConforms(typeOf[Short], f) + else if(a =:= typeOf[Short]) weaklyConforms(typeOf[Int], f) + else if(a =:= typeOf[Char]) weaklyConforms(typeOf[Int], f) + else if(a =:= typeOf[Int]) weaklyConforms(typeOf[Long], f) + else if(a =:= typeOf[Long]) weaklyConforms(typeOf[Float], f) + else if(a =:= typeOf[Float]) weaklyConforms(typeOf[Double], f) + else false + } + def isCompatible(tp: Type, pt: Type): Boolean = { def isCompatibleByName(tp: Type, pt: Type): Boolean = pt match { case TypeRef(_, ByNameParamClass, List(res)) if !definitions.isByNameParamType(tp) => @@ -100,13 +110,13 @@ trait Symbols extends api.Symbols { self: SymbolTable => case _ => false } - (tp <:< pt) || isCompatibleByName(tp, pt) + (tp <:< pt) || weaklyConforms(tp, pt) || isCompatibleByName(tp, pt) } def signatureAsSpecific(method1: MethodSymbol, method2: MethodSymbol): Boolean = { (substituteTypeParams(method1), substituteTypeParams(method2)) match { case (NullaryMethodType(r1), NullaryMethodType(r2)) => - r1 <:< r2 + r1 <:< r2 || weaklyConforms(r1, r2) case (NullaryMethodType(_), MethodType(_, _)) => true case (MethodType(_, _), NullaryMethodType(_)) => @@ -298,7 +308,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => else { val a = argTypes val p = extend(paramTypes, argTypes.length) - (a corresponds p)(_ <:< _) + (a corresponds p)((a, f) => a <:< f || weaklyConforms(a, f)) } } } diff --git a/test/files/run/reflect-resolveoverload-invalid.scala b/test/files/run/reflect-resolveoverload-invalid.scala index def28ccbb4..8c5dc9f94b 100644 --- a/test/files/run/reflect-resolveoverload-invalid.scala +++ b/test/files/run/reflect-resolveoverload-invalid.scala @@ -27,7 +27,7 @@ object Test extends App { val d = t member u.newTermName("d") asTermSymbol val e = t member u.newTermName("e") asTermSymbol - val n1 = a.resolveOverloaded(posVargs = List(u.typeOf[Char])) + val n1 = a.resolveOverloaded(posVargs = List(u.typeOf[Long])) val n2 = b.resolveOverloaded(posVargs = List(u.typeOf[A])) val n3 = c.resolveOverloaded(posVargs = List(u.typeOf[B], u.typeOf[B])) val n4 = d.resolveOverloaded(targs = List(u.typeOf[Int])) diff --git a/test/files/run/reflect-resolveoverload2.scala b/test/files/run/reflect-resolveoverload2.scala index b5f719814b..a800a3e92c 100644 --- a/test/files/run/reflect-resolveoverload2.scala +++ b/test/files/run/reflect-resolveoverload2.scala @@ -2,16 +2,20 @@ class A class B extends A class C { - def a(x: Int) = 1 - def a(x: String) = 2 - //def b(x: => Int)(s: String) = 1 - //def b(x: => String)(a: Array[_]) = 2 - def c(x: A) = 1 - def c(x: B) = 2 - //def d(x: => A)(s: String) = 1 - //def d(x: => B)(a: Array[_]) = 2 - def e(x: A) = 1 - def e(x: B = new B) = 2 + def a(x: Int) = 1 + def a(x: String) = 2 + //def b(x: => Int)(s: String) = 1 + //def b(x: => String)(a: Array[_]) = 2 + def c(x: A) = 1 + def c(x: B) = 2 + //def d(x: => A)(s: String) = 1 + //def d(x: => B)(a: Array[_]) = 2 + def e(x: A) = 1 + def e(x: B = new B) = 2 + def f(x: Int) = 1 + def f(x: String) = 2 + def f(x: Long) = 3 + def f(x: Double) = 4 } object Test extends App { @@ -29,6 +33,8 @@ object Test extends App { } assert(c.a(1) == invoke("a", 1, u.typeOf[Int])) assert(c.a("a") == invoke("a", "a", u.typeOf[String])) + assert(c.a('a') == invoke("a", 'a', u.typeOf[Char])) + assert(c.a(3: Byte) == invoke("a", 3: Byte, u.typeOf[Byte])) //assert(c.b(1)(null) == invoke("b", 1, u.typeOf[Int])) //assert(c.b("a")(null) == invoke("b", "a", u.typeOf[String])) assert(c.c(new A) == invoke("c", new A, u.typeOf[A])) @@ -37,4 +43,9 @@ object Test extends App { //assert(c.d(new B)(null) == invoke("d", new B, u.typeOf[B])) assert(c.e(new A) == invoke("e", new A, u.typeOf[A])) assert(c.e(new B) == invoke("e", new B, u.typeOf[B])) + assert(c.f(1: Short) == invoke("f", 1: Short, u.typeOf[Short])) + assert(c.f(2) == invoke("f", 2, u.typeOf[Int])) + assert(c.f(3L) == invoke("f", 3L, u.typeOf[Long])) + assert(c.f(4f) == invoke("f", 4f, u.typeOf[Float])) + assert(c.f(5d) == invoke("f", 5d, u.typeOf[Double])) } -- cgit v1.2.3 From 31773a248dc685a76f5318e41dde3cac7033e4d2 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Thu, 12 Jul 2012 23:41:02 +0900 Subject: fix Right,Left and Either scaladoc links --- src/library/scala/Option.scala | 8 ++++---- src/library/scala/util/Either.scala | 12 ++++++------ src/library/scala/util/Try.scala | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/library/scala/Option.scala b/src/library/scala/Option.scala index c461b413d6..f651461fe6 100644 --- a/src/library/scala/Option.scala +++ b/src/library/scala/Option.scala @@ -268,9 +268,9 @@ sealed abstract class Option[+A] extends Product with Serializable { def toList: List[A] = if (isEmpty) List() else List(this.get) - /** Returns a [[scala.Left]] containing the given + /** Returns a [[scala.util.Left]] containing the given * argument `left` if this $option is empty, or - * a [[scala.Right]] containing this $option's value if + * a [[scala.util.Right]] containing this $option's value if * this is nonempty. * * @param left the expression to evaluate and return if this is empty @@ -279,9 +279,9 @@ sealed abstract class Option[+A] extends Product with Serializable { @inline final def toRight[X](left: => X) = if (isEmpty) Left(left) else Right(this.get) - /** Returns a [[scala.Right]] containing the given + /** Returns a [[scala.util.Right]] containing the given * argument `right` if this is empty, or - * a [[scala.Left]] containing this $option's value + * a [[scala.util.Left]] containing this $option's value * if this $option is nonempty. * * @param right the expression to evaluate and return if this is empty diff --git a/src/library/scala/util/Either.scala b/src/library/scala/util/Either.scala index 1a2e2d48d5..dcfdc16d33 100644 --- a/src/library/scala/util/Either.scala +++ b/src/library/scala/util/Either.scala @@ -13,12 +13,12 @@ package scala.util import language.implicitConversions /** Represents a value of one of two possible types (a disjoint union.) - * Instances of Either are either an instance of [[scala.Left]] or [[scala.Right]]. + * Instances of Either are either an instance of [[scala.util.Left]] or [[scala.util.Right]]. * * A common use of Either is as an alternative to [[scala.Option]] for dealing * with possible missing values. In this usage, [[scala.None]] is replaced - * with a [[scala.Left]] which can contain useful information. - * [[scala.Right]] takes the place of [[scala.Some]]. Convention dictates + * with a [[scala.util.Left]] which can contain useful information. + * [[scala.util.Right]] takes the place of [[scala.Some]]. Convention dictates * that Left is used for failure and Right is used for success. * * For example, you could use ``Either[String, Int]`` to detect whether a @@ -181,7 +181,7 @@ sealed abstract class Either[+A, +B] { } /** - * The left side of the disjoint union, as opposed to the [[scala.Right]] side. + * The left side of the disjoint union, as opposed to the [[scala.util.Right]] side. * * @author Tony Morris, Workingmouse * @version 1.0, 11/10/2008 @@ -192,7 +192,7 @@ final case class Left[+A, +B](a: A) extends Either[A, B] { } /** - * The right side of the disjoint union, as opposed to the [[scala.Left]] side. + * The right side of the disjoint union, as opposed to the [[scala.util.Left]] side. * * @author Tony Morris, Workingmouse * @version 1.0, 11/10/2008 @@ -283,7 +283,7 @@ object Either { * Right(12).left.get // NoSuchElementException * }}} * - * @throws Predef.NoSuchElementException if the projection is [[scala.Right]] + * @throws Predef.NoSuchElementException if the projection is [[scala.util.Right]] */ def get = e match { case Left(a) => a diff --git a/src/library/scala/util/Try.scala b/src/library/scala/util/Try.scala index 988f68bc18..9475a05d5a 100644 --- a/src/library/scala/util/Try.scala +++ b/src/library/scala/util/Try.scala @@ -16,7 +16,7 @@ import scala.util.control.NonFatal /** * The `Try` type represents a computation that may either result in an exception, or return a - * successfully computed value. It's similar to, but semantically different from the [[scala.Either]] type. + * successfully computed value. It's similar to, but semantically different from the [[scala.util.Either]] type. * * Instances of `Try[T]`, are either an instance of [[scala.util.Success]][T] or [[scala.util.Failure]][T]. * -- cgit v1.2.3 From 2c5890b3a7aa227643fde86ba72820a8016bc453 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 13 Jul 2012 12:31:14 +0200 Subject: SI-5956 trigger copy generation with correct namer the call to `addCopyMethod` passes `templateNamer`, which is supposed to be the namer of the case class template. with two classes of the same name, `addCopyMethod` was triggered in the wrong template. --- .../scala/tools/nsc/typechecker/Namers.scala | 8 +++++--- test/files/neg/t1286.check | 6 +----- test/files/neg/t5956.check | 20 ++++++++++++++++++++ test/files/neg/t5956.scala | 2 ++ 4 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 test/files/neg/t5956.check create mode 100644 test/files/neg/t5956.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 9580cd5676..48fd6ba928 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -835,13 +835,15 @@ trait Namers extends MethodSynthesis { // add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because // the namer phase must traverse this copy method to create default getters for its parameters. - // here, clazz is the ClassSymbol of the case class (not the module). - if (clazz.isClass && !clazz.hasModuleFlag) { + // here, clazz is the ClassSymbol of the case class (not the module). (!clazz.hasModuleFlag) excludes + // the moduleClass symbol of the companion object when the companion is a "case object". + if (clazz.isCaseClass && !clazz.hasModuleFlag) { val modClass = companionSymbolOf(clazz, context).moduleClass modClass.attachments.get[ClassForCaseCompanionAttachment] foreach { cma => val cdef = cma.caseClass def hasCopy(decls: Scope) = (decls lookup nme.copy) != NoSymbol - if (cdef.mods.isCase && !hasCopy(decls) && + // SI-5956 needs (cdef.symbol == clazz): there can be multiple class symbols with the same name + if (cdef.symbol == clazz && !hasCopy(decls) && !parents.exists(p => hasCopy(p.typeSymbol.info.decls)) && !parents.flatMap(_.baseClasses).distinct.exists(bc => hasCopy(bc.info.decls))) addCopyMethod(cdef, templateNamer) diff --git a/test/files/neg/t1286.check b/test/files/neg/t1286.check index c937fb9cf1..912709613c 100644 --- a/test/files/neg/t1286.check +++ b/test/files/neg/t1286.check @@ -1,9 +1,5 @@ -a.scala:1: error: Companions 'object Foo' and 'trait Foo' must be defined in same file: - Found in t1286/b.scala and t1286/a.scala -trait Foo { - ^ b.scala:1: error: Companions 'trait Foo' and 'object Foo' must be defined in same file: Found in t1286/a.scala and t1286/b.scala object Foo extends Foo { ^ -two errors found +one error found diff --git a/test/files/neg/t5956.check b/test/files/neg/t5956.check new file mode 100644 index 0000000000..6641dac97f --- /dev/null +++ b/test/files/neg/t5956.check @@ -0,0 +1,20 @@ +t5956.scala:1: warning: case classes without a parameter list have been deprecated; +use either case objects or case classes with `()' as parameter list. +object O { case class C[T]; class C } + ^ +t5956.scala:2: warning: case classes without a parameter list have been deprecated; +use either case objects or case classes with `()' as parameter list. +object T { case class C[T]; case class C } + ^ +t5956.scala:2: warning: case classes without a parameter list have been deprecated; +use either case objects or case classes with `()' as parameter list. +object T { case class C[T]; case class C } + ^ +t5956.scala:1: error: C is already defined as case class C +object O { case class C[T]; class C } + ^ +t5956.scala:2: error: C is already defined as case class C +object T { case class C[T]; case class C } + ^ +three warnings found +two errors found diff --git a/test/files/neg/t5956.scala b/test/files/neg/t5956.scala new file mode 100644 index 0000000000..d985fa97a4 --- /dev/null +++ b/test/files/neg/t5956.scala @@ -0,0 +1,2 @@ +object O { case class C[T]; class C } +object T { case class C[T]; case class C } -- cgit v1.2.3 From 6f3c6e6ebdde463d2e319f75cd10bd6a796f59c0 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 13 Jul 2012 16:18:03 +0200 Subject: enabled patmatDebug --- .../tools/nsc/typechecker/PatternMatching.scala | 105 +++++++++++---------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index b1e68e2757..dea8a0b866 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -48,8 +48,11 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val phaseName: String = "patmat" // TODO: the inliner fails to inline the closures to patmatDebug - // private val printPatmat = settings.Ypatmatdebug.value - // @inline final def patmatDebug(s: => String) = if (printPatmat) println(s) + object debugging { + val printPatmat = settings.Ypatmatdebug.value + @inline final def patmatDebug(s: => String) = if (printPatmat) println(s) + } + import debugging.patmatDebug def newTransformer(unit: CompilationUnit): Transformer = if (opt.virtPatmat) new MatchTransformer(unit) @@ -186,7 +189,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // (that would require more sophistication when generating trees, // and the only place that emits Matches after typers is for exception handling anyway) if(phase.id >= currentRun.uncurryPhase.id) debugwarn("running translateMatch at "+ phase +" on "+ selector +" match "+ cases) - // patmatDebug ("translating "+ cases.mkString("{", "\n", "}")) + patmatDebug("translating "+ cases.mkString("{", "\n", "}")) def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match { case TypeRef(_, RepeatedParamClass, arg :: Nil) => seqType(arg) @@ -311,14 +314,14 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if (!extractor.isTyped) ErrorUtils.issueNormalTypeError(patTree, "Could not typecheck extractor call: "+ extractor)(context) // if (extractor.resultInMonad == ErrorType) throw new TypeError(pos, "Unsupported extractor type: "+ extractor.tpe) - // patmatDebug ("translateExtractorPattern checking parameter type: "+ (patBinder, patBinder.info.widen, extractor.paramType, patBinder.info.widen <:< extractor.paramType)) + patmatDebug("translateExtractorPattern checking parameter type: "+ (patBinder, patBinder.info.widen, extractor.paramType, patBinder.info.widen <:< extractor.paramType)) // must use type `tp`, which is provided by extractor's result, not the type expected by binder, // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation // (it will later result in a type test when `tp` is not a subtype of `b.info`) // TODO: can we simplify this, together with the Bound case? (extractor.subPatBinders, extractor.subPatTypes).zipped foreach { case (b, tp) => - // patmatDebug ("changing "+ b +" : "+ b.info +" -> "+ tp) + patmatDebug("changing "+ b +" : "+ b.info +" -> "+ tp) b setInfo tp } @@ -425,7 +428,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL */ case Bind(n, p) => // this happens in certain ill-formed programs, there'll be an error later - // patmatDebug ("WARNING: Bind tree with unbound symbol "+ patTree) + patmatDebug("WARNING: Bind tree with unbound symbol "+ patTree) noFurtherSubPats() // there's no symbol -- something's wrong... don't fail here though (or should we?) // case Star(_) | ArrayValue | This => error("stone age pattern relics encountered!") @@ -828,7 +831,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL private[TreeMakers] def incorporateOuterSubstitution(outerSubst: Substitution): Unit = { if (currSub ne null) { - // patmatDebug ("BUG: incorporateOuterSubstitution called more than once for "+ (this, currSub, outerSubst)) + patmatDebug("BUG: incorporateOuterSubstitution called more than once for "+ (this, currSub, outerSubst)) Thread.dumpStack() } else currSub = outerSubst >> substitution @@ -994,7 +997,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL **/ case class TypeTestTreeMaker(prevBinder: Symbol, testedBinder: Symbol, expectedTp: Type, nextBinderTp: Type)(override val pos: Position, extractorArgTypeTest: Boolean = false) extends CondTreeMaker { import TypeTestTreeMaker._ - // patmatDebug ("TTTM"+(prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)) + patmatDebug("TTTM"+(prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)) lazy val outerTestNeeded = ( !((expectedTp.prefix eq NoPrefix) || expectedTp.prefix.typeSymbol.isPackageClass) @@ -1122,7 +1125,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Tree => Tree]): Tree = fixerUpper(owner, scrut.pos){ def matchFailGen = (matchFailGenOverride orElse Some(CODE.MATCHERROR(_: Tree))) - // patmatDebug ("combining cases: "+ (casesNoSubstOnly.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) + patmatDebug("combining cases: "+ (casesNoSubstOnly.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) val (unchecked, requireSwitch) = if (settings.XnoPatmatAnalysis.value) (true, false) @@ -1176,12 +1179,12 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL t match { case Function(_, _) if t.symbol == NoSymbol => t.symbol = currentOwner.newAnonymousFunctionValue(t.pos) - // patmatDebug ("new symbol for "+ (t, t.symbol.ownerChain)) + patmatDebug("new symbol for "+ (t, t.symbol.ownerChain)) case Function(_, _) if (t.symbol.owner == NoSymbol) || (t.symbol.owner == origOwner) => - // patmatDebug ("fundef: "+ (t, t.symbol.ownerChain, currentOwner.ownerChain)) + patmatDebug("fundef: "+ (t, t.symbol.ownerChain, currentOwner.ownerChain)) t.symbol.owner = currentOwner case d : DefTree if (d.symbol != NoSymbol) && ((d.symbol.owner == NoSymbol) || (d.symbol.owner == origOwner)) => // don't indiscriminately change existing owners! (see e.g., pos/t3440, pos/t3534, pos/unapplyContexts2) - // patmatDebug ("def: "+ (d, d.symbol.ownerChain, currentOwner.ownerChain)) + patmatDebug("def: "+ (d, d.symbol.ownerChain, currentOwner.ownerChain)) if(d.symbol.isLazy) { // for lazy val's accessor -- is there no tree?? assert(d.symbol.lazyAccessor != NoSymbol && d.symbol.lazyAccessor.owner == d.symbol.owner, d.symbol.lazyAccessor) d.symbol.lazyAccessor.owner = currentOwner @@ -1191,7 +1194,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL d.symbol.owner = currentOwner // case _ if (t.symbol != NoSymbol) && (t.symbol ne null) => - // patmatDebug ("untouched "+ (t, t.getClass, t.symbol.ownerChain, currentOwner.ownerChain)) + patmatDebug("untouched "+ (t, t.getClass, t.symbol.ownerChain, currentOwner.ownerChain)) case _ => } super.traverse(t) @@ -1472,14 +1475,14 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // reverse substitution that would otherwise replace a variable we already encountered by a new variable // NOTE: this forgets the more precise type we have for these later variables, but that's probably okay normalize >>= Substitution(boundTo map (_.symbol), boundFrom map (CODE.REF(_))) - // patmatDebug ("normalize subst: "+ normalize) + patmatDebug("normalize subst: "+ normalize) val okSubst = Substitution(unboundFrom, unboundTo map (normalize(_))) // it's important substitution does not duplicate trees here -- it helps to keep hash consing simple, anyway pointsToBound ++= ((okSubst.from, okSubst.to).zipped filter { (f, t) => pointsToBound exists (sym => t.exists(_.symbol == sym)) })._1 - // patmatDebug ("pointsToBound: "+ pointsToBound) + patmatDebug("pointsToBound: "+ pointsToBound) accumSubst >>= okSubst - // patmatDebug ("accumSubst: "+ accumSubst) + patmatDebug("accumSubst: "+ accumSubst) } // hashconsing trees (modulo value-equality) @@ -1573,13 +1576,13 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } def showTreeMakers(cases: List[List[TreeMaker]]) = { - // patmatDebug ("treeMakers:") - // patmatDebug (alignAcrossRows(cases, ">>")) + patmatDebug("treeMakers:") + patmatDebug(alignAcrossRows(cases, ">>")) } def showTests(testss: List[List[Test]]) = { - // patmatDebug ("tests: ") - // patmatDebug (alignAcrossRows(testss, "&")) + patmatDebug("tests: ") + patmatDebug(alignAcrossRows(testss, "&")) } } @@ -1771,7 +1774,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL override def hashCode = a.hashCode ^ b.hashCode } - // patmatDebug ("removeVarEq vars: "+ vars) + patmatDebug("removeVarEq vars: "+ vars) vars.foreach { v => val excludedPair = new collection.mutable.HashSet[ExcludedPair] @@ -1790,16 +1793,16 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } val syms = v.equalitySyms - // patmatDebug ("eqSyms "+(v, syms)) + patmatDebug("eqSyms "+(v, syms)) syms foreach { sym => // if we've already excluded the pair at some point (-A \/ -B), then don't exclude the symmetric one (-B \/ -A) // (nor the positive implications -B \/ A, or -A \/ B, which would entail the equality axioms falsifying the whole formula) val todo = syms filterNot (b => (b.const == sym.const) || excludedPair(ExcludedPair(b.const, sym.const))) val (excluded, notExcluded) = todo partition (b => sym.const.excludes(b.const)) val implied = notExcluded filter (b => sym.const.implies(b.const)) - // patmatDebug ("eq axioms for: "+ sym.const) - // patmatDebug ("excluded: "+ excluded) - // patmatDebug ("implied: "+ implied) + patmatDebug("eq axioms for: "+ sym.const) + patmatDebug("excluded: "+ excluded) + patmatDebug("implied: "+ implied) // when this symbol is true, what must hold... implied foreach (impliedSym => addAxiom(Or(Not(sym), impliedSym))) @@ -1812,8 +1815,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } } - // patmatDebug ("eqAxioms:\n"+ cnfString(eqFreePropToSolvable(eqAxioms))) - // patmatDebug ("pure:"+ pure.map(p => cnfString(eqFreePropToSolvable(p))).mkString("\n")) + patmatDebug("eqAxioms:\n"+ cnfString(eqFreePropToSolvable(eqAxioms))) + patmatDebug("pure:"+ pure.map(p => cnfString(eqFreePropToSolvable(p))).mkString("\n")) Statistics.stopTimer(patmatAnaVarEq, start) @@ -1955,12 +1958,12 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def findAllModels(f: Formula, models: List[Model], recursionDepthAllowed: Int = 10): List[Model]= if (recursionDepthAllowed == 0) models else { - // patmatDebug ("find all models for\n"+ cnfString(f)) + patmatDebug("find all models for\n"+ cnfString(f)) val model = findModelFor(f) // if we found a solution, conjunct the formula with the model's negation and recurse if (model ne NoModel) { val unassigned = (vars -- model.keySet).toList - // patmatDebug ("unassigned "+ unassigned +" in "+ model) + patmatDebug("unassigned "+ unassigned +" in "+ model) def force(lit: Lit) = { val model = withLit(findModelFor(dropUnit(f, lit)), lit) if (model ne NoModel) List(model) @@ -1969,7 +1972,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val forced = unassigned flatMap { s => force(Lit(s, true)) ++ force(Lit(s, false)) } - // patmatDebug ("forced "+ forced) + patmatDebug("forced "+ forced) val negated = negateModel(model) findAllModels(f :+ negated, model :: (forced ++ models), recursionDepthAllowed - 1) } @@ -1992,7 +1995,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def findModelFor(f: Formula): Model = { @inline def orElse(a: Model, b: => Model) = if (a ne NoModel) a else b - // patmatDebug ("DPLL\n"+ cnfString(f)) + patmatDebug("DPLL\n"+ cnfString(f)) val start = Statistics.startTimer(patmatAnaDPLL) @@ -2136,11 +2139,11 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL uniques.get(tp).getOrElse( uniques.find {case (oldTp, oldC) => oldTp =:= tp} match { case Some((_, c)) => - // patmatDebug ("unique const: "+ (tp, c)) + patmatDebug("unique const: "+ (tp, c)) c case _ => val fresh = mkFresh - // patmatDebug ("uniqued const: "+ (tp, fresh)) + patmatDebug("uniqued const: "+ (tp, fresh)) uniques(tp) = fresh fresh }) @@ -2156,12 +2159,12 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if (!t.symbol.isStable) t.tpe.narrow else trees find (a => a.correspondsStructure(t)(sameValue)) match { case Some(orig) => - // patmatDebug ("unique tp for tree: "+ (orig, orig.tpe)) + patmatDebug("unique tp for tree: "+ (orig, orig.tpe)) orig.tpe case _ => // duplicate, don't mutate old tree (TODO: use a map tree -> type instead?) val treeWithNarrowedType = t.duplicate setType t.tpe.narrow - // patmatDebug ("uniqued: "+ (t, t.tpe, treeWithNarrowedType.tpe)) + patmatDebug("uniqued: "+ (t, t.tpe, treeWithNarrowedType.tpe)) trees += treeWithNarrowedType treeWithNarrowedType.tpe } @@ -2375,8 +2378,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL var reachable = true var caseIndex = 0 - // patmatDebug ("reachability, vars:\n"+ ((propsCasesFail flatMap gatherVariables) map (_.describe) mkString ("\n"))) - // patmatDebug ("equality axioms:\n"+ cnfString(eqAxiomsCNF)) + patmatDebug("reachability, vars:\n"+ ((propsCasesFail flatMap gatherVariables) map (_.describe) mkString ("\n"))) + patmatDebug("equality axioms:\n"+ cnfString(eqAxiomsCNF)) // invariant (prefixRest.length == current.length) && (prefix.reverse ++ prefixRest == symbolicCasesFail) // termination: prefixRest.length decreases by 1 @@ -2423,7 +2426,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL Some(List(tp)) // make sure it's not a primitive, else (5: Byte) match { case 5 => ... } sees no Byte case sym if !sym.isSealed || isPrimitiveValueClass(sym) => - // patmatDebug ("enum unsealed "+ (tp, sym, sym.isSealed, isPrimitiveValueClass(sym))) + patmatDebug("enum unsealed "+ (tp, sym, sym.isSealed, isPrimitiveValueClass(sym))) None case sym => val subclasses = ( @@ -2431,7 +2434,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // symbols which are both sealed and abstract need not be covered themselves, because // all of their children must be and they cannot otherwise be created. filterNot (x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x))) - // patmatDebug ("enum sealed -- subclasses: "+ (sym, subclasses)) + patmatDebug("enum sealed -- subclasses: "+ (sym, subclasses)) val tpApprox = typer.infer.approximateAbstracts(tp) val pre = tpApprox.prefix @@ -2447,7 +2450,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if (subTpApprox <:< tpApprox) Some(checkableType(subTp)) else None }) - // patmatDebug ("enum sealed "+ (tp, tpApprox) + " as "+ validSubTypes) + patmatDebug("enum sealed "+ (tp, tpApprox) + " as "+ validSubTypes) Some(validSubTypes) } @@ -2467,7 +2470,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } } val res = toCheckable(tp) - // patmatDebug ("checkable "+(tp, res)) + patmatDebug("checkable "+(tp, res)) res } @@ -2525,7 +2528,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val vars = gatherVariables(matchFails) // debug output: - // patmatDebug ("analysing:") + patmatDebug("analysing:") showTreeMakers(cases) showTests(tests) @@ -2545,7 +2548,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL pruned } catch { case e : CNFBudgetExceeded => - // patmatDebug (util.Position.formatMessage(prevBinder.pos, "Cannot check match for exhaustivity", false)) + patmatDebug(util.Position.formatMessage(prevBinder.pos, "Cannot check match for exhaustivity", false)) // e.printStackTrace() Nil // CNF budget exceeded } @@ -2635,7 +2638,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // ... val varAssignment = modelToVarAssignment(model) - // patmatDebug ("var assignment for model "+ model +":\n"+ varAssignmentString(varAssignment)) + patmatDebug("var assignment for model "+ model +":\n"+ varAssignmentString(varAssignment)) // chop a path into a list of symbols def chop(path: Tree): List[Symbol] = path match { @@ -2702,7 +2705,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def toCounterExample(beBrief: Boolean = false): CounterExample = if (!allFieldAssignmentsLegal) NoExample else { - // patmatDebug ("describing "+ (variable, equalTo, notEqualTo, fields, cls, allFieldAssignmentsLegal)) + patmatDebug("describing "+ (variable, equalTo, notEqualTo, fields, cls, allFieldAssignmentsLegal)) val res = prunedEqualTo match { // a definite assignment to a value case List(eq: ValueConst) if fields.isEmpty => ValueExample(eq) @@ -2743,7 +2746,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // TODO: improve reasoning -- in the mean time, a false negative is better than an annoying false positive case _ => NoExample } - // patmatDebug ("described as: "+ res) + patmatDebug("described as: "+ res) res } @@ -2768,6 +2771,9 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL * we generalize sharing to implication, where b reuses a if a => b and priors(a) => priors(b) (the priors of a sub expression form the path through the decision tree) */ def doCSE(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): List[List[TreeMaker]] = { + patmatDebug("before CSE:") + showTreeMakers(cases) + val testss = approximateMatch(prevBinder, cases) // interpret: @@ -2814,7 +2820,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL tested.clear() tests dropWhile storeDependencies } - // patmatDebug ("dependencies: "+ dependencies) + patmatDebug("dependencies: "+ dependencies) // find longest prefix of tests that reuse a prior test, and whose dependent conditions monotonically increase // then, collapse these contiguous sequences of reusing tests @@ -2848,7 +2854,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case _ => } - // patmatDebug("sharedPrefix: "+ sharedPrefix) + patmatDebug("sharedPrefix: "+ sharedPrefix) + patmatDebug("suffix: "+ sharedPrefix) // if the shared prefix contains interesting conditions (!= TrueCond) // and the last of such interesting shared conditions reuses another treemaker's test // replace the whole sharedPrefix by a ReusingCondTreeMaker @@ -2864,7 +2871,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // replace original treemakers that are reused (as determined when computing collapsed), // by ReusedCondTreeMakers val reusedMakers = collapsed mapConserve (_ mapConserve reusedOrOrig) - // patmatDebug ("after CSE:") + patmatDebug("after CSE:") showTreeMakers(reusedMakers) reusedMakers } -- cgit v1.2.3 From 1b6468cd6178249919885847ae55bf3f33372a2a Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Jul 2012 17:24:41 +0200 Subject: Squashed critical enhancements to SIP-14: daemonizing global EC, giving EC to DelayedLazyVal, removing currentExecutionContext, removing impl.Future.isFutureThrowable, implementing asExecutionContext, giving a decent fallback if ForkJoinPool cannot be created --- .../scala/concurrent/ConcurrentPackageObject.scala | 66 ------------------- src/library/scala/concurrent/DelayedLazyVal.scala | 9 +-- .../scala/concurrent/ExecutionContext.scala | 5 -- src/library/scala/concurrent/Future.scala | 2 +- src/library/scala/concurrent/JavaConversions.scala | 12 +++- .../concurrent/default/TaskImpl.scala.disabled | 4 +- .../concurrent/impl/ExecutionContextImpl.scala | 75 ++++++++++++++-------- src/library/scala/concurrent/impl/Future.scala | 15 +---- src/library/scala/concurrent/impl/Promise.scala | 3 +- src/library/scala/concurrent/package.scala | 51 +++++++++++++-- 10 files changed, 110 insertions(+), 132 deletions(-) delete mode 100644 src/library/scala/concurrent/ConcurrentPackageObject.scala diff --git a/src/library/scala/concurrent/ConcurrentPackageObject.scala b/src/library/scala/concurrent/ConcurrentPackageObject.scala deleted file mode 100644 index 86a86966ef..0000000000 --- a/src/library/scala/concurrent/ConcurrentPackageObject.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.concurrent - -import java.util.concurrent.{ Executors, Executor, ThreadFactory } -import scala.concurrent.forkjoin.{ ForkJoinPool, ForkJoinWorkerThread } -import scala.concurrent.util.Duration -import language.implicitConversions - - -/** This package object contains primitives for concurrent and parallel programming. - */ -abstract class ConcurrentPackageObject { - - /* concurrency constructs */ - - /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. - * - * The result becomes available once the asynchronous computation is completed. - * - * @tparam T the type of the result - * @param body the asychronous computation - * @param execctx the execution context on which the future is run - * @return the `Future` holding the result of the computation - */ - def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body) - - /** Creates a promise object which can be completed with a value. - * - * @tparam T the type of the value in the promise - * @param execctx the execution context on which the promise is created on - * @return the newly created `Promise` object - */ - def promise[T]()(implicit execctx: ExecutionContext): Promise[T] = Promise[T]() - - /** Used to block on a piece of code which potentially blocks. - * - * @param body A piece of code which contains potentially blocking or long running calls. - * - * Calling this method may throw the following exceptions: - * - CancellationException - if the computation was cancelled - * - InterruptedException - in the case that a wait within the blockable object was interrupted - * - TimeoutException - in the case that the blockable object timed out - */ - def blocking[T](body: =>T): T = blocking(impl.Future.body2awaitable(body), Duration.Inf) - - /** Blocks on an awaitable object. - * - * @param awaitable An object with a `block` method which runs potentially blocking or long running calls. - * - * Calling this method may throw the following exceptions: - * - CancellationException - if the computation was cancelled - * - InterruptedException - in the case that a wait within the blockable object was interrupted - * - TimeoutException - in the case that the blockable object timed out - */ - def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = - BlockContext.current.internalBlockingCall(awaitable, atMost) - - @inline implicit final def int2durationops(x: Int): DurationOps = new DurationOps(x) -} diff --git a/src/library/scala/concurrent/DelayedLazyVal.scala b/src/library/scala/concurrent/DelayedLazyVal.scala index 91e41748f5..6d262ea9a2 100644 --- a/src/library/scala/concurrent/DelayedLazyVal.scala +++ b/src/library/scala/concurrent/DelayedLazyVal.scala @@ -23,7 +23,7 @@ package scala.concurrent * @author Paul Phillips * @version 2.8 */ -class DelayedLazyVal[T](f: () => T, body: => Unit){ +class DelayedLazyVal[T](f: () => T, body: => Unit)(implicit exec: ExecutionContext){ @volatile private[this] var _isDone = false private[this] lazy val complete = f() @@ -39,10 +39,5 @@ class DelayedLazyVal[T](f: () => T, body: => Unit){ */ def apply(): T = if (isDone) complete else f() - // FIXME need to take ExecutionContext in constructor - import ExecutionContext.Implicits.global - future { - body - _isDone = true - } + exec.execute(new Runnable { def run = { body; _isDone = true } }) } diff --git a/src/library/scala/concurrent/ExecutionContext.scala b/src/library/scala/concurrent/ExecutionContext.scala index b486e5269e..debfc226db 100644 --- a/src/library/scala/concurrent/ExecutionContext.scala +++ b/src/library/scala/concurrent/ExecutionContext.scala @@ -43,11 +43,6 @@ trait ExecutionContextExecutorService extends ExecutionContextExecutor with Exec /** Contains factory methods for creating execution contexts. */ object ExecutionContext { - /** - * The `ExecutionContext` associated with the current `Thread` - */ - val currentExecutionContext: ThreadLocal[ExecutionContext] = new ThreadLocal //FIXME might want to set the initial value to an executionContext that throws an exception on execute and warns that it's not set - /** * This is the explicit global ExecutionContext, * call this when you want to provide the global ExecutionContext explicitly diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 75a83d6ef8..e556be4fe3 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -136,7 +136,7 @@ trait Future[+T] extends Awaitable[T] { * $callbackInContext */ def onFailure[U](callback: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete { - case Left(t) if (impl.Future.isFutureThrowable(t) && callback.isDefinedAt(t)) => callback(t) + case Left(t) if NonFatal(t) && callback.isDefinedAt(t) => callback(t) case _ => }(executor) diff --git a/src/library/scala/concurrent/JavaConversions.scala b/src/library/scala/concurrent/JavaConversions.scala index 9b5e741549..ffb9926fef 100644 --- a/src/library/scala/concurrent/JavaConversions.scala +++ b/src/library/scala/concurrent/JavaConversions.scala @@ -50,8 +50,16 @@ object JavaConversions { } } - implicit def asExecutionContext(exec: ExecutorService): ExecutionContext = null // TODO + /** + * Creates a new `ExecutionContext` which uses the provided `ExecutorService`. + */ + implicit def asExecutionContext(exec: ExecutorService): ExecutionContextExecutorService = + ExecutionContext.fromExecutorService(exec) - implicit def asExecutionContext(exec: Executor): ExecutionContext = null // TODO + /** + * Creates a new `ExecutionContext` which uses the provided `Executor`. + */ + implicit def asExecutionContext(exec: Executor): ExecutionContextExecutor = + ExecutionContext.fromExecutor(exec) } diff --git a/src/library/scala/concurrent/default/TaskImpl.scala.disabled b/src/library/scala/concurrent/default/TaskImpl.scala.disabled index 50753a7154..8b4eb12d4f 100644 --- a/src/library/scala/concurrent/default/TaskImpl.scala.disabled +++ b/src/library/scala/concurrent/default/TaskImpl.scala.disabled @@ -9,7 +9,7 @@ import scala.util.Try import scala.util import scala.concurrent.util.Duration import scala.annotation.tailrec - +import scala.util.control.NonFatal private[concurrent] trait Completable[T] { @@ -167,7 +167,7 @@ extends RecursiveAction with Task[T] with Future[T] with Completable[T] { val res = body processCallbacks(tryCompleteState(Success(res)), util.Success(res)) } catch { - case t if isFutureThrowable(t) => + case t if NonFatal(t) => processCallbacks(tryCompleteState(Failure(t)), util.Failure(t)) case t => val ee = new ExecutionException(t) diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index 551a444425..ccac565eea 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -10,7 +10,7 @@ package scala.concurrent.impl -import java.util.concurrent.{ Callable, Executor, ExecutorService, Executors, ThreadFactory, TimeUnit } +import java.util.concurrent.{ LinkedBlockingQueue, Callable, Executor, ExecutorService, Executors, ThreadFactory, TimeUnit, ThreadPoolExecutor } import java.util.Collection import scala.concurrent.forkjoin._ import scala.concurrent.{ BlockContext, ExecutionContext, Awaitable, ExecutionContextExecutor, ExecutionContextExecutorService } @@ -27,48 +27,71 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: } // Implement BlockContext on FJP threads - def forkJoinPoolThreadFactory = new ForkJoinPool.ForkJoinWorkerThreadFactory { - def newThread(fjp: ForkJoinPool) = new ForkJoinWorkerThread(fjp) with BlockContext { + class DefaultThreadFactory(daemonic: Boolean) extends ThreadFactory with ForkJoinPool.ForkJoinWorkerThreadFactory { + def wire[T <: Thread](thread: T): T = { + thread.setDaemon(daemonic) + //Potentially set things like uncaught exception handler, name etc + thread + } + + def newThread(runnable: Runnable): Thread = wire(new Thread()) + + def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = wire(new ForkJoinWorkerThread(fjp) with BlockContext { override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { var result: T = null.asInstanceOf[T] ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker { @volatile var isdone = false def block(): Boolean = { - result = awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) // FIXME what happens if there's an exception thrown here? - isdone = true + result = try awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) finally { isdone = true } true } def isReleasable = isdone }) result } - } + }) } - def createExecutorService: ExecutorService = try { + def createExecutorService: ExecutorService = { + def getInt(name: String, f: String => Int): Int = - try f(System.getProperty(name)) catch { case e: Exception => Runtime.getRuntime.availableProcessors } + try f(System.getProperty(name)) catch { case e: Exception => Runtime.getRuntime.availableProcessors } def range(floor: Int, desired: Int, ceiling: Int): Int = if (ceiling < floor) range(ceiling, desired, floor) else scala.math.min(scala.math.max(desired, floor), ceiling) + + val minThreads = getInt("scala.concurrent.ec.minThreads", _.toInt) + val maxThreads = getInt("scala.concurrent.ec.maxThreads", _.toInt) + val numThreads = getInt("scala.concurrent.ec.numThreads", { + case null | "" => Runtime.getRuntime.availableProcessors + case s if s.charAt(0) == 'x' => (Runtime.getRuntime.availableProcessors * s.substring(1).toDouble).ceil.toInt + case other => other.toInt + }) + + val desiredParallelism = range(minThreads, numThreads, maxThreads) + + val threadFactory = new DefaultThreadFactory(daemonic = true) - new ForkJoinPool( - range( - getInt("scala.concurrent.ec.minThreads", _.toInt), - getInt("scala.concurrent.ec.numThreads", { - case null | "" => Runtime.getRuntime.availableProcessors - case s if s.charAt(0) == 'x' => (Runtime.getRuntime.availableProcessors * s.substring(1).toDouble).ceil.toInt - case other => other.toInt - }), - getInt("scala.concurrent.ec.maxThreads", _.toInt) - ), - forkJoinPoolThreadFactory, - null, //FIXME we should have an UncaughtExceptionHandler, see what Akka does - true) //FIXME I really think this should be async... - } catch { - case NonFatal(t) => - System.err.println("Failed to create ForkJoinPool for the default ExecutionContext, falling back to Executors.newCachedThreadPool") - t.printStackTrace(System.err) - Executors.newCachedThreadPool() //FIXME use the same desired parallelism here too? + try { + new ForkJoinPool( + desiredParallelism, + threadFactory, + null, //FIXME we should have an UncaughtExceptionHandler, see what Akka does + true) // Async all the way baby + } catch { + case NonFatal(t) => + System.err.println("Failed to create ForkJoinPool for the default ExecutionContext, falling back to ThreadPoolExecutor") + t.printStackTrace(System.err) + val exec = new ThreadPoolExecutor( + desiredParallelism, + desiredParallelism, + 5L, + TimeUnit.MINUTES, + new LinkedBlockingQueue[Runnable], + threadFactory + ) + exec.allowCoreThreadTimeOut(true) + exec + } } def execute(runnable: Runnable): Unit = executor match { diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 073e6c4c9f..0c031743db 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -46,26 +46,13 @@ private[concurrent] object Future { def boxedType(c: Class[_]): Class[_] = if (c.isPrimitive) toBoxed(c) else c - // TODO rename appropriately and make public - private[concurrent] def isFutureThrowable(t: Throwable) = t match { - case e: Error => false - case t: scala.util.control.ControlThrowable => false - case i: InterruptedException => false - case _ => true - } - private[impl] class PromiseCompletingRunnable[T](body: => T) extends Runnable { val promise = new Promise.DefaultPromise[T]() override def run() = { promise complete { - try Right(body) catch { - case NonFatal(e) => - // Commenting out reporting for now, since it produces too much output in the tests - //executor.reportFailure(e) - Left(e) - } + try Right(body) catch { case NonFatal(e) => Left(e) } } } } diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index 3ac34bef8a..ccfcd30c97 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -59,7 +59,7 @@ object Promise { /** Default promise implementation. */ class DefaultPromise[T] extends AbstractPromise with Promise[T] { self => - updateState(null, Nil) // Start at "No callbacks" //FIXME switch to Unsafe instead of ARFU + updateState(null, Nil) // Start at "No callbacks" protected final def tryAwait(atMost: Duration): Boolean = { @tailrec @@ -80,7 +80,6 @@ object Promise { } else isCompleted } - //FIXME do not do this if there'll be no waiting awaitUnsafe(if (atMost.isFinite) atMost.toNanos else Long.MaxValue) } diff --git a/src/library/scala/concurrent/package.scala b/src/library/scala/concurrent/package.scala index e8921ef531..76703bf081 100644 --- a/src/library/scala/concurrent/package.scala +++ b/src/library/scala/concurrent/package.scala @@ -8,17 +8,59 @@ package scala -import scala.util.{ Try, Success, Failure } import scala.concurrent.util.Duration /** This package object contains primitives for concurrent and parallel programming. */ -package object concurrent extends scala.concurrent.ConcurrentPackageObject { +package object concurrent { type ExecutionException = java.util.concurrent.ExecutionException type CancellationException = java.util.concurrent.CancellationException type TimeoutException = java.util.concurrent.TimeoutException + + /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. + * + * The result becomes available once the asynchronous computation is completed. + * + * @tparam T the type of the result + * @param body the asychronous computation + * @param execctx the execution context on which the future is run + * @return the `Future` holding the result of the computation + */ + def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body) + + /** Creates a promise object which can be completed with a value. + * + * @tparam T the type of the value in the promise + * @param execctx the execution context on which the promise is created on + * @return the newly created `Promise` object + */ + def promise[T]()(implicit execctx: ExecutionContext): Promise[T] = Promise[T]() + + /** Used to block on a piece of code which potentially blocks. + * + * @param body A piece of code which contains potentially blocking or long running calls. + * + * Calling this method may throw the following exceptions: + * - CancellationException - if the computation was cancelled + * - InterruptedException - in the case that a wait within the blockable object was interrupted + * - TimeoutException - in the case that the blockable object timed out + */ + def blocking[T](body: =>T): T = blocking(impl.Future.body2awaitable(body), Duration.Inf) + + /** Blocks on an awaitable object. + * + * @param awaitable An object with a `block` method which runs potentially blocking or long running calls. + * + * Calling this method may throw the following exceptions: + * - CancellationException - if the computation was cancelled + * - InterruptedException - in the case that a wait within the blockable object was interrupted + * - TimeoutException - in the case that the blockable object timed out + */ + def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = + BlockContext.current.internalBlockingCall(awaitable, atMost) } +/* concurrency constructs */ package concurrent { sealed trait CanAwait @@ -36,9 +78,4 @@ package concurrent { } } - - final class DurationOps private[concurrent] (x: Int) { - // TODO ADD OTHERS - def ns = util.Duration.fromNanos(0) - } } -- cgit v1.2.3 From 47652e692a5bdcbc18de2881e86267d37757751d Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Jul 2012 18:11:53 +0200 Subject: Fixing oversight in propagating the runnable into the newly created thread. --- src/library/scala/concurrent/impl/ExecutionContextImpl.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index ccac565eea..6c4145e2d5 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -34,7 +34,7 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: thread } - def newThread(runnable: Runnable): Thread = wire(new Thread()) + def newThread(runnable: Runnable): Thread = wire(new Thread(runnable)) def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = wire(new ForkJoinWorkerThread(fjp) with BlockContext { override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { -- cgit v1.2.3 From 7e0b9e8effaebbb6413e4cc63789b8f86428a684 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Jul 2012 18:16:31 +0200 Subject: Changing to scala.concurrent.context. as namespace for the system properties for the global execution context --- src/library/scala/concurrent/impl/ExecutionContextImpl.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index 6c4145e2d5..98f821652f 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -59,15 +59,14 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: def range(floor: Int, desired: Int, ceiling: Int): Int = if (ceiling < floor) range(ceiling, desired, floor) else scala.math.min(scala.math.max(desired, floor), ceiling) - val minThreads = getInt("scala.concurrent.ec.minThreads", _.toInt) - val maxThreads = getInt("scala.concurrent.ec.maxThreads", _.toInt) - val numThreads = getInt("scala.concurrent.ec.numThreads", { + val desiredParallelism = range( + getInt("scala.concurrent.context.minThreads", _.toInt), + getInt("scala.concurrent.context.numThreads", { case null | "" => Runtime.getRuntime.availableProcessors case s if s.charAt(0) == 'x' => (Runtime.getRuntime.availableProcessors * s.substring(1).toDouble).ceil.toInt case other => other.toInt - }) - - val desiredParallelism = range(minThreads, numThreads, maxThreads) + }), + getInt("scala.concurrent.context.maxThreads", _.toInt)) val threadFactory = new DefaultThreadFactory(daemonic = true) -- cgit v1.2.3 From a6b0250b812f189939bd12edf4f543feb801f7c2 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 14 Jul 2012 10:54:52 +0200 Subject: changed line endings in GenASM from Windows to Unix --- .../scala/tools/nsc/backend/jvm/GenASM.scala | 6646 ++++++++++---------- 1 file changed, 3323 insertions(+), 3323 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 8d243a1dd0..9a2cd5259c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -1,3323 +1,3323 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend.jvm - -import java.nio.ByteBuffer -import scala.collection.{ mutable, immutable } -import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } -import scala.tools.nsc.symtab._ -import scala.tools.nsc.io.AbstractFile - -import scala.tools.asm -import asm.Label - -/** - * @author Iulian Dragos (version 1.0, FJBG-based implementation) - * @author Miguel Garcia (version 2.0, ASM-based implementation) - * - * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf - */ -abstract class GenASM extends SubComponent with BytecodeWriters { - import global._ - import icodes._ - import icodes.opcodes._ - import definitions._ - - val phaseName = "jvm" - - /** Create a new phase */ - override def newPhase(p: Phase): Phase = new AsmPhase(p) - - private def outputDirectory(sym: Symbol): AbstractFile = - settings.outputDirs outputDirFor beforeFlatten(sym.sourceFile) - - private def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { - var dir = base - val pathParts = clsName.split("[./]").toList - for (part <- pathParts.init) { - dir = dir.subdirectoryNamed(part) - } - dir.fileNamed(pathParts.last + suffix) - } - private def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile = - getFile(outputDirectory(sym), clsName, suffix) - - /** JVM code generation phase - */ - class AsmPhase(prev: Phase) extends ICodePhase(prev) { - def name = phaseName - override def erasedTypes = true - def apply(cls: IClass) = sys.error("no implementation") - - val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") - - def isJavaEntryPoint(icls: IClass) = { - val sym = icls.symbol - def fail(msg: String, pos: Position = sym.pos) = { - icls.cunit.warning(sym.pos, - sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" + - " Reason: " + msg - // TODO: make this next claim true, if possible - // by generating valid main methods as static in module classes - // not sure what the jvm allows here - // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead." - ) - false - } - def failNoForwarder(msg: String) = { - fail(msg + ", which means no static forwarder can be generated.\n") - } - val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil - val hasApproximate = possibles exists { m => - m.info match { - case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass - case _ => false - } - } - // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. - hasApproximate && { - // Before erasure so we can identify generic mains. - beforeErasure { - val companion = sym.linkedClassOfClass - val companionMain = companion.tpe.member(nme.main) - - if (hasJavaMainMethod(companion)) - failNoForwarder("companion contains its own main method") - else if (companion.tpe.member(nme.main) != NoSymbol) - // this is only because forwarders aren't smart enough yet - failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") - else if (companion.isTrait) - failNoForwarder("companion is a trait") - // Now either succeeed, or issue some additional warnings for things which look like - // attempts to be java main methods. - else possibles exists { m => - m.info match { - case PolyType(_, _) => - fail("main methods cannot be generic.") - case MethodType(params, res) => - if (res.typeSymbol :: params exists (_.isAbstractType)) - fail("main methods cannot refer to type parameters or abstract types.", m.pos) - else - isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) - case tp => - fail("don't know what this is: " + tp, m.pos) - } - } - } - } - } - - private def initBytecodeWriter(entryPoints: List[IClass]): BytecodeWriter = { - settings.outputDirs.getSingleOutput match { - case Some(f) if f hasExtension "jar" => - // If no main class was specified, see if there's only one - // entry point among the classes going into the jar. - if (settings.mainClass.isDefault) { - entryPoints map (_.symbol fullName '.') match { - case Nil => - log("No Main-Class designated or discovered.") - case name :: Nil => - log("Unique entry point: setting Main-Class to " + name) - settings.mainClass.value = name - case names => - log("No Main-Class due to multiple entry points:\n " + names.mkString("\n ")) - } - } - else log("Main-Class was specified: " + settings.mainClass.value) - - new DirectToJarfileWriter(f.file) - - case _ => - if (settings.Ygenjavap.isDefault) { - if(settings.Ydumpclasses.isDefault) - new ClassBytecodeWriter { } - else - new ClassBytecodeWriter with DumpBytecodeWriter { } - } - else new ClassBytecodeWriter with JavapBytecodeWriter { } - - // TODO A ScalapBytecodeWriter could take asm.util.Textifier as starting point. - // Three areas where javap ouput is less than ideal (e.g. when comparing versions of the same classfile) are: - // (a) unreadable pickle; - // (b) two constant pools, while having identical contents, are displayed differently due to physical layout. - // (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, their expansion makes more sense instead. - } - } - - override def run() { - - if (settings.debug.value) - inform("[running phase " + name + " on icode]") - - if (settings.Xdce.value) - for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) - icodes.classes -= sym - - // For predictably ordered error messages. - var sortedClasses = classes.values.toList sortBy ("" + _.symbol.fullName) - - debuglog("Created new bytecode generator for " + classes.size + " classes.") - val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) - val plainCodeGen = new JPlainBuilder(bytecodeWriter) - val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter) - val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter) - - while(!sortedClasses.isEmpty) { - val c = sortedClasses.head - - if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) { - if (c.symbol.companionClass == NoSymbol) { - mirrorCodeGen.genMirrorClass(c.symbol, c.cunit) - } else { - log("No mirror class for module with linked class: " + c.symbol.fullName) - } - } - - plainCodeGen.genClass(c) - - if (c.symbol hasAnnotation BeanInfoAttr) { - beanInfoCodeGen.genBeanInfoClass(c) - } - - sortedClasses = sortedClasses.tail - classes -= c.symbol // GC opportunity - } - - bytecodeWriter.close() - classes.clear() - reverseJavaName.clear() - - /* don't javaNameCache.clear() because that causes the following tests to fail: - * test/files/run/macro-repl-dontexpand.scala - * test/files/jvm/interpreter.scala - * TODO but why? what use could javaNameCache possibly see once GenJVM is over? - */ - - /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification: - * - * (1) call the asm.util.CheckAdapter.verify() overload: - * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) - * - * (2) passing a custom ClassLoader to verify inter-dependent classes. - * - * Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). - */ - - } // end of AsmPhase.run() - - } // end of class AsmPhase - - var pickledBytes = 0 // statistics - - // Don't put this in per run caches. Contains entries for classes as well as members. - val javaNameCache = new mutable.WeakHashMap[Symbol, Name]() ++= List( - NothingClass -> binarynme.RuntimeNothing, - RuntimeNothingClass -> binarynme.RuntimeNothing, - NullClass -> binarynme.RuntimeNull, - RuntimeNullClass -> binarynme.RuntimeNull - ) - - // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. - val reverseJavaName = mutable.Map.empty[String, Symbol] ++= List( - binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type. - binarynme.RuntimeNull.toString() -> RuntimeNullClass - ) - - private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) - - @inline final private def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) - - @inline final private def isRemote(s: Symbol) = (s hasAnnotation RemoteAttr) - - /** - * Return the Java modifiers for the given symbol. - * Java modifiers for classes: - * - public, abstract, final, strictfp (not used) - * for interfaces: - * - the same as for classes, without 'final' - * for fields: - * - public, private (*) - * - static, final - * for methods: - * - the same as for fields, plus: - * - abstract, synchronized (not used), strictfp (not used), native (not used) - * - * (*) protected cannot be used, since inner classes 'see' protected members, - * and they would fail verification after lifted. - */ - def javaFlags(sym: Symbol): Int = { - // constructors of module classes should be private - // PP: why are they only being marked private at this stage and not earlier? - val privateFlag = - sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) - - // Final: the only fields which can receive ACC_FINAL are eager vals. - // Neither vars nor lazy vals can, because: - // - // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 - // "Another problem is that the specification allows aggressive - // optimization of final fields. Within a thread, it is permissible to - // reorder reads of a final field with those modifications of a final - // field that do not take place in the constructor." - // - // A var or lazy val which is marked final still has meaning to the - // scala compiler. The word final is heavily overloaded unfortunately; - // for us it means "not overridable". At present you can't override - // vars regardless; this may change. - // - // The logic does not check .isFinal (which checks flags for the FINAL flag, - // and includes symbols marked lateFINAL) instead inspecting rawflags so - // we can exclude lateFINAL. Such symbols are eligible for inlining, but to - // avoid breaking proxy software which depends on subclassing, we do not - // emit ACC_FINAL. - // Nested objects won't receive ACC_FINAL in order to allow for their overriding. - - val finalFlag = ( - (sym.hasFlag(Flags.FINAL) || isTopLevelModule(sym)) - && !sym.enclClass.isInterface - && !sym.isClassConstructor - && !sym.isMutable // lazy vals and vars both - ) - - // Primitives are "abstract final" to prohibit instantiation - // without having to provide any implementations, but that is an - // illegal combination of modifiers at the bytecode level so - // suppress final if abstract if present. - import asm.Opcodes._ - mkFlags( - if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, - if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (sym.isInterface) ACC_INTERFACE else 0, - if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, - if (sym.isStaticMember) ACC_STATIC else 0, - if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, - if (sym.isHidden) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.isVarargsMethod) ACC_VARARGS else 0, - if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 - ) - } - - def javaFieldFlags(sym: Symbol) = { - javaFlags(sym) | mkFlags( - if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, - if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, - if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL - ) - } - - def isTopLevelModule(sym: Symbol): Boolean = - afterPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } - - def isStaticModule(sym: Symbol): Boolean = { - sym.isModuleClass && !sym.isImplClass && !sym.isLifted - } - - // ----------------------------------------------------------------------------------------- - // finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM) - // Background: - // http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf - // http://comments.gmane.org/gmane.comp.java.vm.languages/2293 - // https://issues.scala-lang.org/browse/SI-3872 - // ----------------------------------------------------------------------------------------- - - /** - * Given an internal name (eg "java/lang/Integer") returns the class symbol for it. - * - * Better not to need this method (an example where control flow arrives here is welcome). - * This method is invoked only upon both (1) and (2) below happening: - * (1) providing an asm.ClassWriter with an internal name by other means than javaName() - * (2) forgetting to track the corresponding class-symbol in reverseJavaName. - * - * (The first item is already unlikely because we rely on javaName() - * to do the bookkeeping for entries that should go in innerClassBuffer.) - * - * (We could do completely without this method at the expense of computing stack-map-frames ourselves and - * invoking visitFrame(), but that would require another pass over all instructions.) - * - * Right now I can't think of any invocation of visitSomething() on MethodVisitor - * where we hand an internal name not backed by a reverseJavaName. - * However, I'm leaving this note just in case any such oversight is discovered. - */ - def inameToSymbol(iname: String): Symbol = { - val name = global.newTypeName(iname) - val res0 = - if (nme.isModuleName(name)) rootMirror.getModule(nme.stripModuleSuffix(name)) - else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested). - assert(res0 != NoSymbol) - val res = jsymbol(res0) - res - } - - def jsymbol(sym: Symbol): Symbol = { - if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass - else if(sym.isModule) sym.moduleClass - else sym // we track only module-classes and plain-classes - } - - private def superClasses(s: Symbol): List[Symbol] = { - assert(!s.isInterface) - s.superClass match { - case NoSymbol => List(s) - case sc => s :: superClasses(sc) - } - } - - private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = { - assert(!(as contains NoSymbol)) - assert(!(bs contains NoSymbol)) - var chainA = as - var chainB = bs - var fcs: Symbol = NoSymbol - do { - if (chainB contains chainA.head) fcs = chainA.head - else if (chainA contains chainB.head) fcs = chainB.head - else { - chainA = chainA.tail - chainB = chainB.tail - } - } while(fcs == NoSymbol) - fcs - } - - @inline final private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = { - - assert(a.isClass) - assert(b.isClass) - - val res = Pair(a.isInterface, b.isInterface) match { - case (true, true) => - global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents - case (true, false) => - if(b isSubClass a) a else ObjectClass - case (false, true) => - if(a isSubClass b) b else ObjectClass - case _ => - firstCommonSuffix(superClasses(a), superClasses(b)) - } - assert(res != NoSymbol) - res - } - - /* The internal name of the least common ancestor of the types given by inameA and inameB. - It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow */ - def getCommonSuperClass(inameA: String, inameB: String): String = { - val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA)) - val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB)) - - // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString() - // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType - val lcaSym = jvmWiseLUB(a, b) - val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer. - val oldsym = reverseJavaName.put(lcaName, lcaSym) - assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption") - assert(lcaName != "scala/Any") - - lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching. - } - - class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { - override def getCommonSuperClass(iname1: String, iname2: String): String = { - GenASM.this.getCommonSuperClass(iname1, iname2) - } - } - - // ----------------------------------------------------------------------------------------- - // constants - // ----------------------------------------------------------------------------------------- - - private val classfileVersion: Int = settings.target.value match { - case "jvm-1.5" => asm.Opcodes.V1_5 - case "jvm-1.5-asm" => asm.Opcodes.V1_5 - case "jvm-1.6" => asm.Opcodes.V1_6 - case "jvm-1.7" => asm.Opcodes.V1_7 - } - - private val majorVersion: Int = (classfileVersion & 0xFF) - private val emitStackMapFrame = (majorVersion >= 50) - - private val extraProc: Int = mkFlags( - asm.ClassWriter.COMPUTE_MAXS, - if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 - ) - - val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object") - val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String") - - /** basic functionality for class file building */ - abstract class JBuilder(bytecodeWriter: BytecodeWriter) { - - val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type] - val EMPTY_STRING_ARRAY = Array.empty[String] - - val mdesc_arglessvoid = "()V" - - val CLASS_CONSTRUCTOR_NAME = "" - val INSTANCE_CONSTRUCTOR_NAME = "" - - val INNER_CLASSES_FLAGS = - (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | - asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT) - - // ----------------------------------------------------------------------------------------- - // factory methods - // ----------------------------------------------------------------------------------------- - - /** - * Returns a new ClassWriter for the class given by arguments. - * - * @param access the class's access flags. This parameter also indicates if the class is deprecated. - * - * @param name the internal name of the class. - * - * @param signature the signature of this class. May be null if - * the class is not a generic one, and does not extend or implement - * generic classes or interfaces. - * - * @param superName the internal of name of the super class. For interfaces, - * the super class is {@link Object}. May be null, but - * only for the {@link Object} class. - * - * @param interfaces the internal names of the class's interfaces (see - * {@link Type#getInternalName() getInternalName}). May be - * null. - */ - def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = { - val cw = new CClassWriter(extraProc) - cw.visit(classfileVersion, - access, name, signature, - superName, interfaces) - - cw - } - - def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { - val dest = new Array[Byte](len); - System.arraycopy(b, offset, dest, 0, len); - new asm.CustomAttr(name, dest) - } - - // ----------------------------------------------------------------------------------------- - // utitilies useful when emitting plain, mirror, and beaninfo classes. - // ----------------------------------------------------------------------------------------- - - def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { - try { - val arr = jclass.toByteArray() - bytecodeWriter.writeClass(label, jclassName, arr, sym) - } catch { - case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") => - // TODO check where ASM throws the equivalent of CodeSizeTooBigException - log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).") - } - } - - /** Specialized array conversion to prevent calling - * java.lang.reflect.Array.newInstance via TraversableOnce.toArray - */ - def mkArray(xs: Traversable[asm.Type]): Array[asm.Type] = { val a = new Array[asm.Type](xs.size); xs.copyToArray(a); a } - def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a } - - // ----------------------------------------------------------------------------------------- - // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances). - // These getters track behind the scenes the inner classes referred to in the class being emitted, - // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()` - // (which also adds as member classes those inner classes that have been declared, - // thus also covering the case of inner classes declared but otherwise not referred). - // ----------------------------------------------------------------------------------------- - - val innerClassBuffer = mutable.LinkedHashSet[Symbol]() - - /** For given symbol return a symbol corresponding to a class that should be declared as inner class. - * - * For example: - * class A { - * class B - * object C - * } - * - * then method will return: - * NoSymbol for A, - * the same symbol for A.B (corresponding to A$B class), and - * A$C$ symbol for A.C. - */ - def innerClassSymbolFor(s: Symbol): Symbol = - if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol - - /** Return the a name of this symbol that can be used on the Java platform. It removes spaces from names. - * - * Special handling: - * scala.Nothing erases to scala.runtime.Nothing$ - * scala.Null erases to scala.runtime.Null$ - * - * This is needed because they are not real classes, and they mean - * 'abrupt termination upon evaluation of that expression' or null respectively. - * This handling is done already in GenICode, but here we need to remove - * references from method signatures to these types, because such classes - * cannot exist in the classpath: the type checker will be very confused. - */ - def javaName(sym: Symbol): String = { - - /** - * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer - * - * Note: This method is called recursively thus making sure that we add complete chain - * of inner class all until root class. - */ - def collectInnerClass(s: Symbol): Unit = { - // TODO: some beforeFlatten { ... } which accounts for - // being nested in parameterized classes (if we're going to selectively flatten.) - val x = innerClassSymbolFor(s) - if(x ne NoSymbol) { - assert(x.isClass, "not an inner-class symbol") - val isInner = !x.rawowner.isPackageClass - if (isInner) { - innerClassBuffer += x - collectInnerClass(x.rawowner) - } - } - } - - collectInnerClass(sym) - - var hasInternalName = (sym.isClass || (sym.isModule && !sym.isMethod)) - val cachedJN = javaNameCache.getOrElseUpdate(sym, { - if (hasInternalName) { sym.javaBinaryName } - else { sym.javaSimpleName } - }) - - if(emitStackMapFrame && hasInternalName) { - val internalName = cachedJN.toString() - val trackedSym = jsymbol(sym) - reverseJavaName.get(internalName) match { - case None => - reverseJavaName.put(internalName, trackedSym) - case Some(oldsym) => - assert((oldsym == trackedSym) || (oldsym == RuntimeNothingClass) || (oldsym == RuntimeNullClass), // In contrast, neither NothingClass nor NullClass show up bytecode-level. - "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name.") - } - } - - cachedJN.toString - } - - def descriptor(t: Type): String = { javaType(t).getDescriptor } - def descriptor(k: TypeKind): String = { javaType(k).getDescriptor } - def descriptor(s: Symbol): String = { javaType(s).getDescriptor } - - def javaType(tk: TypeKind): asm.Type = { - if(tk.isValueType) { - if(tk.isIntSizedType) { - (tk: @unchecked) match { - case BOOL => asm.Type.BOOLEAN_TYPE - case BYTE => asm.Type.BYTE_TYPE - case SHORT => asm.Type.SHORT_TYPE - case CHAR => asm.Type.CHAR_TYPE - case INT => asm.Type.INT_TYPE - } - } else { - (tk: @unchecked) match { - case UNIT => asm.Type.VOID_TYPE - case LONG => asm.Type.LONG_TYPE - case FLOAT => asm.Type.FLOAT_TYPE - case DOUBLE => asm.Type.DOUBLE_TYPE - } - } - } else { - assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway) - (tk: @unchecked) match { - case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) - case ARRAY(elem) => javaArrayType(javaType(elem)) - } - } - } - - def javaType(t: Type): asm.Type = javaType(toTypeKind(t)) - - def javaType(s: Symbol): asm.Type = { - if (s.isMethod) { - val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType); - asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _* ) - } else { javaType(s.tpe) } - } - - def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) } - - def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } - - def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) { - /** The outer name for this inner class. Note that it returns null - * when the inner class should not get an index in the constant pool. - * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. - */ - def outerName(innerSym: Symbol): String = { - if (innerSym.originalEnclosingMethod != NoSymbol) - null - else { - val outerName = javaName(innerSym.rawowner) - if (isTopLevelModule(innerSym.rawowner)) "" + nme.stripModuleSuffix(newTermName(outerName)) - else outerName - } - } - - def innerName(innerSym: Symbol): String = - if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) - null - else - innerSym.rawname + innerSym.moduleSuffix - - // add inner classes which might not have been referenced yet - afterErasure { - for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass) - innerClassBuffer += m - } - - val allInners: List[Symbol] = innerClassBuffer.toList - if (allInners.nonEmpty) { - debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.") - - // entries ready to be serialized into the classfile, used to detect duplicates. - val entries = mutable.Map.empty[String, String] - - // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler - for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ?? - val flags = mkFlags( - if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, - javaFlags(innerSym), - if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag - ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) - val jname = javaName(innerSym) // never null - val oname = outerName(innerSym) // null when method-enclosed - val iname = innerName(innerSym) // null for anonymous inner class - - // Mimicking javap inner class output - debuglog( - if (oname == null || iname == null) "//class " + jname - else "//%s=class %s of class %s".format(iname, jname, oname) - ) - - assert(jname != null, "javaName is broken.") // documentation - val doAdd = entries.get(jname) match { - // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute) - case Some(prevOName) => - // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State, - // i.e. for them it must be the case that oname == java/lang/Thread - assert(prevOName == oname, "duplicate") - false - case None => true - } - - if(doAdd) { - entries += (jname -> oname) - jclass.visitInnerClass(jname, oname, iname, flags) - } - - /* - * TODO assert (JVMS 4.7.6 The InnerClasses attribute) - * If a class file has a version number that is greater than or equal to 51.0, and - * has an InnerClasses attribute in its attributes table, then for all entries in the - * classes array of the InnerClasses attribute, the value of the - * outer_class_info_index item must be zero if the value of the - * inner_name_index item is zero. - */ - - } - } - } - - } // end of class JBuilder - - - /** functionality for building plain and mirror classes */ - abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { - - def debugLevel = settings.debuginfo.indexOfChoice - - val emitSource = debugLevel >= 1 - val emitLines = debugLevel >= 2 - val emitVars = debugLevel >= 3 - - // ----------------------------------------------------------------------------------------- - // more constants - // ----------------------------------------------------------------------------------------- - - val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC - val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL - - val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString - - // ----------------------------------------------------------------------------------------- - // Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only - // i.e., the pickle is contained in a custom annotation, see: - // (1) `addAnnotations()`, - // (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10 - // (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5 - // That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9) - // other than both ending up encoded as attributes (JVMS 4.7) - // (with the caveat that the "ScalaSig" attribute is associated to some classes, - // while the "Signature" attribute can be associated to classes, methods, and fields.) - // ----------------------------------------------------------------------------------------- - - val versionPickle = { - val vp = new PickleBuffer(new Array[Byte](16), -1, 0) - assert(vp.writeIndex == 0, vp) - vp writeNat PickleFormat.MajorVersion - vp writeNat PickleFormat.MinorVersion - vp writeNat 0 - vp - } - - def pickleMarkerLocal = { - createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex) - } - - def pickleMarkerForeign = { - createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0) - } - - /** Returns a ScalaSignature annotation if it must be added to this class, none otherwise. - * This annotation must be added to the class' annotations list when generating them. - * - * Depending on whether the returned option is defined, it adds to `jclass` one of: - * (a) the ScalaSig marker attribute - * (indicating that a scala-signature-annotation aka pickle is present in this class); or - * (b) the Scala marker attribute - * (indicating that a scala-signature-annotation aka pickle is to be found in another file). - * - * - * @param jclassName The class file that is being readied. - * @param sym The symbol for which the signature has been entered in the symData map. - * This is different than the symbol - * that is being generated in the case of a mirror class. - * @return An option that is: - * - defined and contains an AnnotationInfo of the ScalaSignature type, - * instantiated with the pickle signature for sym. - * - empty if the jclass/sym pair must not contain a pickle. - * - */ - def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = { - currentRun.symData get sym match { - case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) => - val scalaAnnot = { - val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex)) - AnnotationInfo(sigBytes.sigAnnot, Nil, List((nme.bytes, sigBytes))) - } - pickledBytes += pickle.writeIndex - currentRun.symData -= sym - currentRun.symData -= sym.companionSymbol - Some(scalaAnnot) - case _ => - None - } - } - - /** - * Quoting from JVMS 4.7.5 The Exceptions Attribute - * "The Exceptions attribute indicates which checked exceptions a method may throw. - * There may be at most one Exceptions attribute in each method_info structure." - * - * The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod() - * This method returns such list of internal names. - * - */ - def getExceptions(excs: List[AnnotationInfo]): List[String] = { - for (AnnotationInfo(tp, List(exc), _) <- excs.distinct if tp.typeSymbol == ThrowsClass) - yield { - val Literal(const) = exc - javaName(const.typeValue.typeSymbol) - } - } - - /** Whether an annotation should be emitted as a Java annotation - * .initialize: if 'annot' is read from pickle, atp might be un-initialized - */ - private def shouldEmitAnnotation(annot: AnnotationInfo) = - annot.symbol.initialize.isJavaDefined && - annot.matches(ClassfileAnnotationClass) && - annot.args.isEmpty && - !annot.matches(DeprecatedAttr) - - // @M don't generate java generics sigs for (members of) implementation - // classes, as they are monomorphic (TODO: ok?) - private def needsGenericSignature(sym: Symbol) = !( - // PP: This condition used to include sym.hasExpandedName, but this leads - // to the total loss of generic information if a private member is - // accessed from a closure: both the field and the accessor were generated - // without it. This is particularly bad because the availability of - // generic information could disappear as a consequence of a seemingly - // unrelated change. - sym.isHidden - || sym.isLiftedMethod - || sym.isBridge - || (sym.ownerChain exists (_.isImplClass)) - ) - - def getCurrentCUnit(): CompilationUnit - - /** @return - * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). - * - otherwise the signature in question - */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = { - - if (!needsGenericSignature(sym)) { return null } - - val memberTpe = beforeErasure(owner.thisType.memberInfo(sym)) - - val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) - if (jsOpt.isEmpty) { return null } - - val sig = jsOpt.get - log(sig) // This seems useful enough in the general case. - - def wrap(op: => Unit) = { - try { op; true } - catch { case _ => false } - } - - if (settings.Xverify.value) { - // Run the signature parser to catch bogus signatures. - val isValidSignature = wrap { - // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) - import scala.tools.asm.util.SignatureChecker - if (sym.isMethod) { SignatureChecker checkMethodSignature sig } // requires asm-util.jar - else if (sym.isTerm) { SignatureChecker checkFieldSignature sig } - else { SignatureChecker checkClassSignature sig } - } - - if(!isValidSignature) { - getCurrentCUnit().warning(sym.pos, - """|compiler bug: created invalid generic signature for %s in %s - |signature: %s - |if this is reproducible, please report bug at https://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) - return null - } - } - - if ((settings.check containsName phaseName)) { - val normalizedTpe = beforeErasure(erasure.prepareSigMap(memberTpe)) - val bytecodeTpe = owner.thisType.memberInfo(sym) - if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { - getCurrentCUnit().warning(sym.pos, - """|compiler bug: created generic signature for %s in %s that does not conform to its erasure - |signature: %s - |original type: %s - |normalized type: %s - |erasure type: %s - |if this is reproducible, please report bug at http://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) - return null - } - } - - sig - } - - def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { - val ca = new Array[Char](bytes.size) - var idx = 0 - while(idx < bytes.size) { - val b: Byte = bytes(idx) - assert((b & ~0x7f) == 0) - ca(idx) = b.asInstanceOf[Char] - idx += 1 - } - - ca - } - - // TODO this method isn't exercised during bootstrapping. Open question: is it bug free? - private def arrEncode(sb: ScalaSigBytes): Array[String] = { - var strs: List[String] = Nil - val bSeven: Array[Byte] = sb.sevenBitsMayBeZero - // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) - var prevOffset = 0 - var offset = 0 - var encLength = 0 - while(offset < bSeven.size) { - val newEncLength = encLength.toLong + (if(bSeven(offset) == 0) 2 else 1) - if(newEncLength > 65535) { - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - encLength = 0 - prevOffset = offset - } else { - encLength += 1 - offset += 1 - } - } - if(prevOffset < offset) { - assert(offset == bSeven.length) - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - } - assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? - strs.reverse.toArray - } - - private def strEncode(sb: ScalaSigBytes): String = { - val ca = ubytesToCharArray(sb.sevenBitsMayBeZero) - new java.lang.String(ca) - // debug val bvA = new asm.ByteVector; bvA.putUTF8(s) - // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes) - // debug assert(enc(idx) == bvA.getByte(idx + 2)) - // debug assert(bvA.getLength == enc.size + 2) - } - - def emitArgument(av: asm.AnnotationVisitor, - name: String, - arg: ClassfileAnnotArg) { - arg match { - - case LiteralAnnotArg(const) => - if(const.isNonUnitAnyVal) { av.visit(name, const.value) } - else { - const.tag match { - case StringTag => - assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` - av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag - case ClazzTag => av.visit(name, javaType(const.typeValue)) - case EnumTag => - val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. - val evalue = const.symbolValue.name.toString // value the actual enumeration value. - av.visitEnum(name, edesc, evalue) - } - } - - case sb@ScalaSigBytes(bytes) => - // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files) - // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure. - val assocValue = (if(sb.fitsInOneString) strEncode(sb) else arrEncode(sb)) - av.visit(name, assocValue) - // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. - - case ArrayAnnotArg(args) => - val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- args) { emitArgument(arrAnnotV, null, arg) } - arrAnnotV.visitEnd() - - case NestedAnnotArg(annInfo) => - val AnnotationInfo(typ, args, assocs) = annInfo - assert(args.isEmpty, args) - val desc = descriptor(typ) // the class descriptor of the nested annotation class - val nestedVisitor = av.visitAnnotation(name, desc) - emitAssocs(nestedVisitor, assocs) - } - } - - def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) { - for ((name, value) <- assocs) { - emitArgument(av, name.toString(), value) - } - av.visitEnd() - } - - def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) { - for(annot <- annotations; if shouldEmitAnnotation(annot)) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), true) - emitAssocs(av, assocs) - } - } - - def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) { - for(annot <- annotations; if shouldEmitAnnotation(annot)) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), true) - emitAssocs(av, assocs) - } - } - - def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) { - for(annot <- annotations; if shouldEmitAnnotation(annot)) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), true) - emitAssocs(av, assocs) - } - } - - def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) { - val annotationss = pannotss map (_ filter shouldEmitAnnotation) - if (annotationss forall (_.isEmpty)) return - for (Pair(annots, idx) <- annotationss.zipWithIndex; - annot <- annots) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true) - emitAssocs(pannVisitor, assocs) - } - } - - /** Adds a @remote annotation, actual use unknown. - * - * Invoked from genMethod() and addForwarder(). - */ - def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { - val needsAnnotation = ( - ( isRemoteClass || - isRemote(meth) && isJMethodPublic - ) && !(meth.throwsAnnotations contains RemoteExceptionClass) - ) - if (needsAnnotation) { - val c = Constant(RemoteExceptionClass.tpe) - val arg = Literal(c) setType c.tpe - meth.addAnnotation(ThrowsClass, arg) - } - } - - // ----------------------------------------------------------------------------------------- - // Static forwarders (related to mirror classes but also present in - // a plain class lacking companion module, for details see `isCandidateForForwarders`). - // ----------------------------------------------------------------------------------------- - - val ExcludedForwarderFlags = { - import Flags._ - // Should include DEFERRED but this breaks findMember. - ( CASE | SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags ) - } - - /** Add a forwarder for method m. Used only from addForwarders(). */ - private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { - val moduleName = javaName(module) - val methodInfo = module.thisType.memberInfo(m) - val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType - // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) - - /** Forwarders must not be marked final, - * as the JVM will not allow redefinition of a final static method, - * and we don't know what classes might be subclassing the companion class. See SI-4827. - */ - // TODO: evaluate the other flags we might be dropping on the floor here. - // TODO: ACC_SYNTHETIC ? - val flags = PublicStatic | ( - if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0 - ) - - // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 - addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) - val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) - val thrownExceptions: List[String] = getExceptions(throws) - - val jReturnType = javaType(methodInfo.resultType) - val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*) - val mirrorMethodName = javaName(m) - val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( - flags, - mirrorMethodName, - mdesc, - jgensig, - mkArray(thrownExceptions) - ) - - // typestate: entering mode with valid call sequences: - // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* - - emitAnnotations(mirrorMethod, others) - emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations)) - - // typestate: entering mode with valid call sequences: - // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd - - mirrorMethod.visitCode() - - mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) - - var index = 0 - for(jparamType <- paramJavaTypes) { - mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index) - assert(jparamType.getSort() != asm.Type.METHOD, jparamType) - index += jparamType.getSize() - } - - mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(m).getDescriptor) - mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN)) - - mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments - mirrorMethod.visitEnd() - - } - - /** Add forwarders for all methods defined in `module` that don't conflict - * with methods in the companion class of `module`. A conflict arises when - * a method with the same name is defined both in a class and its companion object: - * method signature is not taken into account. - */ - def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) { - assert(moduleClass.isModuleClass, moduleClass) - debuglog("Dumping mirror class for object: " + moduleClass) - - val linkedClass = moduleClass.companionClass - val linkedModule = linkedClass.companionSymbol - lazy val conflictingNames: Set[Name] = { - linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name } toSet - } - debuglog("Potentially conflicting names for forwarders: " + conflictingNames) - - for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) { - if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor) - debuglog("No forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass)) - else if (conflictingNames(m.name)) - log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name)) - else { - log("Adding static forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass)) - addForwarder(isRemoteClass, jclass, moduleClass, m) - } - } - } - - } // end of class JCommonBuilder - - - trait JAndroidBuilder { - self: JPlainBuilder => - - /** From the reference documentation of the Android SDK: - * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`. - * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`, - * which is an object implementing the `Parcelable.Creator` interface. - */ - private val androidFieldName = newTermName("CREATOR") - - private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") - private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") - - def isAndroidParcelableClass(sym: Symbol) = - (AndroidParcelableInterface != NoSymbol) && - (sym.parentSymbols contains AndroidParcelableInterface) - - /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */ - def addCreatorCode(block: BasicBlock) { - val fieldSymbol = ( - clasz.symbol.newValue(newTermName(androidFieldName), NoPosition, Flags.STATIC | Flags.FINAL) - setInfo AndroidCreatorClass.tpe - ) - val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName) - clasz addField new IField(fieldSymbol) - block emit CALL_METHOD(methodSymbol, Static(false)) - block emit STORE_FIELD(fieldSymbol, true) - } - - def legacyAddCreatorCode(clinit: asm.MethodVisitor) { - val creatorType: asm.Type = javaType(AndroidCreatorClass) - val tdesc_creator = creatorType.getDescriptor - - jclass.visitField( - PublicStaticFinal, - androidFieldName, - tdesc_creator, - null, // no java-generic-signature - null // no initial value - ).visitEnd() - - val moduleName = javaName(clasz.symbol)+"$" - - // GETSTATIC `moduleName`.MODULE$ : `moduleName`; - clinit.visitFieldInsn( - asm.Opcodes.GETSTATIC, - moduleName, - strMODULE_INSTANCE_FIELD, - asm.Type.getObjectType(moduleName).getDescriptor - ) - - // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator; - clinit.visitMethodInsn( - asm.Opcodes.INVOKEVIRTUAL, - moduleName, - androidFieldName, - asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*) - ) - - // PUTSTATIC `thisName`.CREATOR; - clinit.visitFieldInsn( - asm.Opcodes.PUTSTATIC, - thisName, - androidFieldName, - tdesc_creator - ) - } - - } // end of trait JAndroidBuilder - - /** Map from type kinds to the Java reference types. - * It is used to push class literals onto the operand stack. - * @see Predef.classOf - * @see genConstant() - */ - private val classLiteral = immutable.Map[TypeKind, asm.Type]( - UNIT -> asm.Type.getObjectType("java/lang/Void"), - BOOL -> asm.Type.getObjectType("java/lang/Boolean"), - BYTE -> asm.Type.getObjectType("java/lang/Byte"), - SHORT -> asm.Type.getObjectType("java/lang/Short"), - CHAR -> asm.Type.getObjectType("java/lang/Character"), - INT -> asm.Type.getObjectType("java/lang/Integer"), - LONG -> asm.Type.getObjectType("java/lang/Long"), - FLOAT -> asm.Type.getObjectType("java/lang/Float"), - DOUBLE -> asm.Type.getObjectType("java/lang/Double") - ) - - def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT } - - case class MethodNameAndType(mname: String, mdesc: String) - - private val jBoxTo: Map[TypeKind, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , - BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , - CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , - SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , - INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , - LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , - FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , - DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) - ) - } - - private val jUnboxTo: Map[TypeKind, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , - BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , - CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , - SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , - INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , - LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , - FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , - DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") - ) - } - - case class BlockInteval(start: BasicBlock, end: BasicBlock) - - /** builder of plain classes */ - class JPlainBuilder(bytecodeWriter: BytecodeWriter) - extends JCommonBuilder(bytecodeWriter) - with JAndroidBuilder { - - val MIN_SWITCH_DENSITY = 0.7 - - val StringBuilderClassName = javaName(definitions.StringBuilderClass) - val BoxesRunTime = "scala/runtime/BoxesRunTime" - - val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName) - val mdesc_toString = "()Ljava/lang/String;" - val mdesc_arrayClone = "()Ljava/lang/Object;" - - val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J" - - def isParcelableClass = isAndroidParcelableClass(clasz.symbol) - - def serialVUID: Option[Long] = clasz.symbol getAnnotation SerialVersionUIDAttr collect { - case AnnotationInfo(_, Literal(const) :: _, _) => const.longValue - } - - private def getSuperInterfaces(c: IClass): Array[String] = { - - // Additional interface parents based on annotations and other cues - def newParentForAttr(attr: Symbol): Option[Symbol] = attr match { - case SerializableAttr => Some(SerializableClass) - case CloneableAttr => Some(CloneableClass) - case RemoteAttr => Some(RemoteInterfaceClass) - case _ => None - } - - /** Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents. - * This is important on Android because there is otherwise an interface explosion. - */ - def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { - var rest = lstIfaces - var leaves = List.empty[Symbol] - while(!rest.isEmpty) { - val candidate = rest.head - val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } - if(!nonLeaf) { - leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) - } - rest = rest.tail - } - - leaves - } - - val ps = c.symbol.info.parents - val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses; - val superInterfaces = superInterfaces0 ++ c.symbol.annotations.flatMap(ann => newParentForAttr(ann.symbol)) distinct - - if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY - else mkArray(minimizeInterfaces(superInterfaces) map javaName) - } - - var clasz: IClass = _ // this var must be assigned only by genClass() - var jclass: asm.ClassWriter = _ // the classfile being emitted - var thisName: String = _ // the internal name of jclass - - def thisDescr: String = { - assert(thisName != null, "thisDescr invoked too soon.") - asm.Type.getObjectType(thisName).getDescriptor - } - - def getCurrentCUnit(): CompilationUnit = { clasz.cunit } - - def genClass(c: IClass) { - clasz = c - innerClassBuffer.clear() - - thisName = javaName(c.symbol) // the internal name of the class being emitted - - val ps = c.symbol.info.parents - val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol); - - val ifaces = getSuperInterfaces(c) - - val thisSignature = getGenericSignature(c.symbol, c.symbol.owner) - val flags = mkFlags( - javaFlags(c.symbol), - if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - jclass = createJClass(flags, - thisName, thisSignature, - superClass, ifaces) - - // typestate: entering mode with valid call sequences: - // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* - - if(emitSource) { - jclass.visitSource(c.cunit.source.toString, - null /* SourceDebugExtension */) - } - - val enclM = getEnclosingMethodAttribute() - if(enclM != null) { - val EnclMethodEntry(className, methodName, methodType) = enclM - jclass.visitOuterClass(className, methodName, methodType.getDescriptor) - } - - // typestate: entering mode with valid call sequences: - // ( visitAnnotation | visitAttribute )* - - val ssa = getAnnotPickle(thisName, c.symbol) - jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) - emitAnnotations(jclass, c.symbol.annotations ++ ssa) - - // typestate: entering mode with valid call sequences: - // ( visitInnerClass | visitField | visitMethod )* visitEnd - - if (isStaticModule(c.symbol) || isParcelableClass) { - - if (isStaticModule(c.symbol)) { addModuleInstanceField() } - addStaticInit(c.lookupStaticCtor) - - } else { - - for (constructor <- c.lookupStaticCtor) { - addStaticInit(Some(constructor)) - } - val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders.value) - if (!skipStaticForwarders) { - val lmoc = c.symbol.companionModule - // add static forwarders if there are no name conflicts; see bugs #363 and #1735 - if (lmoc != NoSymbol) { - // it must be a top level class (name contains no $s) - val isCandidateForForwarders = { - afterPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } - } - if (isCandidateForForwarders) { - log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc)) - addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass) - } - } - } - - } - - // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` - serialVUID foreach { value => - val fieldName = "serialVersionUID" - jclass.visitField( - PublicStaticFinal, - fieldName, - tdesc_long, - null, // no java-generic-signature - value - ).visitEnd() - } - - clasz.fields foreach genField - clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) } - - addInnerClasses(clasz.symbol, jclass) - jclass.visitEnd() - writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) - - } - - /** - * @param owner internal name of the enclosing class of the class. - * - * @param name the name of the method that contains the class. - - * @param methodType the method that contains the class. - */ - case class EnclMethodEntry(owner: String, name: String, methodType: asm.Type) - - /** - * @return null if the current class is not internal to a method - * - * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute - * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. - * A class may have no more than one EnclosingMethod attribute. - * - */ - private def getEnclosingMethodAttribute(): EnclMethodEntry = { // JVMS 4.7.7 - var res: EnclMethodEntry = null - val clazz = clasz.symbol - val sym = clazz.originalEnclosingMethod - if (sym.isMethod) { - debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, sym.enclClass)) - res = EnclMethodEntry(javaName(sym.enclClass), javaName(sym), javaType(sym)) - } else if (clazz.isAnonymousClass) { - val enclClass = clazz.rawowner - assert(enclClass.isClass, enclClass) - val sym = enclClass.primaryConstructor - if (sym == NoSymbol) { - log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass, clazz)) - } else { - debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass)) - res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym)) - } - } - - res - } - - def genField(f: IField) { - debuglog("Adding field: " + f.symbol.fullName) - - val javagensig = getGenericSignature(f.symbol, clasz.symbol) - - val flags = mkFlags( - javaFieldFlags(f.symbol), - if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - val jfield: asm.FieldVisitor = jclass.visitField( - flags, - javaName(f.symbol), - javaType(f.symbol.tpe).getDescriptor(), - javagensig, - null // no initial value - ) - - emitAnnotations(jfield, f.symbol.annotations) - jfield.visitEnd() - } - - var method: IMethod = _ - var jmethod: asm.MethodVisitor = _ - var jMethodName: String = _ - - @inline final def emit(opc: Int) { jmethod.visitInsn(opc) } - - def genMethod(m: IMethod, isJInterface: Boolean) { - - def isClosureApply(sym: Symbol): Boolean = { - (sym.name == nme.apply) && - sym.owner.isSynthetic && - sym.owner.tpe.parents.exists { t => - val TypeRef(_, sym, _) = t - FunctionClass contains sym - } - } - - if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return - - debuglog("Generating method " + m.symbol.fullName) - method = m - computeLocalVarsIndex(m) - - var resTpe: asm.Type = javaType(m.symbol.tpe.resultType) - if (m.symbol.isClassConstructor) - resTpe = asm.Type.VOID_TYPE - - val flags = mkFlags( - javaFlags(m.symbol), - if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0, - if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, - if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes - if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } - val jgensig = getGenericSignature(m.symbol, clasz.symbol) - addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol) - val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass) - val thrownExceptions: List[String] = getExceptions(excs) - - jMethodName = javaName(m.symbol) - val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*) - jmethod = jclass.visitMethod( - flags, - jMethodName, - mdesc, - jgensig, - mkArray(thrownExceptions) - ) - - // TODO param names: (m.params map (p => javaName(p.sym))) - - // typestate: entering mode with valid call sequences: - // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* - - emitAnnotations(jmethod, others) - emitParamAnnotations(jmethod, m.params.map(_.sym.annotations)) - - // typestate: entering mode with valid call sequences: - // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd - // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code, - // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and - // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited. - - val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0) - val hasCodeAttribute = (!hasAbstractBitSet && !method.native) - if (hasCodeAttribute) { - - jmethod.visitCode() - - if (emitVars && isClosureApply(method.symbol)) { - // add a fake local for debugging purposes - val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL) - if (outerField != NoSymbol) { - log("Adding fake local to represent outer 'this' for closure " + clasz) - val _this = - new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS), - toTypeKind(outerField.tpe), - false) - m.locals = m.locals ::: List(_this) - computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes - jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) - jmethod.visitFieldInsn(asm.Opcodes.GETFIELD, - javaName(clasz.symbol), // field owner - javaName(outerField), // field name - descriptor(outerField) // field descriptor - ) - assert(_this.kind.isReferenceType, _this.kind) - jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this)) - } - } - - assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals ) - - val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0) - genCode(m, emitVars, hasStaticBitSet) - - jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments - } - - jmethod.visitEnd() - - } - - def addModuleInstanceField() { - val fv = - jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED - strMODULE_INSTANCE_FIELD, - thisDescr, - null, // no java-generic-signature - null // no initial value - ) - - // typestate: entering mode with valid call sequences: - // ( visitAnnotation | visitAttribute )* visitEnd. - - fv.visitEnd() - } - - - /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */ - def addStaticInit(mopt: Option[IMethod]) { - - val clinitMethod: asm.MethodVisitor = jclass.visitMethod( - PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED - CLASS_CONSTRUCTOR_NAME, - mdesc_arglessvoid, - null, // no java-generic-signature - null // no throwable exceptions - ) - - mopt match { - - case Some(m) => - - val oldLastBlock = m.lastBlock - val lastBlock = m.newBlock() - oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock)) - - if (isStaticModule(clasz.symbol)) { - // call object's private ctor from static ctor - lastBlock emit NEW(REFERENCE(m.symbol.enclClass)) - lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(true)) - } - - if (isParcelableClass) { addCreatorCode(lastBlock) } - - lastBlock emit RETURN(UNIT) - lastBlock.close - - method = m - jmethod = clinitMethod - jMethodName = CLASS_CONSTRUCTOR_NAME - jmethod.visitCode() - genCode(m, false, true) - jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments - jmethod.visitEnd() - - case None => - clinitMethod.visitCode() - legacyStaticInitializer(clinitMethod) - clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments - clinitMethod.visitEnd() - - } - } - - /* used only from addStaticInit() */ - private def legacyStaticInitializer(clinit: asm.MethodVisitor) { - if (isStaticModule(clasz.symbol)) { - clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) - clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, - thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid) - } - - if (isParcelableClass) { legacyAddCreatorCode(clinit) } - - clinit.visitInsn(asm.Opcodes.RETURN) - } - - // ----------------------------------------------------------------------------------------- - // Emitting bytecode instructions. - // ----------------------------------------------------------------------------------------- - - private def genConstant(mv: asm.MethodVisitor, const: Constant) { - const.tag match { - - case BooleanTag => jcode.boolconst(const.booleanValue) - - case ByteTag => jcode.iconst(const.byteValue) - case ShortTag => jcode.iconst(const.shortValue) - case CharTag => jcode.iconst(const.charValue) - case IntTag => jcode.iconst(const.intValue) - - case LongTag => jcode.lconst(const.longValue) - case FloatTag => jcode.fconst(const.floatValue) - case DoubleTag => jcode.dconst(const.doubleValue) - - case UnitTag => () - - case StringTag => - assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` - mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag - - case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL) - - case ClazzTag => - val kind = toTypeKind(const.typeValue) - val toPush: asm.Type = - if (kind.isValueType) classLiteral(kind) - else javaType(kind); - mv.visitLdcInsn(toPush) - - case EnumTag => - val sym = const.symbolValue - mv.visitFieldInsn( - asm.Opcodes.GETSTATIC, - javaName(sym.owner), - javaName(sym), - javaType(sym.tpe.underlying).getDescriptor() - ) - - case _ => abort("Unknown constant value: " + const) - } - } - - /** Just a namespace for utilities that encapsulate MethodVisitor idioms. - * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role, - * but the methods here allow choosing when to transition from ICode to ASM types - * (including not at all, e.g. for performance). - */ - object jcode { - - import asm.Opcodes; - - def aconst(cst: AnyRef) { - if (cst == null) { jmethod.visitInsn(Opcodes.ACONST_NULL) } - else { jmethod.visitLdcInsn(cst) } - } - - @inline final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) } - - def iconst(cst: Int) { - if (cst >= -1 && cst <= 5) { - jmethod.visitInsn(Opcodes.ICONST_0 + cst) - } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) { - jmethod.visitIntInsn(Opcodes.BIPUSH, cst) - } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) { - jmethod.visitIntInsn(Opcodes.SIPUSH, cst) - } else { - jmethod.visitLdcInsn(new Integer(cst)) - } - } - - def lconst(cst: Long) { - if (cst == 0L || cst == 1L) { - jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int]) - } else { - jmethod.visitLdcInsn(new java.lang.Long(cst)) - } - } - - def fconst(cst: Float) { - val bits: Int = java.lang.Float.floatToIntBits(cst) - if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 - jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int]) - } else { - jmethod.visitLdcInsn(new java.lang.Float(cst)) - } - } - - def dconst(cst: Double) { - val bits: Long = java.lang.Double.doubleToLongBits(cst) - if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d - jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int]) - } else { - jmethod.visitLdcInsn(new java.lang.Double(cst)) - } - } - - def newarray(elem: TypeKind) { - if(elem.isRefOrArrayType) { - jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName) - } else { - val rand = { - if(elem.isIntSizedType) { - (elem: @unchecked) match { - case BOOL => Opcodes.T_BOOLEAN - case BYTE => Opcodes.T_BYTE - case SHORT => Opcodes.T_SHORT - case CHAR => Opcodes.T_CHAR - case INT => Opcodes.T_INT - } - } else { - (elem: @unchecked) match { - case LONG => Opcodes.T_LONG - case FLOAT => Opcodes.T_FLOAT - case DOUBLE => Opcodes.T_DOUBLE - } - } - } - jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) - } - } - - - @inline def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) } - @inline def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) } - - @inline def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) } - @inline def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) } - - @inline def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) } - @inline def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) } - @inline def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) } - @inline def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) } - @inline def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) } - @inline def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) } - - @inline def invokespecial(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc) - } - @inline def invokestatic(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc) - } - @inline def invokeinterface(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc) - } - @inline def invokevirtual(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc) - } - - @inline def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } - @inline def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) } - @inline def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) } - @inline def emitIF_ACMP(cond: TestOp, label: asm.Label) { - assert((cond == EQ) || (cond == NE), cond) - val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) - jmethod.visitJumpInsn(opc, label) - } - @inline def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) } - @inline def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) } - - @inline def emitRETURN(tk: TypeKind) { - if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) } - else { emitTypeBased(returnOpcodes, tk) } - } - - /** Emits one of tableswitch or lookoupswitch. */ - def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) { - assert(keys.length == branches.length) - - // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only. - // Similar to what javac emits for a switch statement consisting only of a default case. - if (keys.length == 0) { - jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) - return - } - - // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort - var i = 1 - while (i < keys.length) { - var j = 1 - while (j <= keys.length - i) { - if (keys(j) < keys(j - 1)) { - val tmp = keys(j) - keys(j) = keys(j - 1) - keys(j - 1) = tmp - val tmpL = branches(j) - branches(j) = branches(j - 1) - branches(j - 1) = tmpL - } - j += 1 - } - i += 1 - } - - // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011) - i = 1 - while (i < keys.length) { - if(keys(i-1) == keys(i)) { - abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") - } - i += 1 - } - - val keyMin = keys(0) - val keyMax = keys(keys.length - 1) - - val isDenseEnough: Boolean = { - /** Calculate in long to guard against overflow. TODO what overflow??? */ - val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] - val klenD: Double = keys.length - val kdensity: Double = (klenD / keyRangeD) - - kdensity >= minDensity - } - - if (isDenseEnough) { - // use a table in which holes are filled with defaultBranch. - val keyRange = (keyMax - keyMin + 1) - val newBranches = new Array[asm.Label](keyRange) - var oldPos = 0; - var i = 0 - while(i < keyRange) { - val key = keyMin + i; - if (keys(oldPos) == key) { - newBranches(i) = branches(oldPos) - oldPos += 1 - } else { - newBranches(i) = defaultBranch - } - i += 1 - } - assert(oldPos == keys.length, "emitSWITCH") - jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*) - } else { - jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) - } - } - - // internal helpers -- not part of the public API of `jcode` - // don't make private otherwise inlining will suffer - - def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) { - assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc) - jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx) - } - - // ---------------- array load and store ---------------- - - val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) } - val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) } - - val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) } - - def emitTypeBased(opcs: Array[Int], tk: TypeKind) { - assert(tk != UNIT, tk) - val opc = { - if(tk.isRefOrArrayType) { opcs(0) } - else if(tk.isIntSizedType) { - (tk: @unchecked) match { - case BOOL | BYTE => opcs(1) - case SHORT => opcs(2) - case CHAR => opcs(3) - case INT => opcs(4) - } - } else { - (tk: @unchecked) match { - case LONG => opcs(5) - case FLOAT => opcs(6) - case DOUBLE => opcs(7) - } - } - } - jmethod.visitInsn(opc) - } - - // ---------------- primitive operations ---------------- - - val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) } - val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) } - val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) } - val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) } - val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) } - val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) } - - def emitPrimitive(opcs: Array[Int], tk: TypeKind) { - val opc = { - if(tk.isIntSizedType) { opcs(0) } - else { - (tk: @unchecked) match { - case LONG => opcs(1) - case FLOAT => opcs(2) - case DOUBLE => opcs(3) - } - } - } - jmethod.visitInsn(opc) - } - - } - - /** Invoked from genMethod() and addStaticInit() */ - def genCode(m: IMethod, - emitVars: Boolean, // this param name hides the instance-level var - isStatic: Boolean) { - - - newNormal.normalize(m) - - // ------------------------------------------------------------------------------------------------------------ - // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization` - // ------------------------------------------------------------------------------------------------------------ - - val linearization: List[BasicBlock] = linearizer.linearize(m) - if(linearization.isEmpty) { return } - - var isModuleInitialized = false - - val labels: collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*) - - val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted - - // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label. - val linNext: collection.Map[BasicBlock, asm.Label] = { - val result = mutable.HashMap.empty[BasicBlock, asm.Label] - var rest = linearization - var prev = rest.head - rest = rest.tail - while(!rest.isEmpty) { - result += (prev -> labels(rest.head)) - prev = rest.head - rest = rest.tail - } - assert(!result.contains(prev)) - result += (prev -> onePastLast) - - result - } - - // ------------------------------------------------------------------------------------------------------------ - // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock()) - // ------------------------------------------------------------------------------------------------------------ - - /**Generate exception handlers for the current method. - * - * Quoting from the JVMS 4.7.3 The Code Attribute - * The items of the Code_attribute structure are as follows: - * . . . - * exception_table[] - * Each entry in the exception_table array describes one - * exception handler in the code array. The order of the handlers in - * the exception_table array is significant. - * Each exception_table entry contains the following four items: - * start_pc, end_pc: - * ... The value of end_pc either must be a valid index into - * the code array of the opcode of an instruction or must be equal to code_length, - * the length of the code array. - * handler_pc: - * The value of the handler_pc item indicates the start of the exception handler - * catch_type: - * ... If the value of the catch_type item is zero, - * this exception handler is called for all exceptions. - * This is used to implement finally - */ - def genExceptionHandlers() { - - /** Return a list of pairs of intervals where the handler is active. - * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end]. - * Preconditions: - * - e.covered non-empty - * Postconditions for the result: - * - always non-empty - * - intervals are sorted as per `linearization` - * - the argument's `covered` blocks have been grouped into maximally contiguous intervals, - * ie. between any two intervals in the result there is a non-empty gap. - * - each of the `covered` blocks in the argument is contained in some interval in the result - */ - def intervals(e: ExceptionHandler): List[BlockInteval] = { - assert(e.covered.nonEmpty, e) - var result: List[BlockInteval] = Nil - var rest = linearization - - // find intervals - while(!rest.isEmpty) { - // find interval start - var start: BasicBlock = null - while(!rest.isEmpty && (start eq null)) { - if(e.covered(rest.head)) { start = rest.head } - rest = rest.tail - } - if(start ne null) { - // find interval end - var end = start // for the time being - while(!rest.isEmpty && (e.covered(rest.head))) { - end = rest.head - rest = rest.tail - } - result = BlockInteval(start, end) :: result - } - } - - assert(result.nonEmpty, e) - - result - } - - /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains - * blocks not in the linearization (dead-code?). Is that well-formed or not? - * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does). - */ - for (e <- this.method.exh) { - val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } ) - // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)") - if(ignore.nonEmpty) { - e.covered = e.covered filterNot ignore - } - } - - // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table. - // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class? - for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) { - debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method + - " from: " + p.start + " to: " + p.end + " catching: " + e.cls); - val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null - else javaName(e.cls) - jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls) - } - } // end of genCode()'s genExceptionHandlers() - - if (m.exh.nonEmpty) { genExceptionHandlers() } - - // ------------------------------------------------------------------------------------------------------------ - // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute). - // ------------------------------------------------------------------------------------------------------------ - - case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive. - - case class Interval(lstart: asm.Label, lend: asm.Label) { - @inline final def start = lstart.getOffset - @inline final def end = lend.getOffset - - def precedes(that: Interval): Boolean = { this.end < that.start } - - def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) } - - def mergeWith(that: Interval): Interval = { - val newStart = if(this.start <= that.start) this.lstart else that.lstart; - val newEnd = if(this.end <= that.end) that.lend else this.lend; - Interval(newStart, newEnd) - } - - def repOK: Boolean = { start <= end } - - } - - /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */ - object scoping { - - private val pending = mutable.Map.empty[Local, mutable.Stack[Label]] - private var seen: List[LocVarEntry] = Nil - - private def fuse(ranges: List[Interval], added: Interval): List[Interval] = { - assert(added.repOK, added) - if(ranges.isEmpty) { return List(added) } - // precond: ranges is sorted by increasing start - var fused: List[Interval] = Nil - var done = false - var rest = ranges - while(!done && rest.nonEmpty) { - val current = rest.head - assert(current.repOK, current) - rest = rest.tail - if(added precedes current) { - fused = fused ::: ( added :: current :: rest ) - done = true - } else if(current overlaps added) { - fused = fused ::: ( added.mergeWith(current) :: rest ) - done = true - } - } - if(!done) { fused = fused ::: List(added) } - assert(repOK(fused), fused) - - fused - } - - def pushScope(lv: Local, start: Label) { - val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label]) - st.push(start) - } - def popScope(lv: Local, end: Label, iPos: Position) { - pending.get(lv) match { - case Some(st) if st.nonEmpty => - val start = st.pop() - seen ::= LocVarEntry(lv, start, end) - case _ => - // TODO SI-6049 - getCurrentCUnit().warning(iPos, "Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6049") - } - } - - def getMerged(): collection.Map[Local, List[Interval]] = { - // TODO should but isn't: unbalanced start(s) of scope(s) - val shouldBeEmpty = pending filter { p => val Pair(k, st) = p; st.nonEmpty }; - - val merged = mutable.Map.empty[Local, List[Interval]] - - def addToMerged(lv: Local, start: Label, end: Label) { - val ranges = merged.getOrElseUpdate(lv, Nil) - val coalesced = fuse(ranges, Interval(start, end)) - merged.update(lv, coalesced) - } - - for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) } - - /* for each var with unbalanced start(s) of scope(s): - (a) take the earliest start (among unbalanced and balanced starts) - (b) take the latest end (onePastLast if none available) - (c) merge the thus made-up interval - */ - for(Pair(k, st) <- shouldBeEmpty) { - var start = st.toList.sortBy(_.getOffset).head - if(merged.isDefinedAt(k)) { - val balancedStart = merged(k).head.lstart - if(balancedStart.getOffset < start.getOffset) { - start = balancedStart; - } - } - val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend; - val end = endOpt.getOrElse(onePastLast) - addToMerged(k, start, end) - } - - merged - } - - private def repOK(fused: List[Interval]): Boolean = { - fused match { - case Nil => true - case h :: Nil => h.repOK - case h :: n :: rest => - h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest) - } - } - - } - - def genLocalVariableTable() { - // adding `this` and method params. - if (!isStatic) { - jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0) - } - for(lv <- m.params) { - jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv)) - } - // adding non-param locals - var anonCounter = 0 - var fltnd: List[Triple[String, Local, Interval]] = Nil - for(Pair(local, ranges) <- scoping.getMerged()) { - var name = javaName(local.sym) - if (name == null) { - anonCounter += 1; - name = "" - } - for(intrvl <- ranges) { - fltnd ::= Triple(name, local, intrvl) - } - } - // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain). - val srtd = fltnd.sortBy { kr => - val Triple(name: String, local: Local, intrvl: Interval) = kr - - Triple(intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name) - } - - for(Triple(name, local, Interval(start, end)) <- srtd) { - jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local)) - } - // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute" - } - - // ------------------------------------------------------------------------------------------------------------ - // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position. - // ------------------------------------------------------------------------------------------------------------ - - case class LineNumberEntry(line: Int, start: asm.Label) - var lastLineNr: Int = -1 - var lnEntries: List[LineNumberEntry] = Nil - - // ------------------------------------------------------------------------------------------------------------ - // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()). - // ------------------------------------------------------------------------------------------------------------ - - var nextBlock: BasicBlock = linearization.head - - def genBlocks(l: List[BasicBlock]): Unit = l match { - case Nil => () - case x :: Nil => nextBlock = null; genBlock(x) - case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys) - } - - def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = { - target.isPublic || target.isProtected && { - (site.enclClass isSubClass target.enclClass) || - (site.enclosingPackage == target.privateWithin) - } - } // end of genCode()'s isAccessibleFrom() - - def genCallMethod(call: CALL_METHOD) { - val CALL_METHOD(method, style) = call - val siteSymbol = clasz.symbol - val hostSymbol = call.hostClass - val methodOwner = method.owner - // info calls so that types are up to date; erasure may add lateINTERFACE to traits - hostSymbol.info ; methodOwner.info - - def isInterfaceCall(sym: Symbol) = ( - sym.isInterface && methodOwner != ObjectClass - || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass) - ) - // whether to reference the type of the receiver or - // the type of the method owner (if not an interface!) - val useMethodOwner = ( - style != Dynamic - || !isInterfaceCall(hostSymbol) && isAccessibleFrom(methodOwner, siteSymbol) - || hostSymbol.isBottomClass - ) - val receiver = if (useMethodOwner) methodOwner else hostSymbol - val jowner = javaName(receiver) - val jname = javaName(method) - val jtype = javaType(method).getDescriptor() - - def dbg(invoke: String) { - debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype)) - } - - def initModule() { - // we initialize the MODULE$ field immediately after the super ctor - if (isStaticModule(siteSymbol) && !isModuleInitialized && - jMethodName == INSTANCE_CONSTRUCTOR_NAME && - jname == INSTANCE_CONSTRUCTOR_NAME) { - isModuleInitialized = true - jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) - jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr) - } - } - - style match { - case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype) - case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype) - case Dynamic if isInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype) - case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype) - case SuperCall(_) => - dbg("invokespecial") - jcode.invokespecial(jowner, jname, jtype) - initModule() - } - } // end of genCode()'s genCallMethod() - - def genBlock(b: BasicBlock) { - jmethod.visitLabel(labels(b)) - - import asm.Opcodes; - - debuglog("Generating code for block: " + b) - - // val lastInstr = b.lastInstruction - - for (instr <- b) { - - if(instr.pos.isDefined) { - val iPos = instr.pos - val currentLineNr = iPos.line - val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else - if(!skip) { - lastLineNr = currentLineNr - val lineLab = new asm.Label - jmethod.visitLabel(lineLab) - lnEntries ::= LineNumberEntry(currentLineNr, lineLab) - } - } - - (instr.category: @scala.annotation.switch) match { - - case icodes.localsCat => (instr: @unchecked) match { - case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0) - case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind) - case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind) - case STORE_THIS(_) => - // this only works for impl classes because the self parameter comes first - // in the method signature. If that changes, this code has to be revisited. - jmethod.visitVarInsn(Opcodes.ASTORE, 0) - - case SCOPE_ENTER(lv) => - // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored - val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) - if(relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars? - // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) - // similarly, these labels aren't tracked in the `labels` map. - val start = new asm.Label - jmethod.visitLabel(start) - scoping.pushScope(lv, start) - } - - case SCOPE_EXIT(lv) => - val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) - if(relevant) { - // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) - // similarly, these labels aren't tracked in the `labels` map. - val end = new asm.Label - jmethod.visitLabel(end) - scoping.popScope(lv, end, instr.pos) - } - } - - case icodes.stackCat => (instr: @unchecked) match { - - case LOAD_MODULE(module) => - // assert(module.isModule, "Expected module: " + module) - debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags)); - if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) { - jmethod.visitVarInsn(Opcodes.ALOAD, 0) - } else { - jmethod.visitFieldInsn( - Opcodes.GETSTATIC, - javaName(module) /* + "$" */ , - strMODULE_INSTANCE_FIELD, - descriptor(module) - ) - } - - case DROP(kind) => emit(if(kind.isWideType) Opcodes.POP2 else Opcodes.POP) - - case DUP(kind) => emit(if(kind.isWideType) Opcodes.DUP2 else Opcodes.DUP) - - case LOAD_EXCEPTION(_) => () - } - - case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant) - - case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos) - - case icodes.castsCat => (instr: @unchecked) match { - - case IS_INSTANCE(tpe) => - val jtyp: asm.Type = - tpe match { - case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) - case ARRAY(elem) => javaArrayType(javaType(elem)) - case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) - } - jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName) - - case CHECK_CAST(tpe) => - tpe match { - - case REFERENCE(cls) => - if (cls != ObjectClass) { // No need to checkcast for Objects - jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls)) - } - - case ARRAY(elem) => - val iname = javaArrayType(javaType(elem)).getInternalName - jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname) - - case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) - } - - } - - case icodes.objsCat => (instr: @unchecked) match { - - case BOX(kind) => - val MethodNameAndType(mname, mdesc) = jBoxTo(kind) - jcode.invokestatic(BoxesRunTime, mname, mdesc) - - case UNBOX(kind) => - val MethodNameAndType(mname, mdesc) = jUnboxTo(kind) - jcode.invokestatic(BoxesRunTime, mname, mdesc) - - case NEW(REFERENCE(cls)) => - val className = javaName(cls) - jmethod.visitTypeInsn(Opcodes.NEW, className) - - case MONITOR_ENTER() => emit(Opcodes.MONITORENTER) - case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT) - } - - case icodes.fldsCat => (instr: @unchecked) match { - - case lf @ LOAD_FIELD(field, isStatic) => - var owner = javaName(lf.hostClass) - debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags)) - val fieldJName = javaName(field) - val fieldDescr = descriptor(field) - val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD - jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) - - case STORE_FIELD(field, isStatic) => - val owner = javaName(field.owner) - val fieldJName = javaName(field) - val fieldDescr = descriptor(field) - val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD - jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) - - } - - case icodes.mthdsCat => (instr: @unchecked) match { - - /** Special handling to access native Array.clone() */ - case call @ CALL_METHOD(definitions.Array_clone, Dynamic) => - val target: String = javaType(call.targetTypeKind).getInternalName - jcode.invokevirtual(target, "clone", mdesc_arrayClone) - - case call @ CALL_METHOD(method, style) => genCallMethod(call) - - } - - case icodes.arraysCat => (instr: @unchecked) match { - case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind) - case STORE_ARRAY_ITEM(kind) => jcode.astore(kind) - case CREATE_ARRAY(elem, 1) => jcode newarray elem - case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims) - } - - case icodes.jumpsCat => (instr: @unchecked) match { - - case sw @ SWITCH(tagss, branches) => - assert(branches.length == tagss.length + 1, sw) - val flatSize = sw.flatTagsCount - val flatKeys = new Array[Int](flatSize) - val flatBranches = new Array[asm.Label](flatSize) - - var restTagss = tagss - var restBranches = branches - var k = 0 // ranges over flatKeys and flatBranches - while(restTagss.nonEmpty) { - val currLabel = labels(restBranches.head) - for(cTag <- restTagss.head) { - flatKeys(k) = cTag; - flatBranches(k) = currLabel - k += 1 - } - restTagss = restTagss.tail - restBranches = restBranches.tail - } - val defaultLabel = labels(restBranches.head) - assert(restBranches.tail.isEmpty) - debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches) - jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY) - - case JUMP(whereto) => - if (nextBlock != whereto) { - jcode goTo labels(whereto) - } - - case CJUMP(success, failure, cond, kind) => - if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT - if (nextBlock == success) { - jcode.emitIF_ICMP(cond.negate, labels(failure)) - // .. and fall through to success label - } else { - jcode.emitIF_ICMP(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) - if (nextBlock == success) { - jcode.emitIF_ACMP(cond.negate, labels(failure)) - // .. and fall through to success label - } else { - jcode.emitIF_ACMP(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } else { - (kind: @unchecked) match { - case LONG => emit(Opcodes.LCMP) - case FLOAT => - if (cond == LT || cond == LE) emit(Opcodes.FCMPG) - else emit(Opcodes.FCMPL) - case DOUBLE => - if (cond == LT || cond == LE) emit(Opcodes.DCMPG) - else emit(Opcodes.DCMPL) - } - if (nextBlock == success) { - jcode.emitIF(cond.negate, labels(failure)) - // .. and fall through to success label - } else { - jcode.emitIF(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } - - case CZJUMP(success, failure, cond, kind) => - if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT - if (nextBlock == success) { - jcode.emitIF(cond.negate, labels(failure)) - } else { - jcode.emitIF(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) - val Success = success - val Failure = failure - // @unchecked because references aren't compared with GT, GE, LT, LE. - ((cond, nextBlock) : @unchecked) match { - case (EQ, Success) => jcode emitIFNONNULL labels(failure) - case (NE, Failure) => jcode emitIFNONNULL labels(success) - case (EQ, Failure) => jcode emitIFNULL labels(success) - case (NE, Success) => jcode emitIFNULL labels(failure) - case (EQ, _) => - jcode emitIFNULL labels(success) - jcode goTo labels(failure) - case (NE, _) => - jcode emitIFNONNULL labels(success) - jcode goTo labels(failure) - } - } else { - (kind: @unchecked) match { - case LONG => - emit(Opcodes.LCONST_0) - emit(Opcodes.LCMP) - case FLOAT => - emit(Opcodes.FCONST_0) - if (cond == LT || cond == LE) emit(Opcodes.FCMPG) - else emit(Opcodes.FCMPL) - case DOUBLE => - emit(Opcodes.DCONST_0) - if (cond == LT || cond == LE) emit(Opcodes.DCMPG) - else emit(Opcodes.DCMPL) - } - if (nextBlock == success) { - jcode.emitIF(cond.negate, labels(failure)) - } else { - jcode.emitIF(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } - - } - - case icodes.retCat => (instr: @unchecked) match { - case RETURN(kind) => jcode emitRETURN kind - case THROW(_) => emit(Opcodes.ATHROW) - } - - } - - } - - } // end of genCode()'s genBlock() - - /** - * Emits one or more conversion instructions based on the types given as arguments. - * - * @param from The type of the value to be converted into another type. - * @param to The type the value will be converted into. - */ - def emitT2T(from: TypeKind, to: TypeKind) { - assert(isNonUnitValueTK(from), from) - assert(isNonUnitValueTK(to), to) - - def pickOne(opcs: Array[Int]) { - val chosen = (to: @unchecked) match { - case BYTE => opcs(0) - case SHORT => opcs(1) - case CHAR => opcs(2) - case INT => opcs(3) - case LONG => opcs(4) - case FLOAT => opcs(5) - case DOUBLE => opcs(6) - } - if(chosen != -1) { emit(chosen) } - } - - if(from == to) { return } - if((from == BOOL) || (to == BOOL)) { - // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) - throw new Error("inconvertible types : " + from.toString() + " -> " + to.toString()) - } - - if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already) - - val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT) - val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing - val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing - val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) } - - (from: @unchecked) match { - case BYTE => pickOne(fromByte) - case SHORT => pickOne(fromShort) - case CHAR => pickOne(fromChar) - case INT => pickOne(fromInt) - } - - } else { // FLOAT, LONG, DOUBLE - - (from: @unchecked) match { - case FLOAT => - import asm.Opcodes.{ F2L, F2D, F2I } - (to: @unchecked) match { - case LONG => emit(F2L) - case DOUBLE => emit(F2D) - case _ => emit(F2I); emitT2T(INT, to) - } - - case LONG => - import asm.Opcodes.{ L2F, L2D, L2I } - (to: @unchecked) match { - case FLOAT => emit(L2F) - case DOUBLE => emit(L2D) - case _ => emit(L2I); emitT2T(INT, to) - } - - case DOUBLE => - import asm.Opcodes.{ D2L, D2F, D2I } - (to: @unchecked) match { - case FLOAT => emit(D2F) - case LONG => emit(D2L) - case _ => emit(D2I); emitT2T(INT, to) - } - } - } - } // end of genCode()'s emitT2T() - - def genPrimitive(primitive: Primitive, pos: Position) { - - import asm.Opcodes; - - primitive match { - - case Negation(kind) => jcode.neg(kind) - - case Arithmetic(op, kind) => - op match { - - case ADD => jcode.add(kind) - case SUB => jcode.sub(kind) - case MUL => jcode.mul(kind) - case DIV => jcode.div(kind) - case REM => jcode.rem(kind) - - case NOT => - if(kind.isIntSizedType) { - emit(Opcodes.ICONST_M1) - emit(Opcodes.IXOR) - } else if(kind == LONG) { - jmethod.visitLdcInsn(new java.lang.Long(-1)) - jmethod.visitInsn(Opcodes.LXOR) - } else { - abort("Impossible to negate an " + kind) - } - - case _ => - abort("Unknown arithmetic primitive " + primitive) - } - - // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey) - // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead. - // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed). - case Logical(op, kind) => ((op, kind): @unchecked) match { - case (AND, LONG) => emit(Opcodes.LAND) - case (AND, INT) => emit(Opcodes.IAND) - case (AND, _) => - emit(Opcodes.IAND) - if (kind != BOOL) { emitT2T(INT, kind) } - - case (OR, LONG) => emit(Opcodes.LOR) - case (OR, INT) => emit(Opcodes.IOR) - case (OR, _) => - emit(Opcodes.IOR) - if (kind != BOOL) { emitT2T(INT, kind) } - - case (XOR, LONG) => emit(Opcodes.LXOR) - case (XOR, INT) => emit(Opcodes.IXOR) - case (XOR, _) => - emit(Opcodes.IXOR) - if (kind != BOOL) { emitT2T(INT, kind) } - } - - case Shift(op, kind) => ((op, kind): @unchecked) match { - case (LSL, LONG) => emit(Opcodes.LSHL) - case (LSL, INT) => emit(Opcodes.ISHL) - case (LSL, _) => - emit(Opcodes.ISHL) - emitT2T(INT, kind) - - case (ASR, LONG) => emit(Opcodes.LSHR) - case (ASR, INT) => emit(Opcodes.ISHR) - case (ASR, _) => - emit(Opcodes.ISHR) - emitT2T(INT, kind) - - case (LSR, LONG) => emit(Opcodes.LUSHR) - case (LSR, INT) => emit(Opcodes.IUSHR) - case (LSR, _) => - emit(Opcodes.IUSHR) - emitT2T(INT, kind) - } - - case Comparison(op, kind) => ((op, kind): @unchecked) match { - case (CMP, LONG) => emit(Opcodes.LCMP) - case (CMPL, FLOAT) => emit(Opcodes.FCMPL) - case (CMPG, FLOAT) => emit(Opcodes.FCMPG) - case (CMPL, DOUBLE) => emit(Opcodes.DCMPL) - case (CMPG, DOUBLE) => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc3.html - } - - case Conversion(src, dst) => - debuglog("Converting from: " + src + " to: " + dst) - if (dst == BOOL) { println("Illegal conversion at: " + clasz + " at: " + pos.source + ":" + pos.line) } - else { emitT2T(src, dst) } - - case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH) - - case StartConcat => - jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) - jmethod.visitInsn(Opcodes.DUP) - jcode.invokespecial( - StringBuilderClassName, - INSTANCE_CONSTRUCTOR_NAME, - mdesc_arglessvoid - ) - - case StringConcat(el) => - val jtype = el match { - case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT - case _ => javaType(el) - } - jcode.invokevirtual( - StringBuilderClassName, - "append", - asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*) - ) - - case EndConcat => - jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString) - - case _ => abort("Unimplemented primitive " + primitive) - } - } // end of genCode()'s genPrimitive() - - // ------------------------------------------------------------------------------------------------------------ - // Part 6 of genCode(): the executable part of genCode() starts here. - // ------------------------------------------------------------------------------------------------------------ - - genBlocks(linearization) - - jmethod.visitLabel(onePastLast) - - if(emitLines) { - for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) } - } - if(emitVars) { genLocalVariableTable() } - - } // end of BytecodeGenerator.genCode() - - - ////////////////////// local vars /////////////////////// - - // def sizeOf(sym: Symbol): Int = sizeOf(toTypeKind(sym.tpe)) - - def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1 - - // def indexOf(m: IMethod, sym: Symbol): Int = { - // val Some(local) = m lookupLocal sym - // indexOf(local) - // } - - @inline final def indexOf(local: Local): Int = { - assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ") - local.index - } - - /** - * Compute the indexes of each local variable of the given method. - * *Does not assume the parameters come first!* - */ - def computeLocalVarsIndex(m: IMethod) { - var idx = if (m.symbol.isStaticMember) 0 else 1; - - for (l <- m.params) { - debuglog("Index value for " + l + "{" + l.## + "}: " + idx) - l.index = idx - idx += sizeOf(l.kind) - } - - for (l <- m.locals if !l.arg) { - debuglog("Index value for " + l + "{" + l.## + "}: " + idx) - l.index = idx - idx += sizeOf(l.kind) - } - } - - } // end of class JPlainBuilder - - - /** builder of mirror classes */ - class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) { - - private var cunit: CompilationUnit = _ - def getCurrentCUnit(): CompilationUnit = cunit; - - /** Generate a mirror class for a top-level module. A mirror class is a class - * containing only static methods that forward to the corresponding method - * on the MODULE instance of the given Scala object. It will only be - * generated if there is no companion class: if there is, an attempt will - * instead be made to add the forwarder methods to the companion class. - */ - def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) { - assert(modsym.companionClass == NoSymbol, modsym) - innerClassBuffer.clear() - this.cunit = cunit - val moduleName = javaName(modsym) // + "$" - val mirrorName = moduleName.substring(0, moduleName.length() - 1) - - val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) - val mirrorClass = createJClass(flags, - mirrorName, - null /* no java-generic-signature */, - JAVA_LANG_OBJECT.getInternalName, - EMPTY_STRING_ARRAY) - - log("Dumping mirror class for '%s'".format(mirrorName)) - - // typestate: entering mode with valid call sequences: - // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* - - if(emitSource) { - mirrorClass.visitSource("" + cunit.source, - null /* SourceDebugExtension */) - } - - val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol) - mirrorClass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) - emitAnnotations(mirrorClass, modsym.annotations ++ ssa) - - // typestate: entering mode with valid call sequences: - // ( visitInnerClass | visitField | visitMethod )* visitEnd - - addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) - - addInnerClasses(modsym, mirrorClass) - mirrorClass.visitEnd() - writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) - } - - - } // end of class JMirrorBuilder - - - /** builder of bean info classes */ - class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { - - /** - * Generate a bean info class that describes the given class. - * - * @author Ross Judson (ross.judson@soletta.com) - */ - def genBeanInfoClass(clasz: IClass) { - - // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip") - // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName") - // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription") - // val description = c.symbol getAnnotation BeanDescriptionAttr - // informProgress(description.toString) - innerClassBuffer.clear() - - val flags = mkFlags( - javaFlags(clasz.symbol), - if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - val beanInfoName = (javaName(clasz.symbol) + "BeanInfo") - val beanInfoClass = createJClass( - flags, - beanInfoName, - null, // no java-generic-signature - "scala/beans/ScalaBeanInfo", - EMPTY_STRING_ARRAY - ) - - // beanInfoClass typestate: entering mode with valid call sequences: - // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* - - beanInfoClass.visitSource( - clasz.cunit.source.toString, - null /* SourceDebugExtension */ - ) - - var fieldList = List[String]() - - for (f <- clasz.fields if f.symbol.hasGetter; - g = f.symbol.getter(clasz.symbol); - s = f.symbol.setter(clasz.symbol); - if g.isPublic && !(f.symbol.name startsWith "$") - ) { - // inserting $outer breaks the bean - fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList - } - - val methodList: List[String] = - for (m <- clasz.methods - if !m.symbol.isConstructor && - m.symbol.isPublic && - !(m.symbol.name startsWith "$") && - !m.symbol.isGetter && - !m.symbol.isSetter) - yield javaName(m.symbol) - - // beanInfoClass typestate: entering mode with valid call sequences: - // ( visitInnerClass | visitField | visitMethod )* visitEnd - - val constructor = beanInfoClass.visitMethod( - asm.Opcodes.ACC_PUBLIC, - INSTANCE_CONSTRUCTOR_NAME, - mdesc_arglessvoid, - null, // no java-generic-signature - EMPTY_STRING_ARRAY // no throwable exceptions - ) - - // constructor typestate: entering mode with valid call sequences: - // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* - - val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING) - val conJType: asm.Type = - asm.Type.getMethodType( - asm.Type.VOID_TYPE, - Array(javaType(ClassClass), stringArrayJType, stringArrayJType): _* - ) - - def push(lst: List[String]) { - var fi = 0 - for (f <- lst) { - constructor.visitInsn(asm.Opcodes.DUP) - constructor.visitLdcInsn(new java.lang.Integer(fi)) - if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } - else { constructor.visitLdcInsn(f) } - constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE)) - fi += 1 - } - } - - // constructor typestate: entering mode with valid call sequences: - // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd - - constructor.visitCode() - - constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) - // push the class - constructor.visitLdcInsn(javaType(clasz.symbol)) - - // push the string array of field information - constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) - push(fieldList) - - // push the string array of method information - constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) - push(methodList) - - // invoke the superclass constructor, which will do the - // necessary java reflection and create Method objects. - constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor) - constructor.visitInsn(asm.Opcodes.RETURN) - - constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments - constructor.visitEnd() - - addInnerClasses(clasz.symbol, beanInfoClass) - beanInfoClass.visitEnd() - - writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol) - } - - } // end of class JBeanInfoBuilder - - /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for. - * In particualr, IMethod.normalize() doesn't collapseJumpChains(). - * - * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them). - */ - object newNormal { - - def startsWithJump(b: BasicBlock): Boolean = { assert(b.nonEmpty, "empty block"); b.firstInstruction.isInstanceOf[JUMP] } - - /** Prune from an exception handler those covered blocks which are jump-only. */ - private def coverWhatCountsOnly(m: IMethod): Boolean = { - assert(m.hasCode, "code-less method") - - var wasReduced = false - for(h <- m.exh) { - val shouldntCover = (h.covered filter startsWithJump) - if(shouldntCover.nonEmpty) { - wasReduced = true - h.covered --= shouldntCover // not removing any block on purpose. - } - } - - wasReduced - } - - /** An exception handler is pruned provided any of the following holds: - * (1) it covers nothing (for example, this may result after removing unreachable blocks) - * (2) each block it covers is of the form: JUMP(_) - * Return true iff one or more ExceptionHandlers were removed. - * - * A caveat: removing an exception handler, for whatever reason, means that its handler code (even if unreachable) - * won't be able to cause a class-loading-exception. As a result, behavior can be different. - */ - private def elimNonCoveringExh(m: IMethod): Boolean = { - assert(m.hasCode, "code-less method") - - def isRedundant(eh: ExceptionHandler): Boolean = { - (eh.cls != NoSymbol) && ( // TODO `eh.isFinallyBlock` more readable than `eh.cls != NoSymbol` - eh.covered.isEmpty - || (eh.covered forall startsWithJump) - ) - } - - var wasReduced = false - val toPrune = (m.exh.toSet filter isRedundant) - if(toPrune.nonEmpty) { - wasReduced = true - for(h <- toPrune; r <- h.blocks) { m.code.removeBlock(r) } // TODO m.code.removeExh(h) - m.exh = (m.exh filterNot toPrune) - } - - wasReduced - } - - private def isJumpOnly(b: BasicBlock): Option[BasicBlock] = { - b.toList match { - case JUMP(whereto) :: rest => - assert(rest.isEmpty, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)") - Some(whereto) - case _ => None - } - } - - private def directSuccStar(b: BasicBlock): List[BasicBlock] = { directSuccStar(List(b)) } - - /** Transitive closure of successors potentially reachable due to normal (non-exceptional) control flow. - Those BBs in the argument are also included in the result */ - private def directSuccStar(starters: Traversable[BasicBlock]): List[BasicBlock] = { - val result = new mutable.ListBuffer[BasicBlock] - var toVisit: List[BasicBlock] = starters.toList.distinct - while(toVisit.nonEmpty) { - val h = toVisit.head - toVisit = toVisit.tail - result += h - for(p <- h.directSuccessors; if !result.contains(p) && !toVisit.contains(p)) { toVisit = p :: toVisit } - } - result.toList - } - - /** Returns: - * for single-block self-loops, the pair (start, Nil) - * for other cycles, the pair (backedge-target, basic-blocks-in-the-cycle-except-backedge-target) - * otherwise a pair consisting of: - * (a) the endpoint of a (single or multi-hop) chain of JUMPs - * (such endpoint does not start with a JUMP and therefore is not part of the chain); and - * (b) the chain (ie blocks to be removed when collapsing the chain of jumps). - * Precondition: the BasicBlock given as argument starts with an unconditional JUMP. - */ - private def finalDestination(start: BasicBlock): (BasicBlock, List[BasicBlock]) = { - assert(startsWithJump(start), "not the start of a (single or multi-hop) chain of JUMPs.") - var hops: List[BasicBlock] = Nil - var prev = start - var done = false - do { - done = isJumpOnly(prev) match { - case Some(dest) => - if (dest == start) { return (start, hops) } // leave infinite-loops in place - hops ::= prev - if (hops.contains(dest)) { - // leave infinite-loops in place - return (dest, hops filterNot (dest eq)) - } - prev = dest; - false - case None => true - } - } while(!done) - - (prev, hops) - } - - /** - * Collapse a chain of "jump-only" blocks such as: - * - * JUMP b1; - * b1: JUMP b2; - * b2: JUMP ... etc. - * - * by re-wiring predecessors to target directly the "final destination". - * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed. - - * Returns true if any replacement was made, false otherwise. - * - * In more detail: - * Starting at each of the entry points (m.startBlock, the start block of each exception handler) - * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D. - * The blocks thus skipped are also removed from IMethod.blocks. - * - * Rationale for this normalization: - * test/files/run/private-inline.scala after -optimize is chock full of - * BasicBlocks containing just JUMP(whereTo), where no exception handler straddles them. - * They should be collapsed by IMethod.normalize() but aren't. - * That was fine in FJBG times when by the time the exception table was emitted, - * it already contained "anchored" labels (ie instruction offsets were known) - * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question) - * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range" - * Now that visitTryCatchBlock() must be called before Labels are resolved, - * this method gets rid of the BasicBlocks described above (to recap, consisting of just a JUMP). - */ - private def collapseJumpOnlyBlocks(m: IMethod): Boolean = { - assert(m.hasCode, "code-less method") - - /* "start" is relative in a cycle, but we call this helper with the "first" entry-point we found. */ - def realTarget(jumpStart: BasicBlock): Map[BasicBlock, BasicBlock] = { - assert(startsWithJump(jumpStart), "not part of a jump-chain") - val Pair(dest, redundants) = finalDestination(jumpStart) - (for(skipOver <- redundants) yield Pair(skipOver, dest)).toMap - } - - def rephraseGotos(detour: Map[BasicBlock, BasicBlock]) { - for(Pair(oldTarget, newTarget) <- detour.iterator) { - if(m.startBlock == oldTarget) { - m.code.startBlock = newTarget - } - for(eh <- m.exh; if eh.startBlock == oldTarget) { - eh.setStartBlock(newTarget) - } - for(b <- m.blocks; if !detour.isDefinedAt(b)) { - val idxLast = (b.size - 1) - b.lastInstruction match { - case JUMP(whereto) => - if (whereto == oldTarget) { - b.replaceInstruction(idxLast, JUMP(newTarget)) - } - case CJUMP(succ, fail, cond, kind) => - if ((succ == oldTarget) || (fail == oldTarget)) { - b.replaceInstruction(idxLast, CJUMP(detour.getOrElse(succ, succ), - detour.getOrElse(fail, fail), - cond, kind)) - } - case CZJUMP(succ, fail, cond, kind) => - if ((succ == oldTarget) || (fail == oldTarget)) { - b.replaceInstruction(idxLast, CZJUMP(detour.getOrElse(succ, succ), - detour.getOrElse(fail, fail), - cond, kind)) - } - case SWITCH(tags, labels) => - if(labels exists (detour.isDefinedAt(_))) { - val newLabels = (labels map { lab => detour.getOrElse(lab, lab) }) - b.replaceInstruction(idxLast, SWITCH(tags, newLabels)) - } - case _ => () - } - } - } - } - - /* remove from all containers that may contain a reference to */ - def elide(redu: BasicBlock) { - assert(m.startBlock != redu, "startBlock should have been re-wired by now") - m.code.removeBlock(redu); - } - - var wasReduced = false - val entryPoints: List[BasicBlock] = m.startBlock :: (m.exh map (_.startBlock)); - - var elided = mutable.Set.empty[BasicBlock] // debug - var newTargets = mutable.Set.empty[BasicBlock] // debug - - for (ep <- entryPoints) { - var reachable = directSuccStar(ep) // this list may contain blocks belonging to jump-chains that we'll skip over - while(reachable.nonEmpty) { - val h = reachable.head - reachable = reachable.tail - if(startsWithJump(h)) { - val detour = realTarget(h) - if(detour.nonEmpty) { - wasReduced = true - reachable = (reachable filterNot (detour.keySet.contains(_))) - rephraseGotos(detour) - detour.keySet foreach elide - elided ++= detour.keySet - newTargets ++= detour.values - } - } - } - } - assert(newTargets.intersect(elided).isEmpty, "contradiction: we just elided the final destionation of a jump-chain") - - wasReduced - } - - def normalize(m: IMethod) { - if(!m.hasCode) { return } - collapseJumpOnlyBlocks(m) - var wasReduced = false; - do { - wasReduced = false - // Prune from an exception handler those covered blocks which are jump-only. - wasReduced |= coverWhatCountsOnly(m); icodes.checkValid(m) // TODO should be unnecessary now that collapseJumpOnlyBlocks(m) is in place - // Prune exception handlers covering nothing. - wasReduced |= elimNonCoveringExh(m); icodes.checkValid(m) - - // TODO see note in genExceptionHandlers about an ExceptionHandler.covered containing dead blocks (newNormal should remove them, but, where do those blocks come from?) - } while (wasReduced) - - // TODO this would be a good time to remove synthetic local vars seeing no use, don't forget to call computeLocalVarsIndex() afterwards. - } - - } - -} +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm + +import java.nio.ByteBuffer +import scala.collection.{ mutable, immutable } +import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } +import scala.tools.nsc.symtab._ +import scala.tools.nsc.io.AbstractFile + +import scala.tools.asm +import asm.Label + +/** + * @author Iulian Dragos (version 1.0, FJBG-based implementation) + * @author Miguel Garcia (version 2.0, ASM-based implementation) + * + * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf + */ +abstract class GenASM extends SubComponent with BytecodeWriters { + import global._ + import icodes._ + import icodes.opcodes._ + import definitions._ + + val phaseName = "jvm" + + /** Create a new phase */ + override def newPhase(p: Phase): Phase = new AsmPhase(p) + + private def outputDirectory(sym: Symbol): AbstractFile = + settings.outputDirs outputDirFor beforeFlatten(sym.sourceFile) + + private def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { + var dir = base + val pathParts = clsName.split("[./]").toList + for (part <- pathParts.init) { + dir = dir.subdirectoryNamed(part) + } + dir.fileNamed(pathParts.last + suffix) + } + private def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile = + getFile(outputDirectory(sym), clsName, suffix) + + /** JVM code generation phase + */ + class AsmPhase(prev: Phase) extends ICodePhase(prev) { + def name = phaseName + override def erasedTypes = true + def apply(cls: IClass) = sys.error("no implementation") + + val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") + + def isJavaEntryPoint(icls: IClass) = { + val sym = icls.symbol + def fail(msg: String, pos: Position = sym.pos) = { + icls.cunit.warning(sym.pos, + sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" + + " Reason: " + msg + // TODO: make this next claim true, if possible + // by generating valid main methods as static in module classes + // not sure what the jvm allows here + // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead." + ) + false + } + def failNoForwarder(msg: String) = { + fail(msg + ", which means no static forwarder can be generated.\n") + } + val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil + val hasApproximate = possibles exists { m => + m.info match { + case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass + case _ => false + } + } + // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. + hasApproximate && { + // Before erasure so we can identify generic mains. + beforeErasure { + val companion = sym.linkedClassOfClass + val companionMain = companion.tpe.member(nme.main) + + if (hasJavaMainMethod(companion)) + failNoForwarder("companion contains its own main method") + else if (companion.tpe.member(nme.main) != NoSymbol) + // this is only because forwarders aren't smart enough yet + failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") + else if (companion.isTrait) + failNoForwarder("companion is a trait") + // Now either succeeed, or issue some additional warnings for things which look like + // attempts to be java main methods. + else possibles exists { m => + m.info match { + case PolyType(_, _) => + fail("main methods cannot be generic.") + case MethodType(params, res) => + if (res.typeSymbol :: params exists (_.isAbstractType)) + fail("main methods cannot refer to type parameters or abstract types.", m.pos) + else + isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) + case tp => + fail("don't know what this is: " + tp, m.pos) + } + } + } + } + } + + private def initBytecodeWriter(entryPoints: List[IClass]): BytecodeWriter = { + settings.outputDirs.getSingleOutput match { + case Some(f) if f hasExtension "jar" => + // If no main class was specified, see if there's only one + // entry point among the classes going into the jar. + if (settings.mainClass.isDefault) { + entryPoints map (_.symbol fullName '.') match { + case Nil => + log("No Main-Class designated or discovered.") + case name :: Nil => + log("Unique entry point: setting Main-Class to " + name) + settings.mainClass.value = name + case names => + log("No Main-Class due to multiple entry points:\n " + names.mkString("\n ")) + } + } + else log("Main-Class was specified: " + settings.mainClass.value) + + new DirectToJarfileWriter(f.file) + + case _ => + if (settings.Ygenjavap.isDefault) { + if(settings.Ydumpclasses.isDefault) + new ClassBytecodeWriter { } + else + new ClassBytecodeWriter with DumpBytecodeWriter { } + } + else new ClassBytecodeWriter with JavapBytecodeWriter { } + + // TODO A ScalapBytecodeWriter could take asm.util.Textifier as starting point. + // Three areas where javap ouput is less than ideal (e.g. when comparing versions of the same classfile) are: + // (a) unreadable pickle; + // (b) two constant pools, while having identical contents, are displayed differently due to physical layout. + // (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, their expansion makes more sense instead. + } + } + + override def run() { + + if (settings.debug.value) + inform("[running phase " + name + " on icode]") + + if (settings.Xdce.value) + for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) + icodes.classes -= sym + + // For predictably ordered error messages. + var sortedClasses = classes.values.toList sortBy ("" + _.symbol.fullName) + + debuglog("Created new bytecode generator for " + classes.size + " classes.") + val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) + val plainCodeGen = new JPlainBuilder(bytecodeWriter) + val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter) + val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter) + + while(!sortedClasses.isEmpty) { + val c = sortedClasses.head + + if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) { + if (c.symbol.companionClass == NoSymbol) { + mirrorCodeGen.genMirrorClass(c.symbol, c.cunit) + } else { + log("No mirror class for module with linked class: " + c.symbol.fullName) + } + } + + plainCodeGen.genClass(c) + + if (c.symbol hasAnnotation BeanInfoAttr) { + beanInfoCodeGen.genBeanInfoClass(c) + } + + sortedClasses = sortedClasses.tail + classes -= c.symbol // GC opportunity + } + + bytecodeWriter.close() + classes.clear() + reverseJavaName.clear() + + /* don't javaNameCache.clear() because that causes the following tests to fail: + * test/files/run/macro-repl-dontexpand.scala + * test/files/jvm/interpreter.scala + * TODO but why? what use could javaNameCache possibly see once GenJVM is over? + */ + + /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification: + * + * (1) call the asm.util.CheckAdapter.verify() overload: + * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) + * + * (2) passing a custom ClassLoader to verify inter-dependent classes. + * + * Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). + */ + + } // end of AsmPhase.run() + + } // end of class AsmPhase + + var pickledBytes = 0 // statistics + + // Don't put this in per run caches. Contains entries for classes as well as members. + val javaNameCache = new mutable.WeakHashMap[Symbol, Name]() ++= List( + NothingClass -> binarynme.RuntimeNothing, + RuntimeNothingClass -> binarynme.RuntimeNothing, + NullClass -> binarynme.RuntimeNull, + RuntimeNullClass -> binarynme.RuntimeNull + ) + + // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. + val reverseJavaName = mutable.Map.empty[String, Symbol] ++= List( + binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type. + binarynme.RuntimeNull.toString() -> RuntimeNullClass + ) + + private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + + @inline final private def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) + + @inline final private def isRemote(s: Symbol) = (s hasAnnotation RemoteAttr) + + /** + * Return the Java modifiers for the given symbol. + * Java modifiers for classes: + * - public, abstract, final, strictfp (not used) + * for interfaces: + * - the same as for classes, without 'final' + * for fields: + * - public, private (*) + * - static, final + * for methods: + * - the same as for fields, plus: + * - abstract, synchronized (not used), strictfp (not used), native (not used) + * + * (*) protected cannot be used, since inner classes 'see' protected members, + * and they would fail verification after lifted. + */ + def javaFlags(sym: Symbol): Int = { + // constructors of module classes should be private + // PP: why are they only being marked private at this stage and not earlier? + val privateFlag = + sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) + + // Final: the only fields which can receive ACC_FINAL are eager vals. + // Neither vars nor lazy vals can, because: + // + // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 + // "Another problem is that the specification allows aggressive + // optimization of final fields. Within a thread, it is permissible to + // reorder reads of a final field with those modifications of a final + // field that do not take place in the constructor." + // + // A var or lazy val which is marked final still has meaning to the + // scala compiler. The word final is heavily overloaded unfortunately; + // for us it means "not overridable". At present you can't override + // vars regardless; this may change. + // + // The logic does not check .isFinal (which checks flags for the FINAL flag, + // and includes symbols marked lateFINAL) instead inspecting rawflags so + // we can exclude lateFINAL. Such symbols are eligible for inlining, but to + // avoid breaking proxy software which depends on subclassing, we do not + // emit ACC_FINAL. + // Nested objects won't receive ACC_FINAL in order to allow for their overriding. + + val finalFlag = ( + (sym.hasFlag(Flags.FINAL) || isTopLevelModule(sym)) + && !sym.enclClass.isInterface + && !sym.isClassConstructor + && !sym.isMutable // lazy vals and vars both + ) + + // Primitives are "abstract final" to prohibit instantiation + // without having to provide any implementations, but that is an + // illegal combination of modifiers at the bytecode level so + // suppress final if abstract if present. + import asm.Opcodes._ + mkFlags( + if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, + if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (sym.isInterface) ACC_INTERFACE else 0, + if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, + if (sym.isStaticMember) ACC_STATIC else 0, + if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, + if (sym.isHidden) ACC_SYNTHETIC else 0, + if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.isVarargsMethod) ACC_VARARGS else 0, + if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 + ) + } + + def javaFieldFlags(sym: Symbol) = { + javaFlags(sym) | mkFlags( + if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, + if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, + if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL + ) + } + + def isTopLevelModule(sym: Symbol): Boolean = + afterPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } + + def isStaticModule(sym: Symbol): Boolean = { + sym.isModuleClass && !sym.isImplClass && !sym.isLifted + } + + // ----------------------------------------------------------------------------------------- + // finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM) + // Background: + // http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + // http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + // https://issues.scala-lang.org/browse/SI-3872 + // ----------------------------------------------------------------------------------------- + + /** + * Given an internal name (eg "java/lang/Integer") returns the class symbol for it. + * + * Better not to need this method (an example where control flow arrives here is welcome). + * This method is invoked only upon both (1) and (2) below happening: + * (1) providing an asm.ClassWriter with an internal name by other means than javaName() + * (2) forgetting to track the corresponding class-symbol in reverseJavaName. + * + * (The first item is already unlikely because we rely on javaName() + * to do the bookkeeping for entries that should go in innerClassBuffer.) + * + * (We could do completely without this method at the expense of computing stack-map-frames ourselves and + * invoking visitFrame(), but that would require another pass over all instructions.) + * + * Right now I can't think of any invocation of visitSomething() on MethodVisitor + * where we hand an internal name not backed by a reverseJavaName. + * However, I'm leaving this note just in case any such oversight is discovered. + */ + def inameToSymbol(iname: String): Symbol = { + val name = global.newTypeName(iname) + val res0 = + if (nme.isModuleName(name)) rootMirror.getModule(nme.stripModuleSuffix(name)) + else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested). + assert(res0 != NoSymbol) + val res = jsymbol(res0) + res + } + + def jsymbol(sym: Symbol): Symbol = { + if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass + else if(sym.isModule) sym.moduleClass + else sym // we track only module-classes and plain-classes + } + + private def superClasses(s: Symbol): List[Symbol] = { + assert(!s.isInterface) + s.superClass match { + case NoSymbol => List(s) + case sc => s :: superClasses(sc) + } + } + + private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = { + assert(!(as contains NoSymbol)) + assert(!(bs contains NoSymbol)) + var chainA = as + var chainB = bs + var fcs: Symbol = NoSymbol + do { + if (chainB contains chainA.head) fcs = chainA.head + else if (chainA contains chainB.head) fcs = chainB.head + else { + chainA = chainA.tail + chainB = chainB.tail + } + } while(fcs == NoSymbol) + fcs + } + + @inline final private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = { + + assert(a.isClass) + assert(b.isClass) + + val res = Pair(a.isInterface, b.isInterface) match { + case (true, true) => + global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents + case (true, false) => + if(b isSubClass a) a else ObjectClass + case (false, true) => + if(a isSubClass b) b else ObjectClass + case _ => + firstCommonSuffix(superClasses(a), superClasses(b)) + } + assert(res != NoSymbol) + res + } + + /* The internal name of the least common ancestor of the types given by inameA and inameB. + It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow */ + def getCommonSuperClass(inameA: String, inameB: String): String = { + val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA)) + val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB)) + + // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString() + // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType + val lcaSym = jvmWiseLUB(a, b) + val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer. + val oldsym = reverseJavaName.put(lcaName, lcaSym) + assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption") + assert(lcaName != "scala/Any") + + lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching. + } + + class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { + override def getCommonSuperClass(iname1: String, iname2: String): String = { + GenASM.this.getCommonSuperClass(iname1, iname2) + } + } + + // ----------------------------------------------------------------------------------------- + // constants + // ----------------------------------------------------------------------------------------- + + private val classfileVersion: Int = settings.target.value match { + case "jvm-1.5" => asm.Opcodes.V1_5 + case "jvm-1.5-asm" => asm.Opcodes.V1_5 + case "jvm-1.6" => asm.Opcodes.V1_6 + case "jvm-1.7" => asm.Opcodes.V1_7 + } + + private val majorVersion: Int = (classfileVersion & 0xFF) + private val emitStackMapFrame = (majorVersion >= 50) + + private val extraProc: Int = mkFlags( + asm.ClassWriter.COMPUTE_MAXS, + if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 + ) + + val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object") + val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String") + + /** basic functionality for class file building */ + abstract class JBuilder(bytecodeWriter: BytecodeWriter) { + + val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type] + val EMPTY_STRING_ARRAY = Array.empty[String] + + val mdesc_arglessvoid = "()V" + + val CLASS_CONSTRUCTOR_NAME = "" + val INSTANCE_CONSTRUCTOR_NAME = "" + + val INNER_CLASSES_FLAGS = + (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT) + + // ----------------------------------------------------------------------------------------- + // factory methods + // ----------------------------------------------------------------------------------------- + + /** + * Returns a new ClassWriter for the class given by arguments. + * + * @param access the class's access flags. This parameter also indicates if the class is deprecated. + * + * @param name the internal name of the class. + * + * @param signature the signature of this class. May be null if + * the class is not a generic one, and does not extend or implement + * generic classes or interfaces. + * + * @param superName the internal of name of the super class. For interfaces, + * the super class is {@link Object}. May be null, but + * only for the {@link Object} class. + * + * @param interfaces the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). May be + * null. + */ + def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = { + val cw = new CClassWriter(extraProc) + cw.visit(classfileVersion, + access, name, signature, + superName, interfaces) + + cw + } + + def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { + val dest = new Array[Byte](len); + System.arraycopy(b, offset, dest, 0, len); + new asm.CustomAttr(name, dest) + } + + // ----------------------------------------------------------------------------------------- + // utitilies useful when emitting plain, mirror, and beaninfo classes. + // ----------------------------------------------------------------------------------------- + + def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { + try { + val arr = jclass.toByteArray() + bytecodeWriter.writeClass(label, jclassName, arr, sym) + } catch { + case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") => + // TODO check where ASM throws the equivalent of CodeSizeTooBigException + log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).") + } + } + + /** Specialized array conversion to prevent calling + * java.lang.reflect.Array.newInstance via TraversableOnce.toArray + */ + def mkArray(xs: Traversable[asm.Type]): Array[asm.Type] = { val a = new Array[asm.Type](xs.size); xs.copyToArray(a); a } + def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a } + + // ----------------------------------------------------------------------------------------- + // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances). + // These getters track behind the scenes the inner classes referred to in the class being emitted, + // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()` + // (which also adds as member classes those inner classes that have been declared, + // thus also covering the case of inner classes declared but otherwise not referred). + // ----------------------------------------------------------------------------------------- + + val innerClassBuffer = mutable.LinkedHashSet[Symbol]() + + /** For given symbol return a symbol corresponding to a class that should be declared as inner class. + * + * For example: + * class A { + * class B + * object C + * } + * + * then method will return: + * NoSymbol for A, + * the same symbol for A.B (corresponding to A$B class), and + * A$C$ symbol for A.C. + */ + def innerClassSymbolFor(s: Symbol): Symbol = + if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol + + /** Return the a name of this symbol that can be used on the Java platform. It removes spaces from names. + * + * Special handling: + * scala.Nothing erases to scala.runtime.Nothing$ + * scala.Null erases to scala.runtime.Null$ + * + * This is needed because they are not real classes, and they mean + * 'abrupt termination upon evaluation of that expression' or null respectively. + * This handling is done already in GenICode, but here we need to remove + * references from method signatures to these types, because such classes + * cannot exist in the classpath: the type checker will be very confused. + */ + def javaName(sym: Symbol): String = { + + /** + * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer + * + * Note: This method is called recursively thus making sure that we add complete chain + * of inner class all until root class. + */ + def collectInnerClass(s: Symbol): Unit = { + // TODO: some beforeFlatten { ... } which accounts for + // being nested in parameterized classes (if we're going to selectively flatten.) + val x = innerClassSymbolFor(s) + if(x ne NoSymbol) { + assert(x.isClass, "not an inner-class symbol") + val isInner = !x.rawowner.isPackageClass + if (isInner) { + innerClassBuffer += x + collectInnerClass(x.rawowner) + } + } + } + + collectInnerClass(sym) + + var hasInternalName = (sym.isClass || (sym.isModule && !sym.isMethod)) + val cachedJN = javaNameCache.getOrElseUpdate(sym, { + if (hasInternalName) { sym.javaBinaryName } + else { sym.javaSimpleName } + }) + + if(emitStackMapFrame && hasInternalName) { + val internalName = cachedJN.toString() + val trackedSym = jsymbol(sym) + reverseJavaName.get(internalName) match { + case None => + reverseJavaName.put(internalName, trackedSym) + case Some(oldsym) => + assert((oldsym == trackedSym) || (oldsym == RuntimeNothingClass) || (oldsym == RuntimeNullClass), // In contrast, neither NothingClass nor NullClass show up bytecode-level. + "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name.") + } + } + + cachedJN.toString + } + + def descriptor(t: Type): String = { javaType(t).getDescriptor } + def descriptor(k: TypeKind): String = { javaType(k).getDescriptor } + def descriptor(s: Symbol): String = { javaType(s).getDescriptor } + + def javaType(tk: TypeKind): asm.Type = { + if(tk.isValueType) { + if(tk.isIntSizedType) { + (tk: @unchecked) match { + case BOOL => asm.Type.BOOLEAN_TYPE + case BYTE => asm.Type.BYTE_TYPE + case SHORT => asm.Type.SHORT_TYPE + case CHAR => asm.Type.CHAR_TYPE + case INT => asm.Type.INT_TYPE + } + } else { + (tk: @unchecked) match { + case UNIT => asm.Type.VOID_TYPE + case LONG => asm.Type.LONG_TYPE + case FLOAT => asm.Type.FLOAT_TYPE + case DOUBLE => asm.Type.DOUBLE_TYPE + } + } + } else { + assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway) + (tk: @unchecked) match { + case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) + case ARRAY(elem) => javaArrayType(javaType(elem)) + } + } + } + + def javaType(t: Type): asm.Type = javaType(toTypeKind(t)) + + def javaType(s: Symbol): asm.Type = { + if (s.isMethod) { + val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType); + asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _* ) + } else { javaType(s.tpe) } + } + + def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) } + + def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } + + def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) { + /** The outer name for this inner class. Note that it returns null + * when the inner class should not get an index in the constant pool. + * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. + */ + def outerName(innerSym: Symbol): String = { + if (innerSym.originalEnclosingMethod != NoSymbol) + null + else { + val outerName = javaName(innerSym.rawowner) + if (isTopLevelModule(innerSym.rawowner)) "" + nme.stripModuleSuffix(newTermName(outerName)) + else outerName + } + } + + def innerName(innerSym: Symbol): String = + if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) + null + else + innerSym.rawname + innerSym.moduleSuffix + + // add inner classes which might not have been referenced yet + afterErasure { + for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass) + innerClassBuffer += m + } + + val allInners: List[Symbol] = innerClassBuffer.toList + if (allInners.nonEmpty) { + debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.") + + // entries ready to be serialized into the classfile, used to detect duplicates. + val entries = mutable.Map.empty[String, String] + + // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler + for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ?? + val flags = mkFlags( + if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, + javaFlags(innerSym), + if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag + ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) + val jname = javaName(innerSym) // never null + val oname = outerName(innerSym) // null when method-enclosed + val iname = innerName(innerSym) // null for anonymous inner class + + // Mimicking javap inner class output + debuglog( + if (oname == null || iname == null) "//class " + jname + else "//%s=class %s of class %s".format(iname, jname, oname) + ) + + assert(jname != null, "javaName is broken.") // documentation + val doAdd = entries.get(jname) match { + // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute) + case Some(prevOName) => + // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State, + // i.e. for them it must be the case that oname == java/lang/Thread + assert(prevOName == oname, "duplicate") + false + case None => true + } + + if(doAdd) { + entries += (jname -> oname) + jclass.visitInnerClass(jname, oname, iname, flags) + } + + /* + * TODO assert (JVMS 4.7.6 The InnerClasses attribute) + * If a class file has a version number that is greater than or equal to 51.0, and + * has an InnerClasses attribute in its attributes table, then for all entries in the + * classes array of the InnerClasses attribute, the value of the + * outer_class_info_index item must be zero if the value of the + * inner_name_index item is zero. + */ + + } + } + } + + } // end of class JBuilder + + + /** functionality for building plain and mirror classes */ + abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + + def debugLevel = settings.debuginfo.indexOfChoice + + val emitSource = debugLevel >= 1 + val emitLines = debugLevel >= 2 + val emitVars = debugLevel >= 3 + + // ----------------------------------------------------------------------------------------- + // more constants + // ----------------------------------------------------------------------------------------- + + val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC + val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL + + val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString + + // ----------------------------------------------------------------------------------------- + // Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only + // i.e., the pickle is contained in a custom annotation, see: + // (1) `addAnnotations()`, + // (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10 + // (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5 + // That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9) + // other than both ending up encoded as attributes (JVMS 4.7) + // (with the caveat that the "ScalaSig" attribute is associated to some classes, + // while the "Signature" attribute can be associated to classes, methods, and fields.) + // ----------------------------------------------------------------------------------------- + + val versionPickle = { + val vp = new PickleBuffer(new Array[Byte](16), -1, 0) + assert(vp.writeIndex == 0, vp) + vp writeNat PickleFormat.MajorVersion + vp writeNat PickleFormat.MinorVersion + vp writeNat 0 + vp + } + + def pickleMarkerLocal = { + createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex) + } + + def pickleMarkerForeign = { + createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0) + } + + /** Returns a ScalaSignature annotation if it must be added to this class, none otherwise. + * This annotation must be added to the class' annotations list when generating them. + * + * Depending on whether the returned option is defined, it adds to `jclass` one of: + * (a) the ScalaSig marker attribute + * (indicating that a scala-signature-annotation aka pickle is present in this class); or + * (b) the Scala marker attribute + * (indicating that a scala-signature-annotation aka pickle is to be found in another file). + * + * + * @param jclassName The class file that is being readied. + * @param sym The symbol for which the signature has been entered in the symData map. + * This is different than the symbol + * that is being generated in the case of a mirror class. + * @return An option that is: + * - defined and contains an AnnotationInfo of the ScalaSignature type, + * instantiated with the pickle signature for sym. + * - empty if the jclass/sym pair must not contain a pickle. + * + */ + def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = { + currentRun.symData get sym match { + case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) => + val scalaAnnot = { + val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex)) + AnnotationInfo(sigBytes.sigAnnot, Nil, List((nme.bytes, sigBytes))) + } + pickledBytes += pickle.writeIndex + currentRun.symData -= sym + currentRun.symData -= sym.companionSymbol + Some(scalaAnnot) + case _ => + None + } + } + + /** + * Quoting from JVMS 4.7.5 The Exceptions Attribute + * "The Exceptions attribute indicates which checked exceptions a method may throw. + * There may be at most one Exceptions attribute in each method_info structure." + * + * The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod() + * This method returns such list of internal names. + * + */ + def getExceptions(excs: List[AnnotationInfo]): List[String] = { + for (AnnotationInfo(tp, List(exc), _) <- excs.distinct if tp.typeSymbol == ThrowsClass) + yield { + val Literal(const) = exc + javaName(const.typeValue.typeSymbol) + } + } + + /** Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be un-initialized + */ + private def shouldEmitAnnotation(annot: AnnotationInfo) = + annot.symbol.initialize.isJavaDefined && + annot.matches(ClassfileAnnotationClass) && + annot.args.isEmpty && + !annot.matches(DeprecatedAttr) + + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + private def needsGenericSignature(sym: Symbol) = !( + // PP: This condition used to include sym.hasExpandedName, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + sym.isHidden + || sym.isLiftedMethod + || sym.isBridge + || (sym.ownerChain exists (_.isImplClass)) + ) + + def getCurrentCUnit(): CompilationUnit + + /** @return + * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). + * - otherwise the signature in question + */ + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + + if (!needsGenericSignature(sym)) { return null } + + val memberTpe = beforeErasure(owner.thisType.memberInfo(sym)) + + val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) + if (jsOpt.isEmpty) { return null } + + val sig = jsOpt.get + log(sig) // This seems useful enough in the general case. + + def wrap(op: => Unit) = { + try { op; true } + catch { case _ => false } + } + + if (settings.Xverify.value) { + // Run the signature parser to catch bogus signatures. + val isValidSignature = wrap { + // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) + import scala.tools.asm.util.SignatureChecker + if (sym.isMethod) { SignatureChecker checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { SignatureChecker checkFieldSignature sig } + else { SignatureChecker checkClassSignature sig } + } + + if(!isValidSignature) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created invalid generic signature for %s in %s + |signature: %s + |if this is reproducible, please report bug at https://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) + return null + } + } + + if ((settings.check containsName phaseName)) { + val normalizedTpe = beforeErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created generic signature for %s in %s that does not conform to its erasure + |signature: %s + |original type: %s + |normalized type: %s + |erasure type: %s + |if this is reproducible, please report bug at http://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) + return null + } + } + + sig + } + + def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.size) + var idx = 0 + while(idx < bytes.size) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + + ca + } + + // TODO this method isn't exercised during bootstrapping. Open question: is it bug free? + private def arrEncode(sb: ScalaSigBytes): Array[String] = { + var strs: List[String] = Nil + val bSeven: Array[Byte] = sb.sevenBitsMayBeZero + // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) + var prevOffset = 0 + var offset = 0 + var encLength = 0 + while(offset < bSeven.size) { + val newEncLength = encLength.toLong + (if(bSeven(offset) == 0) 2 else 1) + if(newEncLength > 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += 1 + offset += 1 + } + } + if(prevOffset < offset) { + assert(offset == bSeven.length) + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + } + assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? + strs.reverse.toArray + } + + private def strEncode(sb: ScalaSigBytes): String = { + val ca = ubytesToCharArray(sb.sevenBitsMayBeZero) + new java.lang.String(ca) + // debug val bvA = new asm.ByteVector; bvA.putUTF8(s) + // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes) + // debug assert(enc(idx) == bvA.getByte(idx + 2)) + // debug assert(bvA.getLength == enc.size + 2) + } + + def emitArgument(av: asm.AnnotationVisitor, + name: String, + arg: ClassfileAnnotArg) { + arg match { + + case LiteralAnnotArg(const) => + if(const.isNonUnitAnyVal) { av.visit(name, const.value) } + else { + const.tag match { + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag + case ClazzTag => av.visit(name, javaType(const.typeValue)) + case EnumTag => + val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. + val evalue = const.symbolValue.name.toString // value the actual enumeration value. + av.visitEnum(name, edesc, evalue) + } + } + + case sb@ScalaSigBytes(bytes) => + // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files) + // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure. + val assocValue = (if(sb.fitsInOneString) strEncode(sb) else arrEncode(sb)) + av.visit(name, assocValue) + // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. + + case ArrayAnnotArg(args) => + val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) + for(arg <- args) { emitArgument(arrAnnotV, null, arg) } + arrAnnotV.visitEnd() + + case NestedAnnotArg(annInfo) => + val AnnotationInfo(typ, args, assocs) = annInfo + assert(args.isEmpty, args) + val desc = descriptor(typ) // the class descriptor of the nested annotation class + val nestedVisitor = av.visitAnnotation(name, desc) + emitAssocs(nestedVisitor, assocs) + } + } + + def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) { + for ((name, value) <- assocs) { + emitArgument(av, name.toString(), value) + } + av.visitEnd() + } + + def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = cw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = mw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = fw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) { + val annotationss = pannotss map (_ filter shouldEmitAnnotation) + if (annotationss forall (_.isEmpty)) return + for (Pair(annots, idx) <- annotationss.zipWithIndex; + annot <- annots) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true) + emitAssocs(pannVisitor, assocs) + } + } + + /** Adds a @remote annotation, actual use unknown. + * + * Invoked from genMethod() and addForwarder(). + */ + def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { + val needsAnnotation = ( + ( isRemoteClass || + isRemote(meth) && isJMethodPublic + ) && !(meth.throwsAnnotations contains RemoteExceptionClass) + ) + if (needsAnnotation) { + val c = Constant(RemoteExceptionClass.tpe) + val arg = Literal(c) setType c.tpe + meth.addAnnotation(ThrowsClass, arg) + } + } + + // ----------------------------------------------------------------------------------------- + // Static forwarders (related to mirror classes but also present in + // a plain class lacking companion module, for details see `isCandidateForForwarders`). + // ----------------------------------------------------------------------------------------- + + val ExcludedForwarderFlags = { + import Flags._ + // Should include DEFERRED but this breaks findMember. + ( CASE | SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags ) + } + + /** Add a forwarder for method m. Used only from addForwarders(). */ + private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { + val moduleName = javaName(module) + val methodInfo = module.thisType.memberInfo(m) + val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType + // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) + + /** Forwarders must not be marked final, + * as the JVM will not allow redefinition of a final static method, + * and we don't know what classes might be subclassing the companion class. See SI-4827. + */ + // TODO: evaluate the other flags we might be dropping on the floor here. + // TODO: ACC_SYNTHETIC ? + val flags = PublicStatic | ( + if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0 + ) + + // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } + val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 + addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) + val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) + val thrownExceptions: List[String] = getExceptions(throws) + + val jReturnType = javaType(methodInfo.resultType) + val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*) + val mirrorMethodName = javaName(m) + val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( + flags, + mirrorMethodName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ) + + // typestate: entering mode with valid call sequences: + // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* + + emitAnnotations(mirrorMethod, others) + emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations)) + + // typestate: entering mode with valid call sequences: + // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd + + mirrorMethod.visitCode() + + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) + + var index = 0 + for(jparamType <- paramJavaTypes) { + mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index) + assert(jparamType.getSort() != asm.Type.METHOD, jparamType) + index += jparamType.getSize() + } + + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(m).getDescriptor) + mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN)) + + mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + mirrorMethod.visitEnd() + + } + + /** Add forwarders for all methods defined in `module` that don't conflict + * with methods in the companion class of `module`. A conflict arises when + * a method with the same name is defined both in a class and its companion object: + * method signature is not taken into account. + */ + def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) { + assert(moduleClass.isModuleClass, moduleClass) + debuglog("Dumping mirror class for object: " + moduleClass) + + val linkedClass = moduleClass.companionClass + val linkedModule = linkedClass.companionSymbol + lazy val conflictingNames: Set[Name] = { + linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name } toSet + } + debuglog("Potentially conflicting names for forwarders: " + conflictingNames) + + for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) { + if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor) + debuglog("No forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass)) + else if (conflictingNames(m.name)) + log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name)) + else { + log("Adding static forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass)) + addForwarder(isRemoteClass, jclass, moduleClass, m) + } + } + } + + } // end of class JCommonBuilder + + + trait JAndroidBuilder { + self: JPlainBuilder => + + /** From the reference documentation of the Android SDK: + * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`. + * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`, + * which is an object implementing the `Parcelable.Creator` interface. + */ + private val androidFieldName = newTermName("CREATOR") + + private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") + private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") + + def isAndroidParcelableClass(sym: Symbol) = + (AndroidParcelableInterface != NoSymbol) && + (sym.parentSymbols contains AndroidParcelableInterface) + + /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */ + def addCreatorCode(block: BasicBlock) { + val fieldSymbol = ( + clasz.symbol.newValue(newTermName(androidFieldName), NoPosition, Flags.STATIC | Flags.FINAL) + setInfo AndroidCreatorClass.tpe + ) + val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName) + clasz addField new IField(fieldSymbol) + block emit CALL_METHOD(methodSymbol, Static(false)) + block emit STORE_FIELD(fieldSymbol, true) + } + + def legacyAddCreatorCode(clinit: asm.MethodVisitor) { + val creatorType: asm.Type = javaType(AndroidCreatorClass) + val tdesc_creator = creatorType.getDescriptor + + jclass.visitField( + PublicStaticFinal, + androidFieldName, + tdesc_creator, + null, // no java-generic-signature + null // no initial value + ).visitEnd() + + val moduleName = javaName(clasz.symbol)+"$" + + // GETSTATIC `moduleName`.MODULE$ : `moduleName`; + clinit.visitFieldInsn( + asm.Opcodes.GETSTATIC, + moduleName, + strMODULE_INSTANCE_FIELD, + asm.Type.getObjectType(moduleName).getDescriptor + ) + + // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator; + clinit.visitMethodInsn( + asm.Opcodes.INVOKEVIRTUAL, + moduleName, + androidFieldName, + asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*) + ) + + // PUTSTATIC `thisName`.CREATOR; + clinit.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisName, + androidFieldName, + tdesc_creator + ) + } + + } // end of trait JAndroidBuilder + + /** Map from type kinds to the Java reference types. + * It is used to push class literals onto the operand stack. + * @see Predef.classOf + * @see genConstant() + */ + private val classLiteral = immutable.Map[TypeKind, asm.Type]( + UNIT -> asm.Type.getObjectType("java/lang/Void"), + BOOL -> asm.Type.getObjectType("java/lang/Boolean"), + BYTE -> asm.Type.getObjectType("java/lang/Byte"), + SHORT -> asm.Type.getObjectType("java/lang/Short"), + CHAR -> asm.Type.getObjectType("java/lang/Character"), + INT -> asm.Type.getObjectType("java/lang/Integer"), + LONG -> asm.Type.getObjectType("java/lang/Long"), + FLOAT -> asm.Type.getObjectType("java/lang/Float"), + DOUBLE -> asm.Type.getObjectType("java/lang/Double") + ) + + def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT } + + case class MethodNameAndType(mname: String, mdesc: String) + + private val jBoxTo: Map[TypeKind, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , + BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , + CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , + SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , + INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , + LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , + FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , + DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) + ) + } + + private val jUnboxTo: Map[TypeKind, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , + BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , + CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , + SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , + INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , + LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , + FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , + DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") + ) + } + + case class BlockInteval(start: BasicBlock, end: BasicBlock) + + /** builder of plain classes */ + class JPlainBuilder(bytecodeWriter: BytecodeWriter) + extends JCommonBuilder(bytecodeWriter) + with JAndroidBuilder { + + val MIN_SWITCH_DENSITY = 0.7 + + val StringBuilderClassName = javaName(definitions.StringBuilderClass) + val BoxesRunTime = "scala/runtime/BoxesRunTime" + + val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName) + val mdesc_toString = "()Ljava/lang/String;" + val mdesc_arrayClone = "()Ljava/lang/Object;" + + val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J" + + def isParcelableClass = isAndroidParcelableClass(clasz.symbol) + + def serialVUID: Option[Long] = clasz.symbol getAnnotation SerialVersionUIDAttr collect { + case AnnotationInfo(_, Literal(const) :: _, _) => const.longValue + } + + private def getSuperInterfaces(c: IClass): Array[String] = { + + // Additional interface parents based on annotations and other cues + def newParentForAttr(attr: Symbol): Option[Symbol] = attr match { + case SerializableAttr => Some(SerializableClass) + case CloneableAttr => Some(CloneableClass) + case RemoteAttr => Some(RemoteInterfaceClass) + case _ => None + } + + /** Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents. + * This is important on Android because there is otherwise an interface explosion. + */ + def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { + var rest = lstIfaces + var leaves = List.empty[Symbol] + while(!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } + if(!nonLeaf) { + leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) + } + rest = rest.tail + } + + leaves + } + + val ps = c.symbol.info.parents + val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses; + val superInterfaces = superInterfaces0 ++ c.symbol.annotations.flatMap(ann => newParentForAttr(ann.symbol)) distinct + + if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY + else mkArray(minimizeInterfaces(superInterfaces) map javaName) + } + + var clasz: IClass = _ // this var must be assigned only by genClass() + var jclass: asm.ClassWriter = _ // the classfile being emitted + var thisName: String = _ // the internal name of jclass + + def thisDescr: String = { + assert(thisName != null, "thisDescr invoked too soon.") + asm.Type.getObjectType(thisName).getDescriptor + } + + def getCurrentCUnit(): CompilationUnit = { clasz.cunit } + + def genClass(c: IClass) { + clasz = c + innerClassBuffer.clear() + + thisName = javaName(c.symbol) // the internal name of the class being emitted + + val ps = c.symbol.info.parents + val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol); + + val ifaces = getSuperInterfaces(c) + + val thisSignature = getGenericSignature(c.symbol, c.symbol.owner) + val flags = mkFlags( + javaFlags(c.symbol), + if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + jclass = createJClass(flags, + thisName, thisSignature, + superClass, ifaces) + + // typestate: entering mode with valid call sequences: + // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* + + if(emitSource) { + jclass.visitSource(c.cunit.source.toString, + null /* SourceDebugExtension */) + } + + val enclM = getEnclosingMethodAttribute() + if(enclM != null) { + val EnclMethodEntry(className, methodName, methodType) = enclM + jclass.visitOuterClass(className, methodName, methodType.getDescriptor) + } + + // typestate: entering mode with valid call sequences: + // ( visitAnnotation | visitAttribute )* + + val ssa = getAnnotPickle(thisName, c.symbol) + jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) + emitAnnotations(jclass, c.symbol.annotations ++ ssa) + + // typestate: entering mode with valid call sequences: + // ( visitInnerClass | visitField | visitMethod )* visitEnd + + if (isStaticModule(c.symbol) || isParcelableClass) { + + if (isStaticModule(c.symbol)) { addModuleInstanceField() } + addStaticInit(c.lookupStaticCtor) + + } else { + + for (constructor <- c.lookupStaticCtor) { + addStaticInit(Some(constructor)) + } + val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders.value) + if (!skipStaticForwarders) { + val lmoc = c.symbol.companionModule + // add static forwarders if there are no name conflicts; see bugs #363 and #1735 + if (lmoc != NoSymbol) { + // it must be a top level class (name contains no $s) + val isCandidateForForwarders = { + afterPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } + } + if (isCandidateForForwarders) { + log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc)) + addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass) + } + } + } + + } + + // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` + serialVUID foreach { value => + val fieldName = "serialVersionUID" + jclass.visitField( + PublicStaticFinal, + fieldName, + tdesc_long, + null, // no java-generic-signature + value + ).visitEnd() + } + + clasz.fields foreach genField + clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) } + + addInnerClasses(clasz.symbol, jclass) + jclass.visitEnd() + writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) + + } + + /** + * @param owner internal name of the enclosing class of the class. + * + * @param name the name of the method that contains the class. + + * @param methodType the method that contains the class. + */ + case class EnclMethodEntry(owner: String, name: String, methodType: asm.Type) + + /** + * @return null if the current class is not internal to a method + * + * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute + * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. + * A class may have no more than one EnclosingMethod attribute. + * + */ + private def getEnclosingMethodAttribute(): EnclMethodEntry = { // JVMS 4.7.7 + var res: EnclMethodEntry = null + val clazz = clasz.symbol + val sym = clazz.originalEnclosingMethod + if (sym.isMethod) { + debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, sym.enclClass)) + res = EnclMethodEntry(javaName(sym.enclClass), javaName(sym), javaType(sym)) + } else if (clazz.isAnonymousClass) { + val enclClass = clazz.rawowner + assert(enclClass.isClass, enclClass) + val sym = enclClass.primaryConstructor + if (sym == NoSymbol) { + log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass, clazz)) + } else { + debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass)) + res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym)) + } + } + + res + } + + def genField(f: IField) { + debuglog("Adding field: " + f.symbol.fullName) + + val javagensig = getGenericSignature(f.symbol, clasz.symbol) + + val flags = mkFlags( + javaFieldFlags(f.symbol), + if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val jfield: asm.FieldVisitor = jclass.visitField( + flags, + javaName(f.symbol), + javaType(f.symbol.tpe).getDescriptor(), + javagensig, + null // no initial value + ) + + emitAnnotations(jfield, f.symbol.annotations) + jfield.visitEnd() + } + + var method: IMethod = _ + var jmethod: asm.MethodVisitor = _ + var jMethodName: String = _ + + @inline final def emit(opc: Int) { jmethod.visitInsn(opc) } + + def genMethod(m: IMethod, isJInterface: Boolean) { + + def isClosureApply(sym: Symbol): Boolean = { + (sym.name == nme.apply) && + sym.owner.isSynthetic && + sym.owner.tpe.parents.exists { t => + val TypeRef(_, sym, _) = t + FunctionClass contains sym + } + } + + if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return + + debuglog("Generating method " + m.symbol.fullName) + method = m + computeLocalVarsIndex(m) + + var resTpe: asm.Type = javaType(m.symbol.tpe.resultType) + if (m.symbol.isClassConstructor) + resTpe = asm.Type.VOID_TYPE + + val flags = mkFlags( + javaFlags(m.symbol), + if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0, + if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, + if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes + if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } + val jgensig = getGenericSignature(m.symbol, clasz.symbol) + addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol) + val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass) + val thrownExceptions: List[String] = getExceptions(excs) + + jMethodName = javaName(m.symbol) + val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*) + jmethod = jclass.visitMethod( + flags, + jMethodName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ) + + // TODO param names: (m.params map (p => javaName(p.sym))) + + // typestate: entering mode with valid call sequences: + // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* + + emitAnnotations(jmethod, others) + emitParamAnnotations(jmethod, m.params.map(_.sym.annotations)) + + // typestate: entering mode with valid call sequences: + // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd + // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code, + // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and + // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited. + + val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0) + val hasCodeAttribute = (!hasAbstractBitSet && !method.native) + if (hasCodeAttribute) { + + jmethod.visitCode() + + if (emitVars && isClosureApply(method.symbol)) { + // add a fake local for debugging purposes + val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL) + if (outerField != NoSymbol) { + log("Adding fake local to represent outer 'this' for closure " + clasz) + val _this = + new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS), + toTypeKind(outerField.tpe), + false) + m.locals = m.locals ::: List(_this) + computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes + jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) + jmethod.visitFieldInsn(asm.Opcodes.GETFIELD, + javaName(clasz.symbol), // field owner + javaName(outerField), // field name + descriptor(outerField) // field descriptor + ) + assert(_this.kind.isReferenceType, _this.kind) + jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this)) + } + } + + assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals ) + + val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0) + genCode(m, emitVars, hasStaticBitSet) + + jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + } + + jmethod.visitEnd() + + } + + def addModuleInstanceField() { + val fv = + jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + strMODULE_INSTANCE_FIELD, + thisDescr, + null, // no java-generic-signature + null // no initial value + ) + + // typestate: entering mode with valid call sequences: + // ( visitAnnotation | visitAttribute )* visitEnd. + + fv.visitEnd() + } + + + /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */ + def addStaticInit(mopt: Option[IMethod]) { + + val clinitMethod: asm.MethodVisitor = jclass.visitMethod( + PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + CLASS_CONSTRUCTOR_NAME, + mdesc_arglessvoid, + null, // no java-generic-signature + null // no throwable exceptions + ) + + mopt match { + + case Some(m) => + + val oldLastBlock = m.lastBlock + val lastBlock = m.newBlock() + oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock)) + + if (isStaticModule(clasz.symbol)) { + // call object's private ctor from static ctor + lastBlock emit NEW(REFERENCE(m.symbol.enclClass)) + lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(true)) + } + + if (isParcelableClass) { addCreatorCode(lastBlock) } + + lastBlock emit RETURN(UNIT) + lastBlock.close + + method = m + jmethod = clinitMethod + jMethodName = CLASS_CONSTRUCTOR_NAME + jmethod.visitCode() + genCode(m, false, true) + jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + jmethod.visitEnd() + + case None => + clinitMethod.visitCode() + legacyStaticInitializer(clinitMethod) + clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + clinitMethod.visitEnd() + + } + } + + /* used only from addStaticInit() */ + private def legacyStaticInitializer(clinit: asm.MethodVisitor) { + if (isStaticModule(clasz.symbol)) { + clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) + clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, + thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid) + } + + if (isParcelableClass) { legacyAddCreatorCode(clinit) } + + clinit.visitInsn(asm.Opcodes.RETURN) + } + + // ----------------------------------------------------------------------------------------- + // Emitting bytecode instructions. + // ----------------------------------------------------------------------------------------- + + private def genConstant(mv: asm.MethodVisitor, const: Constant) { + const.tag match { + + case BooleanTag => jcode.boolconst(const.booleanValue) + + case ByteTag => jcode.iconst(const.byteValue) + case ShortTag => jcode.iconst(const.shortValue) + case CharTag => jcode.iconst(const.charValue) + case IntTag => jcode.iconst(const.intValue) + + case LongTag => jcode.lconst(const.longValue) + case FloatTag => jcode.fconst(const.floatValue) + case DoubleTag => jcode.dconst(const.doubleValue) + + case UnitTag => () + + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag + + case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL) + + case ClazzTag => + val kind = toTypeKind(const.typeValue) + val toPush: asm.Type = + if (kind.isValueType) classLiteral(kind) + else javaType(kind); + mv.visitLdcInsn(toPush) + + case EnumTag => + val sym = const.symbolValue + mv.visitFieldInsn( + asm.Opcodes.GETSTATIC, + javaName(sym.owner), + javaName(sym), + javaType(sym.tpe.underlying).getDescriptor() + ) + + case _ => abort("Unknown constant value: " + const) + } + } + + /** Just a namespace for utilities that encapsulate MethodVisitor idioms. + * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role, + * but the methods here allow choosing when to transition from ICode to ASM types + * (including not at all, e.g. for performance). + */ + object jcode { + + import asm.Opcodes; + + def aconst(cst: AnyRef) { + if (cst == null) { jmethod.visitInsn(Opcodes.ACONST_NULL) } + else { jmethod.visitLdcInsn(cst) } + } + + @inline final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) } + + def iconst(cst: Int) { + if (cst >= -1 && cst <= 5) { + jmethod.visitInsn(Opcodes.ICONST_0 + cst) + } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.BIPUSH, cst) + } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.SIPUSH, cst) + } else { + jmethod.visitLdcInsn(new Integer(cst)) + } + } + + def lconst(cst: Long) { + if (cst == 0L || cst == 1L) { + jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Long(cst)) + } + } + + def fconst(cst: Float) { + val bits: Int = java.lang.Float.floatToIntBits(cst) + if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 + jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Float(cst)) + } + } + + def dconst(cst: Double) { + val bits: Long = java.lang.Double.doubleToLongBits(cst) + if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d + jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Double(cst)) + } + } + + def newarray(elem: TypeKind) { + if(elem.isRefOrArrayType) { + jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName) + } else { + val rand = { + if(elem.isIntSizedType) { + (elem: @unchecked) match { + case BOOL => Opcodes.T_BOOLEAN + case BYTE => Opcodes.T_BYTE + case SHORT => Opcodes.T_SHORT + case CHAR => Opcodes.T_CHAR + case INT => Opcodes.T_INT + } + } else { + (elem: @unchecked) match { + case LONG => Opcodes.T_LONG + case FLOAT => Opcodes.T_FLOAT + case DOUBLE => Opcodes.T_DOUBLE + } + } + } + jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) + } + } + + + @inline def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) } + @inline def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) } + + @inline def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) } + @inline def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) } + + @inline def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) } + @inline def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) } + @inline def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) } + @inline def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) } + @inline def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) } + @inline def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) } + + @inline def invokespecial(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc) + } + @inline def invokestatic(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc) + } + @inline def invokeinterface(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc) + } + @inline def invokevirtual(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc) + } + + @inline def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } + @inline def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) } + @inline def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) } + @inline def emitIF_ACMP(cond: TestOp, label: asm.Label) { + assert((cond == EQ) || (cond == NE), cond) + val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) + jmethod.visitJumpInsn(opc, label) + } + @inline def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) } + @inline def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) } + + @inline def emitRETURN(tk: TypeKind) { + if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) } + else { emitTypeBased(returnOpcodes, tk) } + } + + /** Emits one of tableswitch or lookoupswitch. */ + def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) { + assert(keys.length == branches.length) + + // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only. + // Similar to what javac emits for a switch statement consisting only of a default case. + if (keys.length == 0) { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + return + } + + // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort + var i = 1 + while (i < keys.length) { + var j = 1 + while (j <= keys.length - i) { + if (keys(j) < keys(j - 1)) { + val tmp = keys(j) + keys(j) = keys(j - 1) + keys(j - 1) = tmp + val tmpL = branches(j) + branches(j) = branches(j - 1) + branches(j - 1) = tmpL + } + j += 1 + } + i += 1 + } + + // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011) + i = 1 + while (i < keys.length) { + if(keys(i-1) == keys(i)) { + abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") + } + i += 1 + } + + val keyMin = keys(0) + val keyMax = keys(keys.length - 1) + + val isDenseEnough: Boolean = { + /** Calculate in long to guard against overflow. TODO what overflow??? */ + val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] + val klenD: Double = keys.length + val kdensity: Double = (klenD / keyRangeD) + + kdensity >= minDensity + } + + if (isDenseEnough) { + // use a table in which holes are filled with defaultBranch. + val keyRange = (keyMax - keyMin + 1) + val newBranches = new Array[asm.Label](keyRange) + var oldPos = 0; + var i = 0 + while(i < keyRange) { + val key = keyMin + i; + if (keys(oldPos) == key) { + newBranches(i) = branches(oldPos) + oldPos += 1 + } else { + newBranches(i) = defaultBranch + } + i += 1 + } + assert(oldPos == keys.length, "emitSWITCH") + jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*) + } else { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + } + } + + // internal helpers -- not part of the public API of `jcode` + // don't make private otherwise inlining will suffer + + def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) { + assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc) + jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx) + } + + // ---------------- array load and store ---------------- + + val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) } + val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) } + + val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) } + + def emitTypeBased(opcs: Array[Int], tk: TypeKind) { + assert(tk != UNIT, tk) + val opc = { + if(tk.isRefOrArrayType) { opcs(0) } + else if(tk.isIntSizedType) { + (tk: @unchecked) match { + case BOOL | BYTE => opcs(1) + case SHORT => opcs(2) + case CHAR => opcs(3) + case INT => opcs(4) + } + } else { + (tk: @unchecked) match { + case LONG => opcs(5) + case FLOAT => opcs(6) + case DOUBLE => opcs(7) + } + } + } + jmethod.visitInsn(opc) + } + + // ---------------- primitive operations ---------------- + + val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) } + val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) } + val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) } + val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) } + val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) } + val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) } + + def emitPrimitive(opcs: Array[Int], tk: TypeKind) { + val opc = { + if(tk.isIntSizedType) { opcs(0) } + else { + (tk: @unchecked) match { + case LONG => opcs(1) + case FLOAT => opcs(2) + case DOUBLE => opcs(3) + } + } + } + jmethod.visitInsn(opc) + } + + } + + /** Invoked from genMethod() and addStaticInit() */ + def genCode(m: IMethod, + emitVars: Boolean, // this param name hides the instance-level var + isStatic: Boolean) { + + + newNormal.normalize(m) + + // ------------------------------------------------------------------------------------------------------------ + // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization` + // ------------------------------------------------------------------------------------------------------------ + + val linearization: List[BasicBlock] = linearizer.linearize(m) + if(linearization.isEmpty) { return } + + var isModuleInitialized = false + + val labels: collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*) + + val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted + + // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label. + val linNext: collection.Map[BasicBlock, asm.Label] = { + val result = mutable.HashMap.empty[BasicBlock, asm.Label] + var rest = linearization + var prev = rest.head + rest = rest.tail + while(!rest.isEmpty) { + result += (prev -> labels(rest.head)) + prev = rest.head + rest = rest.tail + } + assert(!result.contains(prev)) + result += (prev -> onePastLast) + + result + } + + // ------------------------------------------------------------------------------------------------------------ + // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock()) + // ------------------------------------------------------------------------------------------------------------ + + /**Generate exception handlers for the current method. + * + * Quoting from the JVMS 4.7.3 The Code Attribute + * The items of the Code_attribute structure are as follows: + * . . . + * exception_table[] + * Each entry in the exception_table array describes one + * exception handler in the code array. The order of the handlers in + * the exception_table array is significant. + * Each exception_table entry contains the following four items: + * start_pc, end_pc: + * ... The value of end_pc either must be a valid index into + * the code array of the opcode of an instruction or must be equal to code_length, + * the length of the code array. + * handler_pc: + * The value of the handler_pc item indicates the start of the exception handler + * catch_type: + * ... If the value of the catch_type item is zero, + * this exception handler is called for all exceptions. + * This is used to implement finally + */ + def genExceptionHandlers() { + + /** Return a list of pairs of intervals where the handler is active. + * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end]. + * Preconditions: + * - e.covered non-empty + * Postconditions for the result: + * - always non-empty + * - intervals are sorted as per `linearization` + * - the argument's `covered` blocks have been grouped into maximally contiguous intervals, + * ie. between any two intervals in the result there is a non-empty gap. + * - each of the `covered` blocks in the argument is contained in some interval in the result + */ + def intervals(e: ExceptionHandler): List[BlockInteval] = { + assert(e.covered.nonEmpty, e) + var result: List[BlockInteval] = Nil + var rest = linearization + + // find intervals + while(!rest.isEmpty) { + // find interval start + var start: BasicBlock = null + while(!rest.isEmpty && (start eq null)) { + if(e.covered(rest.head)) { start = rest.head } + rest = rest.tail + } + if(start ne null) { + // find interval end + var end = start // for the time being + while(!rest.isEmpty && (e.covered(rest.head))) { + end = rest.head + rest = rest.tail + } + result = BlockInteval(start, end) :: result + } + } + + assert(result.nonEmpty, e) + + result + } + + /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains + * blocks not in the linearization (dead-code?). Is that well-formed or not? + * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does). + */ + for (e <- this.method.exh) { + val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } ) + // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)") + if(ignore.nonEmpty) { + e.covered = e.covered filterNot ignore + } + } + + // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table. + // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class? + for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) { + debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method + + " from: " + p.start + " to: " + p.end + " catching: " + e.cls); + val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null + else javaName(e.cls) + jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls) + } + } // end of genCode()'s genExceptionHandlers() + + if (m.exh.nonEmpty) { genExceptionHandlers() } + + // ------------------------------------------------------------------------------------------------------------ + // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute). + // ------------------------------------------------------------------------------------------------------------ + + case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive. + + case class Interval(lstart: asm.Label, lend: asm.Label) { + @inline final def start = lstart.getOffset + @inline final def end = lend.getOffset + + def precedes(that: Interval): Boolean = { this.end < that.start } + + def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) } + + def mergeWith(that: Interval): Interval = { + val newStart = if(this.start <= that.start) this.lstart else that.lstart; + val newEnd = if(this.end <= that.end) that.lend else this.lend; + Interval(newStart, newEnd) + } + + def repOK: Boolean = { start <= end } + + } + + /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */ + object scoping { + + private val pending = mutable.Map.empty[Local, mutable.Stack[Label]] + private var seen: List[LocVarEntry] = Nil + + private def fuse(ranges: List[Interval], added: Interval): List[Interval] = { + assert(added.repOK, added) + if(ranges.isEmpty) { return List(added) } + // precond: ranges is sorted by increasing start + var fused: List[Interval] = Nil + var done = false + var rest = ranges + while(!done && rest.nonEmpty) { + val current = rest.head + assert(current.repOK, current) + rest = rest.tail + if(added precedes current) { + fused = fused ::: ( added :: current :: rest ) + done = true + } else if(current overlaps added) { + fused = fused ::: ( added.mergeWith(current) :: rest ) + done = true + } + } + if(!done) { fused = fused ::: List(added) } + assert(repOK(fused), fused) + + fused + } + + def pushScope(lv: Local, start: Label) { + val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label]) + st.push(start) + } + def popScope(lv: Local, end: Label, iPos: Position) { + pending.get(lv) match { + case Some(st) if st.nonEmpty => + val start = st.pop() + seen ::= LocVarEntry(lv, start, end) + case _ => + // TODO SI-6049 + getCurrentCUnit().warning(iPos, "Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6049") + } + } + + def getMerged(): collection.Map[Local, List[Interval]] = { + // TODO should but isn't: unbalanced start(s) of scope(s) + val shouldBeEmpty = pending filter { p => val Pair(k, st) = p; st.nonEmpty }; + + val merged = mutable.Map.empty[Local, List[Interval]] + + def addToMerged(lv: Local, start: Label, end: Label) { + val ranges = merged.getOrElseUpdate(lv, Nil) + val coalesced = fuse(ranges, Interval(start, end)) + merged.update(lv, coalesced) + } + + for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) } + + /* for each var with unbalanced start(s) of scope(s): + (a) take the earliest start (among unbalanced and balanced starts) + (b) take the latest end (onePastLast if none available) + (c) merge the thus made-up interval + */ + for(Pair(k, st) <- shouldBeEmpty) { + var start = st.toList.sortBy(_.getOffset).head + if(merged.isDefinedAt(k)) { + val balancedStart = merged(k).head.lstart + if(balancedStart.getOffset < start.getOffset) { + start = balancedStart; + } + } + val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend; + val end = endOpt.getOrElse(onePastLast) + addToMerged(k, start, end) + } + + merged + } + + private def repOK(fused: List[Interval]): Boolean = { + fused match { + case Nil => true + case h :: Nil => h.repOK + case h :: n :: rest => + h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest) + } + } + + } + + def genLocalVariableTable() { + // adding `this` and method params. + if (!isStatic) { + jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0) + } + for(lv <- m.params) { + jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv)) + } + // adding non-param locals + var anonCounter = 0 + var fltnd: List[Triple[String, Local, Interval]] = Nil + for(Pair(local, ranges) <- scoping.getMerged()) { + var name = javaName(local.sym) + if (name == null) { + anonCounter += 1; + name = "" + } + for(intrvl <- ranges) { + fltnd ::= Triple(name, local, intrvl) + } + } + // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain). + val srtd = fltnd.sortBy { kr => + val Triple(name: String, local: Local, intrvl: Interval) = kr + + Triple(intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name) + } + + for(Triple(name, local, Interval(start, end)) <- srtd) { + jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local)) + } + // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute" + } + + // ------------------------------------------------------------------------------------------------------------ + // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position. + // ------------------------------------------------------------------------------------------------------------ + + case class LineNumberEntry(line: Int, start: asm.Label) + var lastLineNr: Int = -1 + var lnEntries: List[LineNumberEntry] = Nil + + // ------------------------------------------------------------------------------------------------------------ + // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()). + // ------------------------------------------------------------------------------------------------------------ + + var nextBlock: BasicBlock = linearization.head + + def genBlocks(l: List[BasicBlock]): Unit = l match { + case Nil => () + case x :: Nil => nextBlock = null; genBlock(x) + case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys) + } + + def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = { + target.isPublic || target.isProtected && { + (site.enclClass isSubClass target.enclClass) || + (site.enclosingPackage == target.privateWithin) + } + } // end of genCode()'s isAccessibleFrom() + + def genCallMethod(call: CALL_METHOD) { + val CALL_METHOD(method, style) = call + val siteSymbol = clasz.symbol + val hostSymbol = call.hostClass + val methodOwner = method.owner + // info calls so that types are up to date; erasure may add lateINTERFACE to traits + hostSymbol.info ; methodOwner.info + + def isInterfaceCall(sym: Symbol) = ( + sym.isInterface && methodOwner != ObjectClass + || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass) + ) + // whether to reference the type of the receiver or + // the type of the method owner (if not an interface!) + val useMethodOwner = ( + style != Dynamic + || !isInterfaceCall(hostSymbol) && isAccessibleFrom(methodOwner, siteSymbol) + || hostSymbol.isBottomClass + ) + val receiver = if (useMethodOwner) methodOwner else hostSymbol + val jowner = javaName(receiver) + val jname = javaName(method) + val jtype = javaType(method).getDescriptor() + + def dbg(invoke: String) { + debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype)) + } + + def initModule() { + // we initialize the MODULE$ field immediately after the super ctor + if (isStaticModule(siteSymbol) && !isModuleInitialized && + jMethodName == INSTANCE_CONSTRUCTOR_NAME && + jname == INSTANCE_CONSTRUCTOR_NAME) { + isModuleInitialized = true + jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) + jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr) + } + } + + style match { + case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype) + case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype) + case Dynamic if isInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype) + case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype) + case SuperCall(_) => + dbg("invokespecial") + jcode.invokespecial(jowner, jname, jtype) + initModule() + } + } // end of genCode()'s genCallMethod() + + def genBlock(b: BasicBlock) { + jmethod.visitLabel(labels(b)) + + import asm.Opcodes; + + debuglog("Generating code for block: " + b) + + // val lastInstr = b.lastInstruction + + for (instr <- b) { + + if(instr.pos.isDefined) { + val iPos = instr.pos + val currentLineNr = iPos.line + val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else + if(!skip) { + lastLineNr = currentLineNr + val lineLab = new asm.Label + jmethod.visitLabel(lineLab) + lnEntries ::= LineNumberEntry(currentLineNr, lineLab) + } + } + + (instr.category: @scala.annotation.switch) match { + + case icodes.localsCat => (instr: @unchecked) match { + case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0) + case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind) + case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind) + case STORE_THIS(_) => + // this only works for impl classes because the self parameter comes first + // in the method signature. If that changes, this code has to be revisited. + jmethod.visitVarInsn(Opcodes.ASTORE, 0) + + case SCOPE_ENTER(lv) => + // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored + val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) + if(relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars? + // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) + // similarly, these labels aren't tracked in the `labels` map. + val start = new asm.Label + jmethod.visitLabel(start) + scoping.pushScope(lv, start) + } + + case SCOPE_EXIT(lv) => + val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) + if(relevant) { + // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) + // similarly, these labels aren't tracked in the `labels` map. + val end = new asm.Label + jmethod.visitLabel(end) + scoping.popScope(lv, end, instr.pos) + } + } + + case icodes.stackCat => (instr: @unchecked) match { + + case LOAD_MODULE(module) => + // assert(module.isModule, "Expected module: " + module) + debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags)); + if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) { + jmethod.visitVarInsn(Opcodes.ALOAD, 0) + } else { + jmethod.visitFieldInsn( + Opcodes.GETSTATIC, + javaName(module) /* + "$" */ , + strMODULE_INSTANCE_FIELD, + descriptor(module) + ) + } + + case DROP(kind) => emit(if(kind.isWideType) Opcodes.POP2 else Opcodes.POP) + + case DUP(kind) => emit(if(kind.isWideType) Opcodes.DUP2 else Opcodes.DUP) + + case LOAD_EXCEPTION(_) => () + } + + case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant) + + case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos) + + case icodes.castsCat => (instr: @unchecked) match { + + case IS_INSTANCE(tpe) => + val jtyp: asm.Type = + tpe match { + case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) + case ARRAY(elem) => javaArrayType(javaType(elem)) + case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) + } + jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName) + + case CHECK_CAST(tpe) => + tpe match { + + case REFERENCE(cls) => + if (cls != ObjectClass) { // No need to checkcast for Objects + jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls)) + } + + case ARRAY(elem) => + val iname = javaArrayType(javaType(elem)).getInternalName + jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname) + + case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) + } + + } + + case icodes.objsCat => (instr: @unchecked) match { + + case BOX(kind) => + val MethodNameAndType(mname, mdesc) = jBoxTo(kind) + jcode.invokestatic(BoxesRunTime, mname, mdesc) + + case UNBOX(kind) => + val MethodNameAndType(mname, mdesc) = jUnboxTo(kind) + jcode.invokestatic(BoxesRunTime, mname, mdesc) + + case NEW(REFERENCE(cls)) => + val className = javaName(cls) + jmethod.visitTypeInsn(Opcodes.NEW, className) + + case MONITOR_ENTER() => emit(Opcodes.MONITORENTER) + case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT) + } + + case icodes.fldsCat => (instr: @unchecked) match { + + case lf @ LOAD_FIELD(field, isStatic) => + var owner = javaName(lf.hostClass) + debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags)) + val fieldJName = javaName(field) + val fieldDescr = descriptor(field) + val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD + jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) + + case STORE_FIELD(field, isStatic) => + val owner = javaName(field.owner) + val fieldJName = javaName(field) + val fieldDescr = descriptor(field) + val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD + jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) + + } + + case icodes.mthdsCat => (instr: @unchecked) match { + + /** Special handling to access native Array.clone() */ + case call @ CALL_METHOD(definitions.Array_clone, Dynamic) => + val target: String = javaType(call.targetTypeKind).getInternalName + jcode.invokevirtual(target, "clone", mdesc_arrayClone) + + case call @ CALL_METHOD(method, style) => genCallMethod(call) + + } + + case icodes.arraysCat => (instr: @unchecked) match { + case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind) + case STORE_ARRAY_ITEM(kind) => jcode.astore(kind) + case CREATE_ARRAY(elem, 1) => jcode newarray elem + case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims) + } + + case icodes.jumpsCat => (instr: @unchecked) match { + + case sw @ SWITCH(tagss, branches) => + assert(branches.length == tagss.length + 1, sw) + val flatSize = sw.flatTagsCount + val flatKeys = new Array[Int](flatSize) + val flatBranches = new Array[asm.Label](flatSize) + + var restTagss = tagss + var restBranches = branches + var k = 0 // ranges over flatKeys and flatBranches + while(restTagss.nonEmpty) { + val currLabel = labels(restBranches.head) + for(cTag <- restTagss.head) { + flatKeys(k) = cTag; + flatBranches(k) = currLabel + k += 1 + } + restTagss = restTagss.tail + restBranches = restBranches.tail + } + val defaultLabel = labels(restBranches.head) + assert(restBranches.tail.isEmpty) + debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches) + jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY) + + case JUMP(whereto) => + if (nextBlock != whereto) { + jcode goTo labels(whereto) + } + + case CJUMP(success, failure, cond, kind) => + if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + if (nextBlock == success) { + jcode.emitIF_ICMP(cond.negate, labels(failure)) + // .. and fall through to success label + } else { + jcode.emitIF_ICMP(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + if (nextBlock == success) { + jcode.emitIF_ACMP(cond.negate, labels(failure)) + // .. and fall through to success label + } else { + jcode.emitIF_ACMP(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } else { + (kind: @unchecked) match { + case LONG => emit(Opcodes.LCMP) + case FLOAT => + if (cond == LT || cond == LE) emit(Opcodes.FCMPG) + else emit(Opcodes.FCMPL) + case DOUBLE => + if (cond == LT || cond == LE) emit(Opcodes.DCMPG) + else emit(Opcodes.DCMPL) + } + if (nextBlock == success) { + jcode.emitIF(cond.negate, labels(failure)) + // .. and fall through to success label + } else { + jcode.emitIF(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } + + case CZJUMP(success, failure, cond, kind) => + if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + if (nextBlock == success) { + jcode.emitIF(cond.negate, labels(failure)) + } else { + jcode.emitIF(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + val Success = success + val Failure = failure + // @unchecked because references aren't compared with GT, GE, LT, LE. + ((cond, nextBlock) : @unchecked) match { + case (EQ, Success) => jcode emitIFNONNULL labels(failure) + case (NE, Failure) => jcode emitIFNONNULL labels(success) + case (EQ, Failure) => jcode emitIFNULL labels(success) + case (NE, Success) => jcode emitIFNULL labels(failure) + case (EQ, _) => + jcode emitIFNULL labels(success) + jcode goTo labels(failure) + case (NE, _) => + jcode emitIFNONNULL labels(success) + jcode goTo labels(failure) + } + } else { + (kind: @unchecked) match { + case LONG => + emit(Opcodes.LCONST_0) + emit(Opcodes.LCMP) + case FLOAT => + emit(Opcodes.FCONST_0) + if (cond == LT || cond == LE) emit(Opcodes.FCMPG) + else emit(Opcodes.FCMPL) + case DOUBLE => + emit(Opcodes.DCONST_0) + if (cond == LT || cond == LE) emit(Opcodes.DCMPG) + else emit(Opcodes.DCMPL) + } + if (nextBlock == success) { + jcode.emitIF(cond.negate, labels(failure)) + } else { + jcode.emitIF(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } + + } + + case icodes.retCat => (instr: @unchecked) match { + case RETURN(kind) => jcode emitRETURN kind + case THROW(_) => emit(Opcodes.ATHROW) + } + + } + + } + + } // end of genCode()'s genBlock() + + /** + * Emits one or more conversion instructions based on the types given as arguments. + * + * @param from The type of the value to be converted into another type. + * @param to The type the value will be converted into. + */ + def emitT2T(from: TypeKind, to: TypeKind) { + assert(isNonUnitValueTK(from), from) + assert(isNonUnitValueTK(to), to) + + def pickOne(opcs: Array[Int]) { + val chosen = (to: @unchecked) match { + case BYTE => opcs(0) + case SHORT => opcs(1) + case CHAR => opcs(2) + case INT => opcs(3) + case LONG => opcs(4) + case FLOAT => opcs(5) + case DOUBLE => opcs(6) + } + if(chosen != -1) { emit(chosen) } + } + + if(from == to) { return } + if((from == BOOL) || (to == BOOL)) { + // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) + throw new Error("inconvertible types : " + from.toString() + " -> " + to.toString()) + } + + if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already) + + val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT) + val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing + val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing + val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) } + + (from: @unchecked) match { + case BYTE => pickOne(fromByte) + case SHORT => pickOne(fromShort) + case CHAR => pickOne(fromChar) + case INT => pickOne(fromInt) + } + + } else { // FLOAT, LONG, DOUBLE + + (from: @unchecked) match { + case FLOAT => + import asm.Opcodes.{ F2L, F2D, F2I } + (to: @unchecked) match { + case LONG => emit(F2L) + case DOUBLE => emit(F2D) + case _ => emit(F2I); emitT2T(INT, to) + } + + case LONG => + import asm.Opcodes.{ L2F, L2D, L2I } + (to: @unchecked) match { + case FLOAT => emit(L2F) + case DOUBLE => emit(L2D) + case _ => emit(L2I); emitT2T(INT, to) + } + + case DOUBLE => + import asm.Opcodes.{ D2L, D2F, D2I } + (to: @unchecked) match { + case FLOAT => emit(D2F) + case LONG => emit(D2L) + case _ => emit(D2I); emitT2T(INT, to) + } + } + } + } // end of genCode()'s emitT2T() + + def genPrimitive(primitive: Primitive, pos: Position) { + + import asm.Opcodes; + + primitive match { + + case Negation(kind) => jcode.neg(kind) + + case Arithmetic(op, kind) => + op match { + + case ADD => jcode.add(kind) + case SUB => jcode.sub(kind) + case MUL => jcode.mul(kind) + case DIV => jcode.div(kind) + case REM => jcode.rem(kind) + + case NOT => + if(kind.isIntSizedType) { + emit(Opcodes.ICONST_M1) + emit(Opcodes.IXOR) + } else if(kind == LONG) { + jmethod.visitLdcInsn(new java.lang.Long(-1)) + jmethod.visitInsn(Opcodes.LXOR) + } else { + abort("Impossible to negate an " + kind) + } + + case _ => + abort("Unknown arithmetic primitive " + primitive) + } + + // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey) + // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead. + // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed). + case Logical(op, kind) => ((op, kind): @unchecked) match { + case (AND, LONG) => emit(Opcodes.LAND) + case (AND, INT) => emit(Opcodes.IAND) + case (AND, _) => + emit(Opcodes.IAND) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (OR, LONG) => emit(Opcodes.LOR) + case (OR, INT) => emit(Opcodes.IOR) + case (OR, _) => + emit(Opcodes.IOR) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (XOR, LONG) => emit(Opcodes.LXOR) + case (XOR, INT) => emit(Opcodes.IXOR) + case (XOR, _) => + emit(Opcodes.IXOR) + if (kind != BOOL) { emitT2T(INT, kind) } + } + + case Shift(op, kind) => ((op, kind): @unchecked) match { + case (LSL, LONG) => emit(Opcodes.LSHL) + case (LSL, INT) => emit(Opcodes.ISHL) + case (LSL, _) => + emit(Opcodes.ISHL) + emitT2T(INT, kind) + + case (ASR, LONG) => emit(Opcodes.LSHR) + case (ASR, INT) => emit(Opcodes.ISHR) + case (ASR, _) => + emit(Opcodes.ISHR) + emitT2T(INT, kind) + + case (LSR, LONG) => emit(Opcodes.LUSHR) + case (LSR, INT) => emit(Opcodes.IUSHR) + case (LSR, _) => + emit(Opcodes.IUSHR) + emitT2T(INT, kind) + } + + case Comparison(op, kind) => ((op, kind): @unchecked) match { + case (CMP, LONG) => emit(Opcodes.LCMP) + case (CMPL, FLOAT) => emit(Opcodes.FCMPL) + case (CMPG, FLOAT) => emit(Opcodes.FCMPG) + case (CMPL, DOUBLE) => emit(Opcodes.DCMPL) + case (CMPG, DOUBLE) => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc3.html + } + + case Conversion(src, dst) => + debuglog("Converting from: " + src + " to: " + dst) + if (dst == BOOL) { println("Illegal conversion at: " + clasz + " at: " + pos.source + ":" + pos.line) } + else { emitT2T(src, dst) } + + case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH) + + case StartConcat => + jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) + jmethod.visitInsn(Opcodes.DUP) + jcode.invokespecial( + StringBuilderClassName, + INSTANCE_CONSTRUCTOR_NAME, + mdesc_arglessvoid + ) + + case StringConcat(el) => + val jtype = el match { + case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT + case _ => javaType(el) + } + jcode.invokevirtual( + StringBuilderClassName, + "append", + asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*) + ) + + case EndConcat => + jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString) + + case _ => abort("Unimplemented primitive " + primitive) + } + } // end of genCode()'s genPrimitive() + + // ------------------------------------------------------------------------------------------------------------ + // Part 6 of genCode(): the executable part of genCode() starts here. + // ------------------------------------------------------------------------------------------------------------ + + genBlocks(linearization) + + jmethod.visitLabel(onePastLast) + + if(emitLines) { + for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) } + } + if(emitVars) { genLocalVariableTable() } + + } // end of BytecodeGenerator.genCode() + + + ////////////////////// local vars /////////////////////// + + // def sizeOf(sym: Symbol): Int = sizeOf(toTypeKind(sym.tpe)) + + def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1 + + // def indexOf(m: IMethod, sym: Symbol): Int = { + // val Some(local) = m lookupLocal sym + // indexOf(local) + // } + + @inline final def indexOf(local: Local): Int = { + assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ") + local.index + } + + /** + * Compute the indexes of each local variable of the given method. + * *Does not assume the parameters come first!* + */ + def computeLocalVarsIndex(m: IMethod) { + var idx = if (m.symbol.isStaticMember) 0 else 1; + + for (l <- m.params) { + debuglog("Index value for " + l + "{" + l.## + "}: " + idx) + l.index = idx + idx += sizeOf(l.kind) + } + + for (l <- m.locals if !l.arg) { + debuglog("Index value for " + l + "{" + l.## + "}: " + idx) + l.index = idx + idx += sizeOf(l.kind) + } + } + + } // end of class JPlainBuilder + + + /** builder of mirror classes */ + class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) { + + private var cunit: CompilationUnit = _ + def getCurrentCUnit(): CompilationUnit = cunit; + + /** Generate a mirror class for a top-level module. A mirror class is a class + * containing only static methods that forward to the corresponding method + * on the MODULE instance of the given Scala object. It will only be + * generated if there is no companion class: if there is, an attempt will + * instead be made to add the forwarder methods to the companion class. + */ + def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) { + assert(modsym.companionClass == NoSymbol, modsym) + innerClassBuffer.clear() + this.cunit = cunit + val moduleName = javaName(modsym) // + "$" + val mirrorName = moduleName.substring(0, moduleName.length() - 1) + + val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) + val mirrorClass = createJClass(flags, + mirrorName, + null /* no java-generic-signature */, + JAVA_LANG_OBJECT.getInternalName, + EMPTY_STRING_ARRAY) + + log("Dumping mirror class for '%s'".format(mirrorName)) + + // typestate: entering mode with valid call sequences: + // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* + + if(emitSource) { + mirrorClass.visitSource("" + cunit.source, + null /* SourceDebugExtension */) + } + + val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol) + mirrorClass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) + emitAnnotations(mirrorClass, modsym.annotations ++ ssa) + + // typestate: entering mode with valid call sequences: + // ( visitInnerClass | visitField | visitMethod )* visitEnd + + addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) + + addInnerClasses(modsym, mirrorClass) + mirrorClass.visitEnd() + writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) + } + + + } // end of class JMirrorBuilder + + + /** builder of bean info classes */ + class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + + /** + * Generate a bean info class that describes the given class. + * + * @author Ross Judson (ross.judson@soletta.com) + */ + def genBeanInfoClass(clasz: IClass) { + + // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip") + // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName") + // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription") + // val description = c.symbol getAnnotation BeanDescriptionAttr + // informProgress(description.toString) + innerClassBuffer.clear() + + val flags = mkFlags( + javaFlags(clasz.symbol), + if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val beanInfoName = (javaName(clasz.symbol) + "BeanInfo") + val beanInfoClass = createJClass( + flags, + beanInfoName, + null, // no java-generic-signature + "scala/beans/ScalaBeanInfo", + EMPTY_STRING_ARRAY + ) + + // beanInfoClass typestate: entering mode with valid call sequences: + // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* + + beanInfoClass.visitSource( + clasz.cunit.source.toString, + null /* SourceDebugExtension */ + ) + + var fieldList = List[String]() + + for (f <- clasz.fields if f.symbol.hasGetter; + g = f.symbol.getter(clasz.symbol); + s = f.symbol.setter(clasz.symbol); + if g.isPublic && !(f.symbol.name startsWith "$") + ) { + // inserting $outer breaks the bean + fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList + } + + val methodList: List[String] = + for (m <- clasz.methods + if !m.symbol.isConstructor && + m.symbol.isPublic && + !(m.symbol.name startsWith "$") && + !m.symbol.isGetter && + !m.symbol.isSetter) + yield javaName(m.symbol) + + // beanInfoClass typestate: entering mode with valid call sequences: + // ( visitInnerClass | visitField | visitMethod )* visitEnd + + val constructor = beanInfoClass.visitMethod( + asm.Opcodes.ACC_PUBLIC, + INSTANCE_CONSTRUCTOR_NAME, + mdesc_arglessvoid, + null, // no java-generic-signature + EMPTY_STRING_ARRAY // no throwable exceptions + ) + + // constructor typestate: entering mode with valid call sequences: + // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* + + val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING) + val conJType: asm.Type = + asm.Type.getMethodType( + asm.Type.VOID_TYPE, + Array(javaType(ClassClass), stringArrayJType, stringArrayJType): _* + ) + + def push(lst: List[String]) { + var fi = 0 + for (f <- lst) { + constructor.visitInsn(asm.Opcodes.DUP) + constructor.visitLdcInsn(new java.lang.Integer(fi)) + if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } + else { constructor.visitLdcInsn(f) } + constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE)) + fi += 1 + } + } + + // constructor typestate: entering mode with valid call sequences: + // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd + + constructor.visitCode() + + constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) + // push the class + constructor.visitLdcInsn(javaType(clasz.symbol)) + + // push the string array of field information + constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + push(fieldList) + + // push the string array of method information + constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + push(methodList) + + // invoke the superclass constructor, which will do the + // necessary java reflection and create Method objects. + constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor) + constructor.visitInsn(asm.Opcodes.RETURN) + + constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments + constructor.visitEnd() + + addInnerClasses(clasz.symbol, beanInfoClass) + beanInfoClass.visitEnd() + + writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol) + } + + } // end of class JBeanInfoBuilder + + /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for. + * In particualr, IMethod.normalize() doesn't collapseJumpChains(). + * + * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them). + */ + object newNormal { + + def startsWithJump(b: BasicBlock): Boolean = { assert(b.nonEmpty, "empty block"); b.firstInstruction.isInstanceOf[JUMP] } + + /** Prune from an exception handler those covered blocks which are jump-only. */ + private def coverWhatCountsOnly(m: IMethod): Boolean = { + assert(m.hasCode, "code-less method") + + var wasReduced = false + for(h <- m.exh) { + val shouldntCover = (h.covered filter startsWithJump) + if(shouldntCover.nonEmpty) { + wasReduced = true + h.covered --= shouldntCover // not removing any block on purpose. + } + } + + wasReduced + } + + /** An exception handler is pruned provided any of the following holds: + * (1) it covers nothing (for example, this may result after removing unreachable blocks) + * (2) each block it covers is of the form: JUMP(_) + * Return true iff one or more ExceptionHandlers were removed. + * + * A caveat: removing an exception handler, for whatever reason, means that its handler code (even if unreachable) + * won't be able to cause a class-loading-exception. As a result, behavior can be different. + */ + private def elimNonCoveringExh(m: IMethod): Boolean = { + assert(m.hasCode, "code-less method") + + def isRedundant(eh: ExceptionHandler): Boolean = { + (eh.cls != NoSymbol) && ( // TODO `eh.isFinallyBlock` more readable than `eh.cls != NoSymbol` + eh.covered.isEmpty + || (eh.covered forall startsWithJump) + ) + } + + var wasReduced = false + val toPrune = (m.exh.toSet filter isRedundant) + if(toPrune.nonEmpty) { + wasReduced = true + for(h <- toPrune; r <- h.blocks) { m.code.removeBlock(r) } // TODO m.code.removeExh(h) + m.exh = (m.exh filterNot toPrune) + } + + wasReduced + } + + private def isJumpOnly(b: BasicBlock): Option[BasicBlock] = { + b.toList match { + case JUMP(whereto) :: rest => + assert(rest.isEmpty, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)") + Some(whereto) + case _ => None + } + } + + private def directSuccStar(b: BasicBlock): List[BasicBlock] = { directSuccStar(List(b)) } + + /** Transitive closure of successors potentially reachable due to normal (non-exceptional) control flow. + Those BBs in the argument are also included in the result */ + private def directSuccStar(starters: Traversable[BasicBlock]): List[BasicBlock] = { + val result = new mutable.ListBuffer[BasicBlock] + var toVisit: List[BasicBlock] = starters.toList.distinct + while(toVisit.nonEmpty) { + val h = toVisit.head + toVisit = toVisit.tail + result += h + for(p <- h.directSuccessors; if !result.contains(p) && !toVisit.contains(p)) { toVisit = p :: toVisit } + } + result.toList + } + + /** Returns: + * for single-block self-loops, the pair (start, Nil) + * for other cycles, the pair (backedge-target, basic-blocks-in-the-cycle-except-backedge-target) + * otherwise a pair consisting of: + * (a) the endpoint of a (single or multi-hop) chain of JUMPs + * (such endpoint does not start with a JUMP and therefore is not part of the chain); and + * (b) the chain (ie blocks to be removed when collapsing the chain of jumps). + * Precondition: the BasicBlock given as argument starts with an unconditional JUMP. + */ + private def finalDestination(start: BasicBlock): (BasicBlock, List[BasicBlock]) = { + assert(startsWithJump(start), "not the start of a (single or multi-hop) chain of JUMPs.") + var hops: List[BasicBlock] = Nil + var prev = start + var done = false + do { + done = isJumpOnly(prev) match { + case Some(dest) => + if (dest == start) { return (start, hops) } // leave infinite-loops in place + hops ::= prev + if (hops.contains(dest)) { + // leave infinite-loops in place + return (dest, hops filterNot (dest eq)) + } + prev = dest; + false + case None => true + } + } while(!done) + + (prev, hops) + } + + /** + * Collapse a chain of "jump-only" blocks such as: + * + * JUMP b1; + * b1: JUMP b2; + * b2: JUMP ... etc. + * + * by re-wiring predecessors to target directly the "final destination". + * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed. + + * Returns true if any replacement was made, false otherwise. + * + * In more detail: + * Starting at each of the entry points (m.startBlock, the start block of each exception handler) + * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D. + * The blocks thus skipped are also removed from IMethod.blocks. + * + * Rationale for this normalization: + * test/files/run/private-inline.scala after -optimize is chock full of + * BasicBlocks containing just JUMP(whereTo), where no exception handler straddles them. + * They should be collapsed by IMethod.normalize() but aren't. + * That was fine in FJBG times when by the time the exception table was emitted, + * it already contained "anchored" labels (ie instruction offsets were known) + * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question) + * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range" + * Now that visitTryCatchBlock() must be called before Labels are resolved, + * this method gets rid of the BasicBlocks described above (to recap, consisting of just a JUMP). + */ + private def collapseJumpOnlyBlocks(m: IMethod): Boolean = { + assert(m.hasCode, "code-less method") + + /* "start" is relative in a cycle, but we call this helper with the "first" entry-point we found. */ + def realTarget(jumpStart: BasicBlock): Map[BasicBlock, BasicBlock] = { + assert(startsWithJump(jumpStart), "not part of a jump-chain") + val Pair(dest, redundants) = finalDestination(jumpStart) + (for(skipOver <- redundants) yield Pair(skipOver, dest)).toMap + } + + def rephraseGotos(detour: Map[BasicBlock, BasicBlock]) { + for(Pair(oldTarget, newTarget) <- detour.iterator) { + if(m.startBlock == oldTarget) { + m.code.startBlock = newTarget + } + for(eh <- m.exh; if eh.startBlock == oldTarget) { + eh.setStartBlock(newTarget) + } + for(b <- m.blocks; if !detour.isDefinedAt(b)) { + val idxLast = (b.size - 1) + b.lastInstruction match { + case JUMP(whereto) => + if (whereto == oldTarget) { + b.replaceInstruction(idxLast, JUMP(newTarget)) + } + case CJUMP(succ, fail, cond, kind) => + if ((succ == oldTarget) || (fail == oldTarget)) { + b.replaceInstruction(idxLast, CJUMP(detour.getOrElse(succ, succ), + detour.getOrElse(fail, fail), + cond, kind)) + } + case CZJUMP(succ, fail, cond, kind) => + if ((succ == oldTarget) || (fail == oldTarget)) { + b.replaceInstruction(idxLast, CZJUMP(detour.getOrElse(succ, succ), + detour.getOrElse(fail, fail), + cond, kind)) + } + case SWITCH(tags, labels) => + if(labels exists (detour.isDefinedAt(_))) { + val newLabels = (labels map { lab => detour.getOrElse(lab, lab) }) + b.replaceInstruction(idxLast, SWITCH(tags, newLabels)) + } + case _ => () + } + } + } + } + + /* remove from all containers that may contain a reference to */ + def elide(redu: BasicBlock) { + assert(m.startBlock != redu, "startBlock should have been re-wired by now") + m.code.removeBlock(redu); + } + + var wasReduced = false + val entryPoints: List[BasicBlock] = m.startBlock :: (m.exh map (_.startBlock)); + + var elided = mutable.Set.empty[BasicBlock] // debug + var newTargets = mutable.Set.empty[BasicBlock] // debug + + for (ep <- entryPoints) { + var reachable = directSuccStar(ep) // this list may contain blocks belonging to jump-chains that we'll skip over + while(reachable.nonEmpty) { + val h = reachable.head + reachable = reachable.tail + if(startsWithJump(h)) { + val detour = realTarget(h) + if(detour.nonEmpty) { + wasReduced = true + reachable = (reachable filterNot (detour.keySet.contains(_))) + rephraseGotos(detour) + detour.keySet foreach elide + elided ++= detour.keySet + newTargets ++= detour.values + } + } + } + } + assert(newTargets.intersect(elided).isEmpty, "contradiction: we just elided the final destionation of a jump-chain") + + wasReduced + } + + def normalize(m: IMethod) { + if(!m.hasCode) { return } + collapseJumpOnlyBlocks(m) + var wasReduced = false; + do { + wasReduced = false + // Prune from an exception handler those covered blocks which are jump-only. + wasReduced |= coverWhatCountsOnly(m); icodes.checkValid(m) // TODO should be unnecessary now that collapseJumpOnlyBlocks(m) is in place + // Prune exception handlers covering nothing. + wasReduced |= elimNonCoveringExh(m); icodes.checkValid(m) + + // TODO see note in genExceptionHandlers about an ExceptionHandler.covered containing dead blocks (newNormal should remove them, but, where do those blocks come from?) + } while (wasReduced) + + // TODO this would be a good time to remove synthetic local vars seeing no use, don't forget to call computeLocalVarsIndex() afterwards. + } + + } + +} -- cgit v1.2.3 From 4276f61551b89162001dadbb3b77f29a85c19c4a Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 13 Jul 2012 14:13:50 +0200 Subject: SI-6077 more conservative TreeMakersToConds for CSE we were dropping irrefutable extractor calls (implemented for SI-4691) in common subcondition elimination, which caused bad code to be generated clean up TreeMakersToConds significantly in the process --- .../tools/nsc/typechecker/PatternMatching.scala | 229 ++++++++++++--------- test/files/run/t6077_patmat_cse_irrefutable.check | 1 + test/files/run/t6077_patmat_cse_irrefutable.scala | 13 ++ 3 files changed, 140 insertions(+), 103 deletions(-) create mode 100644 test/files/run/t6077_patmat_cse_irrefutable.check create mode 100644 test/files/run/t6077_patmat_cse_irrefutable.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index dea8a0b866..2a7a1bac5c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -638,7 +638,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // binder has type paramType def treeMaker(binder: Symbol, pos: Position): TreeMaker = { // checks binder ne null before chaining to the next extractor - ProductExtractorTreeMaker(binder, lengthGuard(binder), Substitution(subPatBinders, subPatRefs(binder))) + ProductExtractorTreeMaker(binder, lengthGuard(binder))(Substitution(subPatBinders, subPatRefs(binder))) } // reference the (i-1)th case accessor if it exists, otherwise the (i-1)th tuple component @@ -682,7 +682,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // the extractor call (applied to the binder bound by the flatMap corresponding to the previous (i.e., enclosing/outer) pattern) val extractorApply = atPos(pos)(spliceApply(patBinderOrCasted)) val binder = freshSym(pos, pureType(resultInMonad)) // can't simplify this when subPatBinders.isEmpty, since UnitClass.tpe is definitely wrong when isSeq, and resultInMonad should always be correct since it comes directly from the extractor's result type - ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder, Substitution(subPatBinders, subPatRefs(binder)))(resultType.typeSymbol == BooleanClass, checkedLength, patBinderOrCasted) + ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)(Substitution(subPatBinders, subPatRefs(binder)), resultType.typeSymbol == BooleanClass, checkedLength, patBinderOrCasted) } override protected def seqTree(binder: Symbol): Tree = @@ -893,7 +893,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL * the function's body is determined by the next TreeMaker * in this function's body, and all the subsequent ones, references to the symbols in `from` will be replaced by the corresponding tree in `to` */ - case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol, localSubstitution: Substitution)(extractorReturnsBoolean: Boolean, val checkedLength: Option[Int], val prevBinder: Symbol) extends FunTreeMaker { + case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol)(val localSubstitution: Substitution, extractorReturnsBoolean: Boolean, val checkedLength: Option[Int], val prevBinder: Symbol) extends FunTreeMaker { def chainBefore(next: Tree)(casegen: Casegen): Tree = { val condAndNext = extraCond map (casegen.ifThenElseZero(_, next)) getOrElse next atPos(extractor.pos)( @@ -906,7 +906,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } // TODO: allow user-defined unapplyProduct - case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree], localSubstitution: Substitution) extends FunTreeMaker { import CODE._ + case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree])(val localSubstitution: Substitution) extends FunTreeMaker { import CODE._ val nextBinder = prevBinder // just passing through def chainBefore(next: Tree)(casegen: Casegen): Tree = { val nullCheck = REF(prevBinder) OBJ_NE NULL @@ -1372,7 +1372,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case object FalseCond extends Cond {override def toString = "F"} case class AndCond(a: Cond, b: Cond) extends Cond {override def toString = a +"/\\"+ b} - case class OrCond(a: Cond, b: Cond) extends Cond {override def toString = "("+a+") \\/ ("+ b +")"} + case class OrCond(a: Cond, b: Cond) extends Cond {override def toString = "("+a+") \\/ ("+ b +")"} object EqualityCond { private val uniques = new collection.mutable.HashMap[(Tree, Tree), EqualityCond] @@ -1431,9 +1431,9 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case _ => false } - def unapply(xtm: ExtractorTreeMaker): Option[(Tree, Symbol, Substitution)] = xtm match { - case ExtractorTreeMaker(extractor, None, nextBinder, subst) if irrefutableExtractorType(extractor.tpe) => - Some(extractor, nextBinder, subst) + def unapply(xtm: ExtractorTreeMaker): Option[(Tree, Symbol)] = xtm match { + case ExtractorTreeMaker(extractor, None, nextBinder) if irrefutableExtractorType(extractor.tpe) => + Some(extractor, nextBinder) case _ => None } @@ -1441,18 +1441,13 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // returns (tree, tests), where `tree` will be used to refer to `root` in `tests` class TreeMakersToConds(val root: Symbol) { - def discard() = { - pointsToBound.clear() - trees.clear() - normalize = EmptySubstitution - accumSubst = EmptySubstitution - } // a variable in this set should never be replaced by a tree that "does not consist of a selection on a variable in this set" (intuitively) private val pointsToBound = collection.mutable.HashSet(root) private val trees = collection.mutable.HashSet.empty[Tree] // the substitution that renames variables to variables in pointsToBound private var normalize: Substitution = EmptySubstitution + private var substitutionComputed = false // replaces a variable (in pointsToBound) by a selection on another variable in pointsToBound // in the end, instead of having x1, x1.hd, x2, x2.hd, ... flying around, @@ -1462,29 +1457,6 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // pointsToBound -- accumSubst.from == Set(root) && (accumSubst.from.toSet -- pointsToBound) isEmpty private var accumSubst: Substitution = EmptySubstitution - private def updateSubstitution(subst: Substitution) = { - // find part of substitution that replaces bound symbols by new symbols, and reverse that part - // so that we don't introduce new aliases for existing symbols, thus keeping the set of bound symbols minimal - val (boundSubst, unboundSubst) = (subst.from zip subst.to) partition { - case (f, t) => - t.isInstanceOf[Ident] && (t.symbol ne NoSymbol) && pointsToBound(f) - } - val (boundFrom, boundTo) = boundSubst.unzip - val (unboundFrom, unboundTo) = unboundSubst.unzip - - // reverse substitution that would otherwise replace a variable we already encountered by a new variable - // NOTE: this forgets the more precise type we have for these later variables, but that's probably okay - normalize >>= Substitution(boundTo map (_.symbol), boundFrom map (CODE.REF(_))) - patmatDebug("normalize subst: "+ normalize) - - val okSubst = Substitution(unboundFrom, unboundTo map (normalize(_))) // it's important substitution does not duplicate trees here -- it helps to keep hash consing simple, anyway - pointsToBound ++= ((okSubst.from, okSubst.to).zipped filter { (f, t) => pointsToBound exists (sym => t.exists(_.symbol == sym)) })._1 - patmatDebug("pointsToBound: "+ pointsToBound) - - accumSubst >>= okSubst - patmatDebug("accumSubst: "+ accumSubst) - } - // hashconsing trees (modulo value-equality) def unique(t: Tree, tpOverride: Type = NoType): Tree = trees find (a => a.correspondsStructure(t)(sameValue)) match { @@ -1515,66 +1487,112 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // note that the sequencing of operations is important: must visit in same order as match execution // binderToUniqueTree uses the type of the first symbol that was encountered as the type for all future binders - final def treeMakerToCond(tm: TreeMaker, handleUnknown: TreeMaker => Cond, updateSubst: Boolean, rewriteNil: Boolean = false): Cond = { - if (updateSubst) updateSubstitution(tm.substitution) - - tm match { - case ttm@TypeTestTreeMaker(prevBinder, testedBinder, pt, _) => - object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy { - type Result = Cond - def and(a: Result, b: Result) = AndCond(a, b) - def outerTest(testedBinder: Symbol, expectedTp: Type) = TrueCond // TODO OuterEqCond(testedBinder, expectedType) - def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T) - val p = binderToUniqueTree(b); AndCond(NonNullCond(p), TypeCond(p, uniqueTp(pt))) + abstract class TreeMakerToCond extends (TreeMaker => Cond) { + // requires(if (!substitutionComputed)) + def updateSubstitution(subst: Substitution): Unit = { + // find part of substitution that replaces bound symbols by new symbols, and reverse that part + // so that we don't introduce new aliases for existing symbols, thus keeping the set of bound symbols minimal + val (boundSubst, unboundSubst) = (subst.from zip subst.to) partition { + case (f, t) => + t.isInstanceOf[Ident] && (t.symbol ne NoSymbol) && pointsToBound(f) + } + val (boundFrom, boundTo) = boundSubst.unzip + val (unboundFrom, unboundTo) = unboundSubst.unzip + + // reverse substitution that would otherwise replace a variable we already encountered by a new variable + // NOTE: this forgets the more precise type we have for these later variables, but that's probably okay + normalize >>= Substitution(boundTo map (_.symbol), boundFrom map (CODE.REF(_))) + // patmatDebug ("normalize subst: "+ normalize) + + val okSubst = Substitution(unboundFrom, unboundTo map (normalize(_))) // it's important substitution does not duplicate trees here -- it helps to keep hash consing simple, anyway + pointsToBound ++= ((okSubst.from, okSubst.to).zipped filter { (f, t) => pointsToBound exists (sym => t.exists(_.symbol == sym)) })._1 + // patmatDebug("pointsToBound: "+ pointsToBound) + + accumSubst >>= okSubst + // patmatDebug("accumSubst: "+ accumSubst) + } + + def handleUnknown(tm: TreeMaker): Cond + + /** apply itself must render a faithful representation of the TreeMaker + * + * Concretely, TrueCond must only be used to represent a TreeMaker that is sure to match and that does not do any computation at all + * e.g., doCSE relies on apply itself being sound in this sense (since it drops TreeMakers that are approximated to TrueCond -- SI-6077) + * + * handleUnknown may be customized by the caller to approximate further + * + * TODO: don't ignore outer-checks + */ + def apply(tm: TreeMaker): Cond = { + if (!substitutionComputed) updateSubstitution(tm.substitution) + + tm match { + case ttm@TypeTestTreeMaker(prevBinder, testedBinder, pt, _) => + object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy { + type Result = Cond + def and(a: Result, b: Result) = AndCond(a, b) + def outerTest(testedBinder: Symbol, expectedTp: Type) = TrueCond // TODO OuterEqCond(testedBinder, expectedType) + def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T) + val p = binderToUniqueTree(b); AndCond(NonNullCond(p), TypeCond(p, uniqueTp(pt))) + } + def nonNullTest(testedBinder: Symbol) = NonNullCond(binderToUniqueTree(testedBinder)) + def equalsTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) + def eqTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) // TODO: eq, not == } - def nonNullTest(testedBinder: Symbol) = NonNullCond(binderToUniqueTree(testedBinder)) - def equalsTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) - def eqTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) // TODO: eq, not == - } - ttm.renderCondition(condStrategy) - case EqualityTestTreeMaker(prevBinder, patTree, _) => EqualityCond(binderToUniqueTree(prevBinder), unique(patTree)) - case AlternativesTreeMaker(_, altss, _) => \/(altss map (alts => /\(alts map (treeMakerToCond(_, handleUnknown, updateSubst))))) - case ProductExtractorTreeMaker(testedBinder, None, subst) => NonNullCond(binderToUniqueTree(testedBinder)) - case IrrefutableExtractorTreeMaker(_, _, _) => - // the extra condition is None, the extractor's result indicates it always succeeds, - // and the potential type-test for the argument is represented by a separate TypeTestTreeMaker - TrueCond - case GuardTreeMaker(guard) => - guard.tpe match { - case ConstantType(Constant(true)) => TrueCond - case ConstantType(Constant(false)) => FalseCond - case _ => handleUnknown(tm) - } - case p @ ExtractorTreeMaker(extractor, Some(lenCheck), testedBinder, _) => - p.checkedLength match { - // special-case: interpret pattern `List()` as `Nil` - // TODO: make it more general List(1, 2) => 1 :: 2 :: Nil -- not sure this is a good idea... - case Some(0) if rewriteNil && testedBinder.tpe.typeSymbol == ListClass => // extractor.symbol.owner == SeqFactory - EqualityCond(binderToUniqueTree(p.prevBinder), unique(Ident(NilModule) setType NilModule.tpe)) - case _ => handleUnknown(tm) - } - case SubstOnlyTreeMaker(_, _) => TrueCond - case ProductExtractorTreeMaker(_, Some(_), _) | - ExtractorTreeMaker(_, _, _, _) | BodyTreeMaker(_, _) => handleUnknown(tm) + ttm.renderCondition(condStrategy) + case EqualityTestTreeMaker(prevBinder, patTree, _) => EqualityCond(binderToUniqueTree(prevBinder), unique(patTree)) + case AlternativesTreeMaker(_, altss, _) => \/(altss map (alts => /\(alts map this))) + case ProductExtractorTreeMaker(testedBinder, None) => NonNullCond(binderToUniqueTree(testedBinder)) + case SubstOnlyTreeMaker(_, _) => TrueCond + case GuardTreeMaker(guard) => + guard.tpe match { + case ConstantType(Constant(true)) => TrueCond + case ConstantType(Constant(false)) => FalseCond + case _ => handleUnknown(tm) + } + case ExtractorTreeMaker(_, _, _) | + ProductExtractorTreeMaker(_, _) | + BodyTreeMaker(_, _) => handleUnknown(tm) + } } } - val constFalse = (_: TreeMaker) => FalseCond - val constTrue = (_: TreeMaker) => TrueCond - final def approximateMatch(cases: List[List[TreeMaker]], handleUnknown: TreeMaker => Cond = constFalse, rewriteNil: Boolean = false): List[List[Test]] = - cases.map { _ map (tm => Test(treeMakerToCond(tm, handleUnknown, updateSubst = true, rewriteNil), tm)) } + private val irrefutableExtractor: PartialFunction[TreeMaker, Cond] = { + // the extra condition is None, the extractor's result indicates it always succeeds, + // (the potential type-test for the argument is represented by a separate TypeTestTreeMaker) + case IrrefutableExtractorTreeMaker(_, _) => TrueCond + } - final def approximateMatchAgain(cases: List[List[TreeMaker]], handleUnknown: TreeMaker => Cond = constFalse, rewriteNil: Boolean = false): List[List[Test]] = - cases.map { _ map (tm => Test(treeMakerToCond(tm, handleUnknown, updateSubst = false, rewriteNil), tm)) } - } + // special-case: interpret pattern `List()` as `Nil` + // TODO: make it more general List(1, 2) => 1 :: 2 :: Nil -- not sure this is a good idea... + private val rewriteListPattern: PartialFunction[TreeMaker, Cond] = { + case p @ ExtractorTreeMaker(_, _, testedBinder) + if testedBinder.tpe.typeSymbol == ListClass && p.checkedLength == Some(0) => + EqualityCond(binderToUniqueTree(p.prevBinder), unique(Ident(NilModule) setType NilModule.tpe)) + } + val fullRewrite = (irrefutableExtractor orElse rewriteListPattern) + val refutableRewrite = irrefutableExtractor + @inline def onUnknown(handler: TreeMaker => Cond) = new TreeMakerToCond { + def handleUnknown(tm: TreeMaker) = handler(tm) + } + + // used for CSE -- rewrite all unknowns to False (the most conserative option) + object conservative extends TreeMakerToCond { + def handleUnknown(tm: TreeMaker) = FalseCond + } - def approximateMatch(root: Symbol, cases: List[List[TreeMaker]]): List[List[Test]] = { - object approximator extends TreeMakersToConds(root) - approximator.approximateMatch(cases) + final def approximateMatch(cases: List[List[TreeMaker]], treeMakerToCond: TreeMakerToCond = conservative) ={ + val testss = cases.map { _ map (tm => Test(treeMakerToCond(tm), tm)) } + substitutionComputed = true // a second call to approximateMatch should not re-compute the substitution (would be wrong) + testss + } } + def approximateMatchConservative(root: Symbol, cases: List[List[TreeMaker]]): List[List[Test]] = + (new TreeMakersToConds(root)).approximateMatch(cases) + def showTreeMakers(cases: List[List[TreeMaker]]) = { patmatDebug("treeMakers:") patmatDebug(alignAcrossRows(cases, ">>")) @@ -1800,6 +1818,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val todo = syms filterNot (b => (b.const == sym.const) || excludedPair(ExcludedPair(b.const, sym.const))) val (excluded, notExcluded) = todo partition (b => sym.const.excludes(b.const)) val implied = notExcluded filter (b => sym.const.implies(b.const)) + patmatDebug("eq axioms for: "+ sym.const) patmatDebug("excluded: "+ excluded) patmatDebug("implied: "+ implied) @@ -2354,11 +2373,15 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // use the same approximator so we share variables, // but need different conditions depending on whether we're conservatively looking for failure or success - val reachabilityApproximation = new TreeMakersToConds(prevBinder) - val testCasesOk = reachabilityApproximation.approximateMatch(cases, reachabilityApproximation.constTrue) - val testCasesFail = reachabilityApproximation.approximateMatchAgain(cases, reachabilityApproximation.constFalse) + // don't rewrite List-like patterns, as List() and Nil need to distinguished for unreachability + val approx = new TreeMakersToConds(prevBinder) + def approximate(default: Cond) = approx.approximateMatch(cases, approx.onUnknown { tm => + approx.refutableRewrite.applyOrElse(tm, (_: TreeMaker) => default ) + }) + + val testCasesOk = approximate(TrueCond) + val testCasesFail = approximate(FalseCond) - reachabilityApproximation.discard() prepareNewAnalysis() val propsCasesOk = testCasesOk map (t => symbolicCase(t, modelNull = true)) @@ -2495,19 +2518,19 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val start = Statistics.startTimer(patmatAnaExhaust) var backoff = false - val exhaustivityApproximation = new TreeMakersToConds(prevBinder) - val tests = exhaustivityApproximation.approximateMatch(cases, { - case BodyTreeMaker(_, _) => TrueCond // will be discarded by symbolCase later - case tm => - // patmatDebug("backing off due to "+ tm) + val approx = new TreeMakersToConds(prevBinder) + val tests = approx.approximateMatch(cases, approx.onUnknown { tm => + approx.fullRewrite.applyOrElse[TreeMaker, Cond](tm, { + case BodyTreeMaker(_, _) => TrueCond // irrelevant -- will be discarded by symbolCase later + case _ => // patmatDebug("backing off due to "+ tm) backoff = true FalseCond - }, rewriteNil = true) + }) + }) if (backoff) Nil else { - val prevBinderTree = exhaustivityApproximation.binderToUniqueTree(prevBinder) + val prevBinderTree = approx.binderToUniqueTree(prevBinder) - exhaustivityApproximation.discard() prepareNewAnalysis() val symbolicCases = tests map (symbolicCase(_, modelNull = false)) @@ -2774,7 +2797,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL patmatDebug("before CSE:") showTreeMakers(cases) - val testss = approximateMatch(prevBinder, cases) + val testss = approximateMatchConservative(prevBinder, cases) // interpret: val dependencies = new collection.mutable.LinkedHashMap[Test, Set[Cond]] @@ -2784,10 +2807,10 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val cond = test.cond def simplify(c: Cond): Set[Cond] = c match { - case AndCond(a, b) => simplify(a) ++ simplify(b) + case AndCond(a, b) => simplify(a) ++ simplify(b) case OrCond(_, _) => Set(FalseCond) // TODO: make more precise case NonNullCond(_) => Set(TrueCond) // not worth remembering - case _ => Set(c) + case _ => Set(c) } val conds = simplify(cond) @@ -2802,7 +2825,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case (priorTest, deps) => ((simplify(priorTest.cond) == nonTrivial) || // our conditions are implied by priorTest if it checks the same thing directly (nonTrivial subsetOf deps) // or if it depends on a superset of our conditions - ) && (deps subsetOf tested) // the conditions we've tested when we are here in the match satisfy the prior test, and hence what it tested + ) && (deps subsetOf tested) // the conditions we've tested when we are here in the match satisfy the prior test, and hence what it tested } foreach { case (priorTest, _) => // if so, note the dependency in both tests diff --git a/test/files/run/t6077_patmat_cse_irrefutable.check b/test/files/run/t6077_patmat_cse_irrefutable.check new file mode 100644 index 0000000000..9766475a41 --- /dev/null +++ b/test/files/run/t6077_patmat_cse_irrefutable.check @@ -0,0 +1 @@ +ok diff --git a/test/files/run/t6077_patmat_cse_irrefutable.scala b/test/files/run/t6077_patmat_cse_irrefutable.scala new file mode 100644 index 0000000000..b130ae7813 --- /dev/null +++ b/test/files/run/t6077_patmat_cse_irrefutable.scala @@ -0,0 +1,13 @@ +class LiteralNode(val value: Any) + +object LiteralNode { + // irrefutable + def unapply(n: LiteralNode) = Some(n.value) +} + +object Test extends App { + ((new LiteralNode(false)): Any) match { + case LiteralNode(true) => println("uh-oh") + case LiteralNode(false) => println("ok") + } +} \ No newline at end of file -- cgit v1.2.3 From 93751072ef9ce684a5a8282bbf619f62736c30f4 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 14 Jul 2012 10:56:35 +0200 Subject: With -Yno-generic-signatures, exit GenASM.getGenericSignature early This allows using -Yno-generic-signatures as a workaround for SI-4365. --- src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala | 3 ++- src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 9a2cd5259c..756d90bc53 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -850,7 +850,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { // without it. This is particularly bad because the availability of // generic information could disappear as a consequence of a seemingly // unrelated change. - sym.isHidden + settings.Ynogenericsig.value + || sym.isHidden || sym.isLiftedMethod || sym.isBridge || (sym.ownerChain exists (_.isImplClass)) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 9661ae6b3e..912a5b0e90 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -727,7 +727,8 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with // without it. This is particularly bad because the availability of // generic information could disappear as a consequence of a seemingly // unrelated change. - sym.isHidden + settings.Ynogenericsig.value + || sym.isHidden || sym.isLiftedMethod || sym.isBridge || (sym.ownerChain exists (_.isImplClass)) -- cgit v1.2.3 From 32dc7e80698c83947bf4b74f6eadd385a06f5b09 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 7 Jul 2012 13:18:44 +0200 Subject: A test case that scrutinises lambdalifter's output. No doubt there are plenty of additional variations that could be added to exercise more code paths. This is the unflattering "before" photo; the next commit will make over the name mangling and reveal the simple beauty of unmangled names. References SI-6028 --- test/files/run/t6028.check | 84 ++++++++++++++++++++++++++++++++++++++++++++++ test/files/run/t6028.scala | 22 ++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 test/files/run/t6028.check create mode 100644 test/files/run/t6028.scala diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check new file mode 100644 index 0000000000..6c9930b503 --- /dev/null +++ b/test/files/run/t6028.check @@ -0,0 +1,84 @@ +[[syntax trees at end of lambdalift]] // newSource1 +package { + class T extends Object { + val T$$classParam: Int = _; + def (classParam: Int): T = { + T.super.(); + () + }; + private[this] val field: Int = 0; + def field(): Int = T.this.field; + def foo(methodParam$1: Int): Function0 = { + val methodLocal$1: Int = 0; + { + (new anonymous class $anonfun$foo$1(T.this, methodParam$1, methodLocal$1): Function0) + } + }; + def bar(barParam$1: Int): Object = { + @volatile var MethodLocalObject$module$1: scala.runtime.VolatileObjectRef = new scala.runtime.VolatileObjectRef(); + T.this.MethodLocalObject$1(barParam$1, MethodLocalObject$module$1) + }; + def tryy(tryyParam$1: Int): Function0 = { + var tryyLocal$1: scala.runtime.IntRef = new scala.runtime.IntRef(0); + { + (new anonymous class $anonfun$tryy$1(T.this, tryyParam$1, tryyLocal$1): Function0) + } + }; + @SerialVersionUID(0) final class $anonfun$foo$1 extends scala.runtime.AbstractFunction0$mcI$sp with Serializable { + def ($outer: T, methodParam$1: Int, methodLocal$1: Int): anonymous class $anonfun$foo$1 = { + $anonfun$foo$1.super.(); + () + }; + final def apply(): Int = $anonfun$foo$1.this.apply$mcI$sp(); + def apply$mcI$sp(): Int = $anonfun$foo$1.this.$outer.T$$classParam.+($anonfun$foo$1.this.$outer.field()).+($anonfun$foo$1.this.methodParam$1).+($anonfun$foo$1.this.methodLocal$1); + private[this] val $outer: T = _; + def T$$anonfun$$$outer(): T = $anonfun$foo$1.this.$outer; + final def apply(): Object = scala.Int.box($anonfun$foo$1.this.apply()); + private[this] val methodParam$1: Int = _; + private[this] val methodLocal$1: Int = _ + }; + abstract trait MethodLocalTrait$1 extends Object { + def T$MethodLocalTrait$$$outer(): T + }; + object MethodLocalObject$2 extends Object with T#MethodLocalTrait$1 { + def ($outer: T, barParam$1: Int): ... = { + MethodLocalObject$2.super.(); + MethodLocalObject$2.this.$asInstanceOf[T#MethodLocalTrait$1$class]()./*MethodLocalTrait$1$class*/$init$(barParam$1); + () + }; + private[this] val $outer: T = _; + def T$MethodLocalObject$$$outer(): T = MethodLocalObject$2.this.$outer; + def T$MethodLocalTrait$$$outer(): T = MethodLocalObject$2.this.$outer + }; + final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: scala.runtime.VolatileObjectRef): ... = { + MethodLocalObject$module$1.elem = new ...(T.this, barParam$1); + MethodLocalObject$module$1.elem.$asInstanceOf[...]() + }; + abstract trait MethodLocalTrait$1$class extends Object with T#MethodLocalTrait$1 { + def /*MethodLocalTrait$1$class*/$init$(barParam$1: Int): Unit = { + () + }; + scala.this.Predef.print(scala.Int.box(barParam$1)) + }; + @SerialVersionUID(0) final class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { + def ($outer: T, tryyParam$1: Int, tryyLocal$1: scala.runtime.IntRef): anonymous class $anonfun$tryy$1 = { + $anonfun$tryy$1.super.(); + () + }; + final def apply(): Unit = $anonfun$tryy$1.this.apply$mcV$sp(); + def apply$mcV$sp(): Unit = try { + $anonfun$tryy$1.this.tryyLocal$1.elem = $anonfun$tryy$1.this.tryyParam$1 + } finally (); + private[this] val $outer: T = _; + def T$$anonfun$$$outer(): T = $anonfun$tryy$1.this.$outer; + final def apply(): Object = { + $anonfun$tryy$1.this.apply(); + scala.runtime.BoxedUnit.UNIT + }; + private[this] val tryyParam$1: Int = _; + private[this] val tryyLocal$1: scala.runtime.IntRef = _ + } + } +} + +warning: there were 1 feature warnings; re-run with -feature for details diff --git a/test/files/run/t6028.scala b/test/files/run/t6028.scala new file mode 100644 index 0000000000..7611aee0fc --- /dev/null +++ b/test/files/run/t6028.scala @@ -0,0 +1,22 @@ +import scala.tools.partest._ +import java.io.{Console => _, _} + +object Test extends DirectTest { + + override def extraSettings: String = "-usejavacp -Xprint:lambdalift -d " + testOutput.path + + override def code = """class T(classParam: Int) { + | val field: Int = 0 + | def foo(methodParam: Int) = {val methodLocal = 0 ; () => classParam + field + methodParam + methodLocal } + | def bar(barParam: Int) = { trait MethodLocalTrait { print(barParam) }; object MethodLocalObject extends MethodLocalTrait; MethodLocalObject } + | def tryy(tryyParam: Int) = { var tryyLocal = 0; () => try { tryyLocal = tryyParam } finally () } + |} + |""".stripMargin.trim + + override def show(): Unit = { + // redirect err to out, for logging + Console.withErr(System.out) { + compile() + } + } +} -- cgit v1.2.3 From a3bf34563d718f19ad02ff9ac5a2a1cec865aa24 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 7 Jul 2012 09:24:50 +0200 Subject: SI-6028 Avoid needless symbol renaming in lambdalift. Preserve names of all referenced free vars. Only the proxy symbols have the fresh names. The resulting natural beauty is evident in the diff of t6028.check. This subsumes the treatment in 0e170e4b that ensured named parameter calls cannot see mangled names; pos/t6028 confirms as much. --- .../scala/tools/nsc/transform/LambdaLift.scala | 85 ++++++++++++++-------- test/files/pos/t6028/t6028_1.scala | 3 + test/files/pos/t6028/t6028_2.scala | 4 + test/files/run/t6028.check | 18 ++--- test/files/run/t6028.scala | 1 - 5 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 test/files/pos/t6028/t6028_1.scala create mode 100644 test/files/pos/t6028/t6028_2.scala diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index fad41ae98d..bee5aa5f4f 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -56,6 +56,31 @@ abstract class LambdaLift extends InfoTransform { /** The set of symbols that need to be renamed. */ private val renamable = newSymSet + /** + * The new names for free variables proxies. If we simply renamed the + * free variables, we would transform: + * {{{ + * def closure(x: Int) = { () => x } + * }}} + * + * To: + * {{{ + * def closure(x$1: Int) = new anonFun$1(this, x$1) + * class anonFun$1(outer$: Outer, x$1: Int) { def apply() => x$1 } + * }}} + * + * This is fatally bad for named arguments (0e170e4b), extremely impolite to tools + * reflecting on the method parameter names in the generated bytecode (SI-6028), + * and needlessly bothersome to anyone using a debugger. + * + * Instead, we transform to: + * {{{ + * def closure(x: Int) = new anonFun$1(this, x) + * class anonFun$1(outer$: Outer, x$1: Int) { def apply() => x$1 } + * }}} + */ + private val proxyNames = mutable.HashMap[Symbol, Name]() + // (trait, name) -> owner private val localTraits = mutable.HashMap[(Symbol, Name), Symbol]() // (owner, name) -> implClass @@ -117,15 +142,6 @@ abstract class LambdaLift extends InfoTransform { if (!ss(sym)) { ss addEntry sym renamable addEntry sym - beforePickler { - // The param symbol in the MethodType should not be renamed, only the symbol in scope. This way, - // parameter names for named arguments are not changed. Example: without cloning the MethodType, - // def closure(x: Int) = { () => x } - // would have the signature - // closure: (x$1: Int)() => Int - if (sym.isParameter && sym.owner.info.paramss.exists(_ contains sym)) - sym.owner modifyInfo (_ cloneInfo sym.owner) - } changedFreeVars = true debuglog("" + sym + " is free in " + enclosure); if (sym.isVariable) sym setFlag CAPTURED @@ -214,25 +230,27 @@ abstract class LambdaLift extends InfoTransform { } while (changedFreeVars) def renameSym(sym: Symbol) { + val originalName = sym.name + sym setName newName(sym) + debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) + } + + def newName(sym: Symbol): Name = { val originalName = sym.name def freshen(prefix: String): Name = if (originalName.isTypeName) unit.freshTypeName(prefix) else unit.freshTermName(prefix) - val newName: Name = ( - if (sym.isAnonymousFunction && sym.owner.isMethod) { - freshen(sym.name + nme.NAME_JOIN_STRING + sym.owner.name + nme.NAME_JOIN_STRING) - } else { - // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) - // Generating a a unique name, mangled with the enclosing class name, avoids a VerifyError - // in the case that a sub-class happens to lifts out a method with the *same* name. - val name = freshen(sym.name + nme.NAME_JOIN_STRING) - if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) nme.expandedName(name, sym.enclClass) - else name - } - ) - sym setName newName - debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) + if (sym.isAnonymousFunction && sym.owner.isMethod) { + freshen(sym.name + nme.NAME_JOIN_STRING + sym.owner.name + nme.NAME_JOIN_STRING) + } else { + // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) + // Generating a a unique name, mangled with the enclosing class name, avoids a VerifyError + // in the case that a sub-class happens to lifts out a method with the *same* name. + val name = freshen(sym.name + nme.NAME_JOIN_STRING) + if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) nme.expandedName(name, sym.enclClass) + else name + } } /** Rename a trait's interface and implementation class in coordinated fashion. @@ -245,6 +263,8 @@ abstract class LambdaLift extends InfoTransform { debuglog("renaming impl class in step with %s: %s => %s".format(traitSym, originalImplName, implSym.name)) } + val allFree: Set[Symbol] = free.values.flatMap(_.iterator).toSet + for (sym <- renamable) { // If we renamed a trait from Foo to Foo$1, we must rename the implementation // class from Foo$class to Foo$1$class. (Without special consideration it would @@ -252,7 +272,9 @@ abstract class LambdaLift extends InfoTransform { // under us, and there's no reliable link between trait symbol and impl symbol, // we have maps from ((trait, name)) -> owner and ((owner, name)) -> impl. localTraits remove ((sym, sym.name)) match { - case None => renameSym(sym) + case None => + if (allFree(sym)) proxyNames(sym) = newName(sym) + else renameSym(sym) case Some(owner) => localImplClasses remove ((owner, sym.name)) match { case Some(implSym) => renameTrait(sym, implSym) @@ -267,7 +289,8 @@ abstract class LambdaLift extends InfoTransform { debuglog("free var proxy: %s, %s".format(owner.fullLocationString, freeValues.toList.mkString(", "))) proxies(owner) = for (fv <- freeValues.toList) yield { - val proxy = owner.newValue(fv.name, owner.pos, newFlags) setInfo fv.info + val proxyName = proxyNames.getOrElse(fv, fv.name) + val proxy = owner.newValue(proxyName, owner.pos, newFlags) setInfo fv.info if (owner.isClass) owner.info.decls enter proxy proxy } @@ -280,9 +303,9 @@ abstract class LambdaLift extends InfoTransform { if (enclosure eq NoSymbol) throw new IllegalArgumentException("Could not find proxy for "+ sym.defString +" in "+ sym.ownerChain +" (currentOwner= "+ currentOwner +" )") debuglog("searching for " + sym + "(" + sym.owner + ") in " + enclosure + " " + enclosure.logicallyEnclosingMember) - val ps = (proxies get enclosure.logicallyEnclosingMember).toList.flatten filter (_.name == sym.name) - if (ps.isEmpty) searchIn(enclosure.skipConstructor.owner) - else ps.head + val proxyName = proxyNames.getOrElse(sym, sym.name) + val ps = (proxies get enclosure.logicallyEnclosingMember).toList.flatten find (_.name == proxyName) + ps getOrElse searchIn(enclosure.skipConstructor.owner) } debuglog("proxy %s from %s has logical enclosure %s".format( sym.debugLocationString, @@ -501,8 +524,10 @@ abstract class LambdaLift extends InfoTransform { } override def transformUnit(unit: CompilationUnit) { - computeFreeVars - afterOwnPhase(super.transformUnit(unit)) + computeFreeVars() + afterOwnPhase { + super.transformUnit(unit) + } assert(liftedDefs.isEmpty, liftedDefs.keys mkString ", ") } } // class LambdaLifter diff --git a/test/files/pos/t6028/t6028_1.scala b/test/files/pos/t6028/t6028_1.scala new file mode 100644 index 0000000000..6edb76069e --- /dev/null +++ b/test/files/pos/t6028/t6028_1.scala @@ -0,0 +1,3 @@ +class C { + def foo(a: Int): Unit = () => a +} diff --git a/test/files/pos/t6028/t6028_2.scala b/test/files/pos/t6028/t6028_2.scala new file mode 100644 index 0000000000..f44048c0ab --- /dev/null +++ b/test/files/pos/t6028/t6028_2.scala @@ -0,0 +1,4 @@ +object Test { + // ensure that parameter names are untouched by lambdalift + new C().foo(a = 0) +} diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check index 6c9930b503..dca61115ad 100644 --- a/test/files/run/t6028.check +++ b/test/files/run/t6028.check @@ -8,20 +8,20 @@ package { }; private[this] val field: Int = 0; def field(): Int = T.this.field; - def foo(methodParam$1: Int): Function0 = { - val methodLocal$1: Int = 0; + def foo(methodParam: Int): Function0 = { + val methodLocal: Int = 0; { - (new anonymous class $anonfun$foo$1(T.this, methodParam$1, methodLocal$1): Function0) + (new anonymous class $anonfun$foo$1(T.this, methodParam, methodLocal): Function0) } }; - def bar(barParam$1: Int): Object = { - @volatile var MethodLocalObject$module$1: scala.runtime.VolatileObjectRef = new scala.runtime.VolatileObjectRef(); - T.this.MethodLocalObject$1(barParam$1, MethodLocalObject$module$1) + def bar(barParam: Int): Object = { + @volatile var MethodLocalObject$module: scala.runtime.VolatileObjectRef = new scala.runtime.VolatileObjectRef(); + T.this.MethodLocalObject$1(barParam, MethodLocalObject$module) }; - def tryy(tryyParam$1: Int): Function0 = { - var tryyLocal$1: scala.runtime.IntRef = new scala.runtime.IntRef(0); + def tryy(tryyParam: Int): Function0 = { + var tryyLocal: scala.runtime.IntRef = new scala.runtime.IntRef(0); { - (new anonymous class $anonfun$tryy$1(T.this, tryyParam$1, tryyLocal$1): Function0) + (new anonymous class $anonfun$tryy$1(T.this, tryyParam, tryyLocal): Function0) } }; @SerialVersionUID(0) final class $anonfun$foo$1 extends scala.runtime.AbstractFunction0$mcI$sp with Serializable { diff --git a/test/files/run/t6028.scala b/test/files/run/t6028.scala index 7611aee0fc..cab17535fc 100644 --- a/test/files/run/t6028.scala +++ b/test/files/run/t6028.scala @@ -14,7 +14,6 @@ object Test extends DirectTest { |""".stripMargin.trim override def show(): Unit = { - // redirect err to out, for logging Console.withErr(System.out) { compile() } -- cgit v1.2.3 From 65eb59134517ccf6509ef988954965adab550d5a Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 14 Jul 2012 11:14:53 +0200 Subject: updated intellij project files --- src/intellij/README | 11 ++++++----- src/intellij/actors.iml.SAMPLE | 4 ++-- src/intellij/asm.iml.SAMPLE | 12 ++++++++++++ src/intellij/compiler.iml.SAMPLE | 8 ++++++-- src/intellij/fjbg.iml.SAMPLE | 12 ++++++++++++ src/intellij/forkjoin.iml.SAMPLE | 12 ++++++++++++ src/intellij/library.iml.SAMPLE | 6 +++--- src/intellij/manual.iml.SAMPLE | 2 +- src/intellij/msil.iml.SAMPLE | 24 ++++++++++++++++++++++++ src/intellij/partest.iml.SAMPLE | 6 +++--- src/intellij/reflect.iml.SAMPLE | 6 +++--- src/intellij/scala-lang.ipr.SAMPLE | 30 +++++++++++++----------------- src/intellij/scalap.iml.SAMPLE | 3 ++- src/intellij/swing.iml.SAMPLE | 2 +- src/intellij/test.iml.SAMPLE | 10 +++++++--- 15 files changed, 107 insertions(+), 41 deletions(-) create mode 100644 src/intellij/asm.iml.SAMPLE create mode 100644 src/intellij/fjbg.iml.SAMPLE create mode 100644 src/intellij/forkjoin.iml.SAMPLE create mode 100644 src/intellij/msil.iml.SAMPLE diff --git a/src/intellij/README b/src/intellij/README index f24d700889..9ef612bd0a 100644 --- a/src/intellij/README +++ b/src/intellij/README @@ -1,12 +1,13 @@ -Use IntelliJ IDEA X EAP (http://confluence.jetbrains.net/display/IDEADEV/IDEA+X+EAP) -a Scala Plugin nightly build (http://confluence.jetbrains.net/display/SCA/Scala+Plugin+Nightly+Builds+for+IDEA+X) +Use the latest IntelliJ IDEA release and install the Scala plugin from within the IDE. The following steps are required to use IntelliJ IDEA on Scala trunk + - compile "locker" using "ant locker.done" - Copy the *.iml.SAMPLE / *.ipr.SAMPLE files to *.iml / *.ipr - In IDEA, create a global library named "ant" which contains "ant.jar" - - In the Scala Facet of the "library" module, update the path in the command-line - argument for "-sourcepath" + - Also create an SDK entry named "1.6" containing the java 1.6 SDK + - In the Scala Facet of the "library" and "reflect" modules, update the path in the + command-line argument for "-sourcepath" - In the Project Settings, update the "Version Control" to match your checkout Known problems - - Currently, it's not possible to build the "actors" module in IDEA + - Due to SI-4365, the "library" module has to be built using "-Yno-generic-signatures" diff --git a/src/intellij/actors.iml.SAMPLE b/src/intellij/actors.iml.SAMPLE index b095d29d7b..896c4966ff 100644 --- a/src/intellij/actors.iml.SAMPLE +++ b/src/intellij/actors.iml.SAMPLE @@ -6,7 +6,7 @@