From 1a8daa2d6cbf46a7cdd9180c11c641adabcf6d92 Mon Sep 17 00:00:00 2001 From: Simon Ochsenreither Date: Wed, 16 Sep 2015 17:31:02 +0200 Subject: Remove GenASM, merge remaining common code snippets With GenBCode being the default and only supported backend for Java 8, we can get rid of GenASM. This commit also fixes/migrates/moves to pending/deletes tests which depended on GenASM before. --- src/compiler/scala/tools/nsc/Global.scala | 8 - .../scala/tools/nsc/backend/JavaPlatform.scala | 8 +- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 476 --- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 501 ++- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 1 - .../scala/tools/nsc/backend/jvm/BTypes.scala | 6 +- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 97 +- .../scala/tools/nsc/backend/jvm/GenASM.scala | 3355 -------------------- .../scala/tools/nsc/settings/ScalaSettings.scala | 4 +- .../instrumented/inline-in-constructors.flags | 2 +- test/files/jvm/bytecode-test-example/Foo_1.flags | 1 - test/files/jvm/constant-optimization/Foo_1.flags | 1 - test/files/jvm/constant-optimization/Foo_1.scala | 9 - test/files/jvm/constant-optimization/Test.scala | 27 - test/files/jvm/nooptimise/Foo_1.flags | 1 - test/files/jvm/nooptimise/Foo_1.scala | 8 - test/files/jvm/nooptimise/Test.scala | 23 - test/files/jvm/patmat_opt_ignore_underscore.check | 1 - test/files/jvm/patmat_opt_ignore_underscore.flags | 1 - .../patmat_opt_ignore_underscore/Analyzed_1.scala | 29 - .../jvm/patmat_opt_ignore_underscore/test.scala | 18 - test/files/jvm/patmat_opt_no_nullcheck.check | 1 - test/files/jvm/patmat_opt_no_nullcheck.flags | 1 - .../jvm/patmat_opt_no_nullcheck/Analyzed_1.scala | 24 - test/files/jvm/patmat_opt_no_nullcheck/test.scala | 11 - test/files/jvm/patmat_opt_primitive_typetest.check | 1 - test/files/jvm/patmat_opt_primitive_typetest.flags | 1 - .../patmat_opt_primitive_typetest/Analyzed_1.scala | 24 - .../jvm/patmat_opt_primitive_typetest/test.scala | 8 - test/files/jvm/t7006.check | 29 - test/files/jvm/t7006/Foo_1.flags | 1 - test/files/jvm/t7006/Foo_1.scala | 10 - test/files/jvm/t7006/Test.scala | 19 - test/files/neg/case-collision.check | 6 +- test/files/neg/case-collision.flags | 2 +- test/files/neg/sealed-final-neg.check | 11 +- test/files/neg/sealed-final-neg.flags | 2 +- test/files/neg/sealed-final-neg.scala | 2 - test/files/neg/t3234.check | 6 - test/files/neg/t3234.flags | 1 - test/files/neg/t3234.scala | 19 - test/files/pos/inline-access-levels.flags | 2 +- test/files/pos/inliner2.flags | 1 - test/files/pos/inliner2.scala | 57 - test/files/pos/sealed-final.flags | 1 - test/files/pos/sealed-final.scala | 14 - test/files/pos/t3234.flags | 1 + test/files/pos/t3234.scala | 17 + test/files/pos/t3420.flags | 2 +- test/files/pos/t8410.flags | 2 +- test/files/run/constant-optimization.flags | 1 - test/files/run/dead-code-elimination.flags | 1 - test/files/run/delambdafy-specialized.check | 1 - test/files/run/delambdafy-specialized.flags | 1 - test/files/run/delambdafy-specialized.scala | 6 - test/files/run/elidable-opt.flags | 2 +- test/files/run/finalvar.flags | 2 +- test/files/run/icode-reader-dead-code.scala | 2 +- test/files/run/inline-ex-handlers.check | 492 --- test/files/run/inline-ex-handlers.scala | 329 -- test/files/run/optimizer-array-load.flags | 1 - test/files/run/sbt-icode-interface.scala | 48 +- test/files/run/synchronized.flags | 2 +- test/files/run/t3509.flags | 2 +- test/files/run/t3569.flags | 2 +- test/files/run/t4285.flags | 2 +- test/files/run/t4935.flags | 2 +- test/files/run/t5313.check | 12 - test/files/run/t5313.scala | 54 - test/files/run/t5789.scala | 2 +- test/files/run/t6188.flags | 2 +- test/files/run/t6546.flags | 1 - test/files/run/t6546/A_1.scala | 6 - test/files/run/t6546/B_2.scala | 8 - test/files/run/t6955.scala | 36 - test/files/run/t6956.scala | 33 - test/files/run/t7008-scala-defined.flags | 1 - test/files/run/t7459b-optimize.flags | 2 +- test/files/run/t7582.flags | 2 +- test/files/run/t7582b.flags | 2 +- test/files/run/t8601-closure-elim.flags | 2 +- test/files/run/t8601-closure-elim.scala | 4 +- test/files/run/t8601.flags | 2 +- test/files/run/t8601b.flags | 2 +- test/files/run/t8601c.flags | 2 +- test/files/run/t8601d.flags | 2 +- test/files/run/t8601e.flags | 2 +- test/files/run/t9003.flags | 2 +- test/files/run/t9403.flags | 2 +- test/files/run/test-cpp.check | 81 - test/files/run/test-cpp.scala | 104 - test/pending/jvm/constant-optimization/Foo_1.flags | 1 + test/pending/jvm/constant-optimization/Foo_1.scala | 9 + test/pending/jvm/constant-optimization/Test.scala | 27 + .../pending/jvm/patmat_opt_ignore_underscore.check | 1 + .../pending/jvm/patmat_opt_ignore_underscore.flags | 1 + .../patmat_opt_ignore_underscore/Analyzed_1.scala | 29 + .../jvm/patmat_opt_ignore_underscore/test.scala | 18 + test/pending/jvm/patmat_opt_no_nullcheck.check | 1 + test/pending/jvm/patmat_opt_no_nullcheck.flags | 1 + .../jvm/patmat_opt_no_nullcheck/Analyzed_1.scala | 24 + .../pending/jvm/patmat_opt_no_nullcheck/test.scala | 14 + .../jvm/patmat_opt_primitive_typetest.check | 1 + .../jvm/patmat_opt_primitive_typetest.flags | 1 + .../patmat_opt_primitive_typetest/Analyzed_1.scala | 24 + .../jvm/patmat_opt_primitive_typetest/test.scala | 8 + test/pending/jvm/t7006.check | 29 + test/pending/jvm/t7006/Foo_1.flags | 1 + test/pending/jvm/t7006/Foo_1.scala | 10 + test/pending/jvm/t7006/Test.scala | 21 + test/pending/pos/inliner2.flags | 35 + test/pending/pos/inliner2.scala | 57 + test/pending/pos/sealed-final.flags | 41 + test/pending/pos/sealed-final.scala | 14 + test/pending/run/inline-ex-handlers.check | 491 +++ test/pending/run/inline-ex-handlers.scala | 329 ++ test/pending/run/t5313.check | 12 + test/pending/run/t5313.scala | 54 + test/pending/run/t6955.scala | 33 + test/pending/run/t6956.scala | 31 + test/pending/run/test-cpp.check | 81 + test/pending/run/test-cpp.scala | 104 + 122 files changed, 2180 insertions(+), 5438 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala delete mode 100644 src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala delete mode 100644 test/files/jvm/bytecode-test-example/Foo_1.flags delete mode 100644 test/files/jvm/constant-optimization/Foo_1.flags delete mode 100644 test/files/jvm/constant-optimization/Foo_1.scala delete mode 100644 test/files/jvm/constant-optimization/Test.scala delete mode 100644 test/files/jvm/nooptimise/Foo_1.flags delete mode 100644 test/files/jvm/nooptimise/Foo_1.scala delete mode 100644 test/files/jvm/nooptimise/Test.scala delete mode 100644 test/files/jvm/patmat_opt_ignore_underscore.check delete mode 100644 test/files/jvm/patmat_opt_ignore_underscore.flags delete mode 100644 test/files/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala delete mode 100644 test/files/jvm/patmat_opt_ignore_underscore/test.scala delete mode 100644 test/files/jvm/patmat_opt_no_nullcheck.check delete mode 100644 test/files/jvm/patmat_opt_no_nullcheck.flags delete mode 100644 test/files/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala delete mode 100644 test/files/jvm/patmat_opt_no_nullcheck/test.scala delete mode 100644 test/files/jvm/patmat_opt_primitive_typetest.check delete mode 100644 test/files/jvm/patmat_opt_primitive_typetest.flags delete mode 100644 test/files/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala delete mode 100644 test/files/jvm/patmat_opt_primitive_typetest/test.scala delete mode 100644 test/files/jvm/t7006.check delete mode 100644 test/files/jvm/t7006/Foo_1.flags delete mode 100644 test/files/jvm/t7006/Foo_1.scala delete mode 100644 test/files/jvm/t7006/Test.scala delete mode 100644 test/files/neg/t3234.check delete mode 100644 test/files/neg/t3234.flags delete mode 100644 test/files/neg/t3234.scala delete mode 100644 test/files/pos/inliner2.flags delete mode 100644 test/files/pos/inliner2.scala delete mode 100644 test/files/pos/sealed-final.flags delete mode 100644 test/files/pos/sealed-final.scala create mode 100644 test/files/pos/t3234.flags create mode 100644 test/files/pos/t3234.scala delete mode 100644 test/files/run/constant-optimization.flags delete mode 100644 test/files/run/dead-code-elimination.flags delete mode 100644 test/files/run/delambdafy-specialized.check delete mode 100644 test/files/run/delambdafy-specialized.flags delete mode 100644 test/files/run/delambdafy-specialized.scala delete mode 100644 test/files/run/inline-ex-handlers.check delete mode 100644 test/files/run/inline-ex-handlers.scala delete mode 100644 test/files/run/optimizer-array-load.flags delete mode 100644 test/files/run/t5313.check delete mode 100644 test/files/run/t5313.scala delete mode 100644 test/files/run/t6546.flags delete mode 100644 test/files/run/t6546/A_1.scala delete mode 100644 test/files/run/t6546/B_2.scala delete mode 100644 test/files/run/t6955.scala delete mode 100644 test/files/run/t6956.scala delete mode 100644 test/files/run/test-cpp.check delete mode 100644 test/files/run/test-cpp.scala create mode 100644 test/pending/jvm/constant-optimization/Foo_1.flags create mode 100644 test/pending/jvm/constant-optimization/Foo_1.scala create mode 100644 test/pending/jvm/constant-optimization/Test.scala create mode 100644 test/pending/jvm/patmat_opt_ignore_underscore.check create mode 100644 test/pending/jvm/patmat_opt_ignore_underscore.flags create mode 100644 test/pending/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala create mode 100644 test/pending/jvm/patmat_opt_ignore_underscore/test.scala create mode 100644 test/pending/jvm/patmat_opt_no_nullcheck.check create mode 100644 test/pending/jvm/patmat_opt_no_nullcheck.flags create mode 100644 test/pending/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala create mode 100644 test/pending/jvm/patmat_opt_no_nullcheck/test.scala create mode 100644 test/pending/jvm/patmat_opt_primitive_typetest.check create mode 100644 test/pending/jvm/patmat_opt_primitive_typetest.flags create mode 100644 test/pending/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala create mode 100644 test/pending/jvm/patmat_opt_primitive_typetest/test.scala create mode 100644 test/pending/jvm/t7006.check create mode 100644 test/pending/jvm/t7006/Foo_1.flags create mode 100644 test/pending/jvm/t7006/Foo_1.scala create mode 100644 test/pending/jvm/t7006/Test.scala create mode 100644 test/pending/pos/inliner2.flags create mode 100644 test/pending/pos/inliner2.scala create mode 100644 test/pending/pos/sealed-final.flags create mode 100644 test/pending/pos/sealed-final.scala create mode 100644 test/pending/run/inline-ex-handlers.check create mode 100644 test/pending/run/inline-ex-handlers.scala create mode 100644 test/pending/run/t5313.check create mode 100644 test/pending/run/t5313.scala create mode 100644 test/pending/run/t6955.scala create mode 100644 test/pending/run/t6956.scala create mode 100644 test/pending/run/test-cpp.check create mode 100644 test/pending/run/test-cpp.scala diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 422e2080f0..765cf15a93 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -28,7 +28,6 @@ import transform._ import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, JavaPlatform } import backend.jvm.GenBCode -import backend.jvm.GenASM import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, ClosureElimination, DeadCodeElimination } import backend.icode.analysis._ import scala.language.postfixOps @@ -633,13 +632,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = None } with DeadCodeElimination - // phaseName = "jvm", ASM-based version - object genASM extends { - val global: Global.this.type = Global.this - val runsAfter = List("dce") - val runsRightAfter = None - } with GenASM - // phaseName = "bcode" object genBCode extends { val global: Global.this.type = Global.this diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 6bd123c51f..16f086e9e7 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -40,13 +40,9 @@ trait JavaPlatform extends Platform { def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) = currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst)) - private def classEmitPhase = - if (settings.isBCodeActive) genBCode - else genASM - def platformPhases = List( - flatten, // get rid of inner classes - classEmitPhase // generate .class files + flatten, // get rid of inner classes + genBCode // generate .class files ) lazy val externalEquals = getDecl(BoxesRunTimeClass, nme.equals_) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala deleted file mode 100644 index 42738d3e1c..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ /dev/null @@ -1,476 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2014 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend.jvm - -import scala.tools.nsc.Global -import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo} -import BackendReporting.ClassSymbolInfoFailureSI9111 -import scala.tools.asm - -/** - * This trait contains code shared between GenBCode and GenASM that depends on types defined in - * the compiler cake (Global). - */ -final class BCodeAsmCommon[G <: Global](val global: G) { - import global._ - import definitions._ - - val ExcludedForwarderFlags = { - import scala.tools.nsc.symtab.Flags._ - // Should include DEFERRED but this breaks findMember. - SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO - } - - /** - * True for classes generated by the Scala compiler that are considered top-level in terms of - * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes. - */ - def considerAsTopLevelImplementationArtifact(classSym: Symbol) = { - classSym.isImplClass || classSym.isSpecialized - } - - /** - * Cache the value of delambdafy == "inline" for each run. We need to query this value many - * times, so caching makes sense. - */ - object delambdafyInline { - private var runId = -1 - private var value = false - - def apply(): Boolean = { - if (runId != global.currentRunId) { - runId = global.currentRunId - value = settings.Ydelambdafy.value == "inline" - } - value - } - } - - /** - * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a - * member class. This method is used to decide if we should emit an EnclosingMethod attribute. - * It is also used to decide whether the "owner" field in the InnerClass attribute should be - * null. - */ - def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { - assert(classSym.isClass, s"not a class: $classSym") - val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass - if (r && settings.Ybackend.value == "GenBCode") { - // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally - // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode - // we prevent this, see `nonAnon` in LambdaLift. - // phase travel necessary: after flatten, the name includes the name of outer classes. - // if some outer name contains $lambda, a non-lambda class is considered lambda. - assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name) - } - r - } - - /** - * The next enclosing definition in the source structure. Includes anonymous function classes - * under delambdafy:inline, even though they are only generated during UnCurry. - */ - def nextEnclosing(sym: Symbol): Symbol = { - val origOwner = sym.originalOwner - // phase travel necessary: after flatten, the name includes the name of outer classes. - // if some outer name contains $anon, a non-anon class is considered anon. - if (delambdafyInline() && sym.rawowner.isAnonymousFunction) { - // SI-9105: special handling for anonymous functions under delambdafy:inline. - // - // class C { def t = () => { def f { class Z } } } - // - // class C { def t = byNameMethod { def f { class Z } } } - // - // In both examples, the method f lambda-lifted into the anonfun class. - // - // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun. - // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ... - // - // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!) - // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!). - // - // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if` - // above makes sure we don't jump over the anonymous function in the by-name argument case. - // - // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f - // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym` - // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and - // we need to return it. - // If the rawowners are different, the symbol was not in between. In the first example, the - // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing - // of `f` is its rawowner, the anonFunClassSym. - // - // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C, - // not into the anonymous function class. The originalOwner chain is Z - f - C. - if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner - else sym.rawowner - } else { - origOwner - } - } - - def nextEnclosingClass(sym: Symbol): Symbol = { - if (sym.isClass) sym - else nextEnclosingClass(nextEnclosing(sym)) - } - - def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={ - nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass - } - - /** - * Returns the enclosing method for non-member classes. In the following example - * - * class A { - * def f = { - * class B { - * class C - * } - * } - * } - * - * the method returns Some(f) for B, but None for C, because C is a member class. For non-member - * classes that are not enclosed by a method, it returns None: - * - * class A { - * { class B } - * } - * - * In this case, for B, we return None. - * - * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes). - * This is a source-level property, so we need to use the originalOwner chain to reconstruct it. - */ - private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { - assert(classSym.isClass, classSym) - - def doesNotExist(method: Symbol) = { - // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. - // (2) Value classes. Member methods of value classes exist in the generated box class. However, - // nested methods lifted into a value class are moved to the companion object and don't exist - // in the value class itself. We can identify such nested methods: the initial enclosing class - // is a value class, but the current owner is some other class (the module class). - method.owner.isTrait && method.isImplOnly || { // (1) - val enclCls = nextEnclosingClass(method) - exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2) - } - } - - def enclosingMethod(sym: Symbol): Option[Symbol] = { - if (sym.isClass || sym == NoSymbol) None - else if (sym.isMethod) { - if (doesNotExist(sym)) None else Some(sym) - } - else enclosingMethod(nextEnclosing(sym)) - } - enclosingMethod(nextEnclosing(classSym)) - } - - /** - * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level - * property, this method looks at the originalOwner chain. See doc in BTypes. - */ - private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { - assert(classSym.isClass, classSym) - val r = nextEnclosingClass(nextEnclosing(classSym)) - // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release - if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r") - r - } - - final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) - - /** - * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not - * an anonymous or local class). See doc in BTypes. - * - * The class is parametrized by two functions to obtain a bytecode class descriptor for a class - * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend - * on the implementation of GenASM / GenBCode, so they need to be passed in. - */ - def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { - // trait impl classes are always top-level, see comment in BTypes - if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { - val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym) - val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match { - case some @ Some(m) => - if (m.owner != enclosingClass) { - // This should never happen. In case it does, it prevents emitting an invalid - // EnclosingMethod attribute: if the attribute specifies an enclosing method, - // it needs to exist in the specified enclosing class. - devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass") - None - } else some - case none => none - } - Some(EnclosingMethodEntry( - classDesc(enclosingClass), - methodOpt.map(_.javaSimpleName.toString).orNull, - methodOpt.map(methodDesc).orNull)) - } else { - None - } - } - - /** - * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. - * - * The problem is that we are interested in a source-level property. Various phases changed the - * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. - * Therefore, `sym.isStatic` is not what we want. For example, in - * object T { def f { object U } } - * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. - */ - def isOriginallyStaticOwner(sym: Symbol): Boolean = { - sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) - } - - /** - * Reconstruct the classfile flags from a Java defined class symbol. - * - * The implementation of this method is slightly different that `javaFlags` in BTypesFromSymbols. - * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags - * that are used in the generated classfiles. For example, all classes emitted by the Scala - * compiler have ACC_PUBLIC. - * - * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have - * to correspond exactly to the flags in the classfile. For example, if the class is package - * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the - * ClassBType. For example, the inliner needs the correct flags for access checks. - * - * Class flags are listed here: - * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1 - */ - def javaClassfileFlags(classSym: Symbol): Int = { - assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}") - import asm.Opcodes._ - def enumFlags = ACC_ENUM | { - // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method. - // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all - // Java enums for exhaustiveness checking. - val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred) - if (hasAbstractMethod) ACC_ABSTRACT else 0 - } - GenBCode.mkFlags( - // SI-9393: the classfile / java source parser make java annotation symbols look like classes. - // here we recover the actual classfile flags. - if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0, - if (classSym.isPublic) ACC_PUBLIC else 0, - if (classSym.isFinal) ACC_FINAL else 0, - // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces. - if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, - // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags) - if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (classSym.isArtifact) ACC_SYNTHETIC else 0, - if (classSym.hasJavaEnumFlag) enumFlags else 0 - ) - } - - /** - * The member classes of a class symbol. Note that the result of this method depends on the - * current phase, for example, after lambdalift, all local classes become member of the enclosing - * class. - * - * Impl classes are always considered top-level, see comment in BTypes. - */ - def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ - case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) => - sym - case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin - val r = exitingPickler(sym.moduleClass) - assert(r != NoSymbol, sym.fullLocationString) - r - })(collection.breakOut) - - lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule - lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE")) - lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) - lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) - - /** Whether an annotation should be emitted as a Java annotation - * .initialize: if 'annot' is read from pickle, atp might be uninitialized - */ - def shouldEmitAnnotation(annot: AnnotationInfo) = { - annot.symbol.initialize.isJavaDefined && - annot.matches(ClassfileAnnotationClass) && - retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue && - annot.args.isEmpty - } - - def isRuntimeVisible(annot: AnnotationInfo): Boolean = { - annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match { - case Some(retentionAnnot) => - retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue))) - case _ => - // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the - // annotation is emitted with visibility `RUNTIME` - true - } - } - - private def retentionPolicyOf(annot: AnnotationInfo): Symbol = - annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc => - assoc.collectFirst { - case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value - }).getOrElse(AnnotationRetentionPolicyClassValue) - - def implementedInterfaces(classSym: Symbol): List[Symbol] = { - // Additional interface parents based on annotations and other cues - def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match { - case RemoteAttr => Some(RemoteInterfaceClass.tpe) - case _ => None - } - - // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes. - def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag - - val classParents = { - val parents = classSym.info.parents - // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the - // parents of a java annotations. undo this for the backend (where we need classfile-level information). - if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass) - else parents - } - - val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation) - - // We keep the superClass when computing minimizeParents to eliminate more interfaces. - // Example: T can be eliminated from D - // trait T - // class C extends T - // class D extends C with T - val interfaces = erasure.minimizeParents(allParents) match { - case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) => - ifs - case ifs => - // minimizeParents removes the superclass if it's redundant, for example: - // trait A - // class C extends Object with A // minimizeParents removes Object - ifs - } - interfaces.map(_.typeSymbol) - } - - /** - * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We - * cannot change the typer context of the completer at this point and make it silent: the context - * captured when creating the completer in the namer. However, we can temporarily replace - * global.reporter (it's a var) to store errors. - */ - def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = { - if (sym.hasCompleteInfo) false - else { - val originalReporter = global.reporter - val storeReporter = new reporters.StoreReporter() - global.reporter = storeReporter - try { - sym.info - } finally { - global.reporter = originalReporter - } - sym.isErroneous - } - } - - /** - * Build the [[InlineInfo]] for a class symbol. - */ - def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = { - val traitSelfType = if (classSym.isTrait && !classSym.isImplClass) { - // The mixin phase uses typeOfThis for the self parameter in implementation class methods. - val selfSym = classSym.typeOfThis.typeSymbol - if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None - } else { - None - } - - val isEffectivelyFinal = classSym.isEffectivelyFinal - - val sam = { - if (classSym.isImplClass || classSym.isEffectivelyFinal) None - else { - // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an - // empty parameter list in later phases and would therefore be picked as SAM. - val samSym = exitingPickler(definitions.findSam(classSym.tpe)) - if (samSym == NoSymbol) None - else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) - } - } - - var warning = Option.empty[ClassSymbolInfoFailureSI9111] - - // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some - // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. - val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ - case methodSym => - if (completeSilentlyAndCheckErroneous(methodSym)) { - // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. - if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") - warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) - None - } else { - val name = methodSym.javaSimpleName.toString // same as in genDefDef - val signature = name + methodSymToDescriptor(methodSym) - - // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE): - // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin. - // This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final - // but non-overridden methods of sealed traits from being inlined. - // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the - // lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late - // flag is ignored. The members are therefore not isEffectivelyFinal (their owner - // is not a module). Since we know that all impl class members are static, we can - // just take the shortcut. - val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden) - - // Identify trait interface methods that have a static implementation in the implementation - // class. Invocations of these methods can be re-wrired directly to the static implementation - // if they are final or the receiver is known. - // - // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters - // and super accessors. When AddInterfaces creates the impl class, these methods are - // initially added to it. - // - // The mixin phase later on filters out most of these members from the impl class (see - // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the - // impl class after mixin. So the filter in mixin is not exactly what we need here (we - // want to identify concrete trait methods, not any accessors). So we check some symbol - // properties manually. - val traitMethodWithStaticImplementation = { - import symtab.Flags._ - classSym.isTrait && !classSym.isImplClass && - erasure.needsImplMethod(methodSym) && - !methodSym.isModule && - !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR)) - } - - val info = MethodInlineInfo( - effectivelyFinal = effectivelyFinal, - traitMethodWithStaticImplementation = traitMethodWithStaticImplementation, - annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), - annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) - ) - Some((signature, info)) - } - }).toMap - - InlineInfo(traitSelfType, isEffectivelyFinal, sam, methodInlineInfos, warning) - } -} - -object BCodeAsmCommon { - /** - * Valid flags for InnerClass attribute entry. - * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 - */ - val INNER_CLASSES_FLAGS = { - asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | - asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | - asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | - asm.Opcodes.ACC_ENUM - } -} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index d501d4239b..1e93903bd2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -22,8 +22,316 @@ import BackendReporting._ */ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { import global._ + import definitions._ import bTypes._ import coreBTypes._ + import BTypes.{InternalName, InlineInfo, MethodInlineInfo} + + /** + * True for classes generated by the Scala compiler that are considered top-level in terms of + * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes. + */ + def considerAsTopLevelImplementationArtifact(classSym: Symbol) = { + classSym.isImplClass || classSym.isSpecialized + } + + /** + * Cache the value of delambdafy == "inline" for each run. We need to query this value many + * times, so caching makes sense. + */ + object delambdafyInline { + private var runId = -1 + private var value = false + + def apply(): Boolean = { + if (runId != global.currentRunId) { + runId = global.currentRunId + value = settings.Ydelambdafy.value == "inline" + } + value + } + } + + /** + * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a + * member class. This method is used to decide if we should emit an EnclosingMethod attribute. + * It is also used to decide whether the "owner" field in the InnerClass attribute should be + * null. + */ + def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { + assert(classSym.isClass, s"not a class: $classSym") + val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass + if (r && settings.Ybackend.value == "GenBCode") { + // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally + // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode + // we prevent this, see `nonAnon` in LambdaLift. + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $lambda, a non-lambda class is considered lambda. + assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name) + } + r + } + + /** + * The next enclosing definition in the source structure. Includes anonymous function classes + * under delambdafy:inline, even though they are only generated during UnCurry. + */ + def nextEnclosing(sym: Symbol): Symbol = { + val origOwner = sym.originalOwner + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $anon, a non-anon class is considered anon. + if (delambdafyInline() && sym.rawowner.isAnonymousFunction) { + // SI-9105: special handling for anonymous functions under delambdafy:inline. + // + // class C { def t = () => { def f { class Z } } } + // + // class C { def t = byNameMethod { def f { class Z } } } + // + // In both examples, the method f lambda-lifted into the anonfun class. + // + // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun. + // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ... + // + // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!) + // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!). + // + // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if` + // above makes sure we don't jump over the anonymous function in the by-name argument case. + // + // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f + // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym` + // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and + // we need to return it. + // If the rawowners are different, the symbol was not in between. In the first example, the + // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing + // of `f` is its rawowner, the anonFunClassSym. + // + // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C, + // not into the anonymous function class. The originalOwner chain is Z - f - C. + if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner + else sym.rawowner + } else { + origOwner + } + } + + def nextEnclosingClass(sym: Symbol): Symbol = + if (sym.isClass) sym + else nextEnclosingClass(nextEnclosing(sym)) + + def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) = + nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass + + /** + * Returns the enclosing method for non-member classes. In the following example + * + * class A { + * def f = { + * class B { + * class C + * } + * } + * } + * + * the method returns Some(f) for B, but None for C, because C is a member class. For non-member + * classes that are not enclosed by a method, it returns None: + * + * class A { + * { class B } + * } + * + * In this case, for B, we return None. + * + * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes). + * This is a source-level property, so we need to use the originalOwner chain to reconstruct it. + */ + private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { + assert(classSym.isClass, classSym) + + def doesNotExist(method: Symbol) = { + // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. + // (2) Value classes. Member methods of value classes exist in the generated box class. However, + // nested methods lifted into a value class are moved to the companion object and don't exist + // in the value class itself. We can identify such nested methods: the initial enclosing class + // is a value class, but the current owner is some other class (the module class). + method.owner.isTrait && method.isImplOnly || { // (1) + val enclCls = nextEnclosingClass(method) + exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2) + } + } + + def enclosingMethod(sym: Symbol): Option[Symbol] = { + if (sym.isClass || sym == NoSymbol) None + else if (sym.isMethod) { + if (doesNotExist(sym)) None else Some(sym) + } + else enclosingMethod(nextEnclosing(sym)) + } + enclosingMethod(nextEnclosing(classSym)) + } + + /** + * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level + * property, this method looks at the originalOwner chain. See doc in BTypes. + */ + private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { + assert(classSym.isClass, classSym) + val r = nextEnclosingClass(nextEnclosing(classSym)) + // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release + if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r") + r + } + + final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) + + /** + * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not + * an anonymous or local class). See doc in BTypes. + * + * The class is parametrized by two functions to obtain a bytecode class descriptor for a class + * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend + * on the implementation of GenASM / GenBCode, so they need to be passed in. + */ + def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { + // trait impl classes are always top-level, see comment in BTypes + if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { + val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym) + val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match { + case some @ Some(m) => + if (m.owner != enclosingClass) { + // This should never happen. In case it does, it prevents emitting an invalid + // EnclosingMethod attribute: if the attribute specifies an enclosing method, + // it needs to exist in the specified enclosing class. + devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass") + None + } else some + case none => none + } + Some(EnclosingMethodEntry( + classDesc(enclosingClass), + methodOpt.map(_.javaSimpleName.toString).orNull, + methodOpt.map(methodDesc).orNull)) + } else { + None + } + } + + /** + * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. + * + * The problem is that we are interested in a source-level property. Various phases changed the + * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. + * Therefore, `sym.isStatic` is not what we want. For example, in + * object T { def f { object U } } + * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. + */ + def isOriginallyStaticOwner(sym: Symbol): Boolean = + sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) + + /** + * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We + * cannot change the typer context of the completer at this point and make it silent: the context + * captured when creating the completer in the namer. However, we can temporarily replace + * global.reporter (it's a var) to store errors. + */ + def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = + if (sym.hasCompleteInfo) false + else { + val originalReporter = global.reporter + val storeReporter = new reporters.StoreReporter() + global.reporter = storeReporter + try { + sym.info + } finally { + global.reporter = originalReporter + } + sym.isErroneous + } + + /** + * Build the [[InlineInfo]] for a class symbol. + */ + def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = { + val traitSelfType = if (classSym.isTrait && !classSym.isImplClass) { + // The mixin phase uses typeOfThis for the self parameter in implementation class methods. + val selfSym = classSym.typeOfThis.typeSymbol + if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None + } else { + None + } + + val isEffectivelyFinal = classSym.isEffectivelyFinal + + val sam = { + if (classSym.isImplClass || classSym.isEffectivelyFinal) None + else { + // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an + // empty parameter list in later phases and would therefore be picked as SAM. + val samSym = exitingPickler(definitions.findSam(classSym.tpe)) + if (samSym == NoSymbol) None + else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) + } + } + + var warning = Option.empty[ClassSymbolInfoFailureSI9111] + + // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some + // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. + val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ + case methodSym => + if (completeSilentlyAndCheckErroneous(methodSym)) { + // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. + if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") + warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) + None + } else { + val name = methodSym.javaSimpleName.toString // same as in genDefDef + val signature = name + methodSymToDescriptor(methodSym) + + // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE): + // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin. + // This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final + // but non-overridden methods of sealed traits from being inlined. + // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the + // lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late + // flag is ignored. The members are therefore not isEffectivelyFinal (their owner + // is not a module). Since we know that all impl class members are static, we can + // just take the shortcut. + val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden) + + // Identify trait interface methods that have a static implementation in the implementation + // class. Invocations of these methods can be re-wrired directly to the static implementation + // if they are final or the receiver is known. + // + // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters + // and super accessors. When AddInterfaces creates the impl class, these methods are + // initially added to it. + // + // The mixin phase later on filters out most of these members from the impl class (see + // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the + // impl class after mixin. So the filter in mixin is not exactly what we need here (we + // want to identify concrete trait methods, not any accessors). So we check some symbol + // properties manually. + val traitMethodWithStaticImplementation = { + import symtab.Flags._ + classSym.isTrait && !classSym.isImplClass && + erasure.needsImplMethod(methodSym) && + !methodSym.isModule && + !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR)) + } + + val info = MethodInlineInfo( + effectivelyFinal = effectivelyFinal, + traitMethodWithStaticImplementation = traitMethodWithStaticImplementation, + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) + ) + Some((signature, info)) + } + }).toMap + + InlineInfo(traitSelfType, isEffectivelyFinal, sam, methodInlineInfos, warning) + } /* * must-single-thread @@ -335,9 +643,79 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } // end of trait BCInnerClassGen trait BCAnnotGen extends BCInnerClassGen { + private lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule + private lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE")) + private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) + private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) + + /** Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be uninitialized + */ + private def shouldEmitAnnotation(annot: AnnotationInfo) = { + annot.symbol.initialize.isJavaDefined && + annot.matches(ClassfileAnnotationClass) && + retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue && + annot.args.isEmpty + } + + private def isRuntimeVisible(annot: AnnotationInfo): Boolean = { + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match { + case Some(retentionAnnot) => + retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue))) + case _ => + // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the + // annotation is emitted with visibility `RUNTIME` + true + } + } - import genASM.{ubytesToCharArray, arrEncode} - import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible} + private def retentionPolicyOf(annot: AnnotationInfo): Symbol = + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc => + assoc.collectFirst { + case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value + }).getOrElse(AnnotationRetentionPolicyClassValue) + + def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.length) + var idx = 0 + while(idx < bytes.length) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + + ca + } + + final 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.length) { + val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) + val newEncLength = encLength.toLong + deltaEncLength + if(newEncLength >= 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += deltaEncLength + 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 + } /* * can-multi-thread @@ -384,7 +762,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { av.visit(name, strEncode(sb)) } else { val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- genASM.arrEncode(sb)) { arrAnnotV.visit(name, arg) } + for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } arrAnnotV.visitEnd() } // 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. @@ -471,13 +849,87 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def getCurrentCUnit(): CompilationUnit + // @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. + settings.Ynogenericsig + || sym.isArtifact + || sym.isLiftedMethod + || sym.isBridge + || (sym.ownerChain exists (_.isImplClass)) + ) + /* @return * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). * - otherwise the signature in question * * must-single-thread */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit()) + def getGenericSignature(sym: Symbol, owner: Symbol): String = getGenericSignature(sym, owner, getCurrentCUnit()) + + def getGenericSignature(sym: Symbol, owner: Symbol, unit: CompilationUnit): String = { + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + getGenericSignature(sym, owner, memberTpe, unit) + } + + def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type, unit: CompilationUnit): String = { + if (!needsGenericSignature(sym)) { return null } + + 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 _: Throwable => false } + } + + if (settings.Xverify) { + // 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.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } + } + + if(!isValidSignature) { + reporter.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 = enteringErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + reporter.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 + } } // end of trait BCJGenSigGen @@ -510,7 +962,23 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * * must-single-thread */ - private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { + private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol): Unit = { + def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol, unit: CompilationUnit): String = { + if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745 + else { + // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to. + // By rights, it should use the signature as-seen-from the module class, and add suitable + // primitive and value-class boxing/unboxing. + // But for now, just like we did in mixin, we just avoid writing a wrong generic signature + // (one that doesn't erase to the actual signature). See run/t3452b for a test case. + val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym)) + val erasedMemberType = erasure.erasure(sym)(memberTpe) + if (erasedMemberType =:= sym.info) + getGenericSignature(sym, moduleClass, memberTpe, unit) + else null + } + } + val moduleName = internalName(module) val methodInfo = module.thisType.memberInfo(m) val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType @@ -527,7 +995,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = genASM.staticForwarderGenericSignature(m, module, getCurrentCUnit()) + val jgensig = staticForwarderGenericSignature(m, module, getCurrentCUnit()) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) @@ -582,7 +1050,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") - for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) { + for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) { if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") else if (conflictingNames(m.name)) @@ -866,3 +1334,22 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } // end of trait JAndroidBuilder } + +object BCodeHelpers { + val ExcludedForwarderFlags = { + import scala.tools.nsc.symtab.Flags._ + // Should include DEFERRED but this breaks findMember. + SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO + } + + /** + * Valid flags for InnerClass attribute entry. + * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 + */ + val INNER_CLASSES_FLAGS = { + asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | + asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | + asm.Opcodes.ACC_ENUM + } +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 3fe56dd962..abd06b90a0 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -25,7 +25,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { import global._ import bTypes._ import coreBTypes._ - import bCodeAsmCommon._ /* * There's a dedicated PlainClassBuilder for each CompilationUnit, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index ef3fab7617..ab52bf72d8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -126,7 +126,7 @@ abstract class BTypes { /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType * is constructed by parsing the corresponding classfile. - * + * * Some JVM operations use either a full descriptor or only an internal name. Example: * ANEWARRAY java/lang/String // a new array of strings (internal name for the String class) * ANEWARRAY [Ljava/lang/String; // a new array of array of string (full descriptor for the String class) @@ -932,7 +932,7 @@ abstract class BTypes { // the static flag in the InnerClass table has a special meaning, see InnerClass comment i.flags & ~Opcodes.ACC_STATIC, if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 - ) & BCodeAsmCommon.INNER_CLASSES_FLAGS + ) & BCodeHelpers.INNER_CLASSES_FLAGS ) }) @@ -1192,4 +1192,4 @@ object BTypes { // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR val ScalaAttributeName = "Scala" val ScalaSigAttributeName = "ScalaSig" -} \ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index a2bf8e5725..c8ef3474c5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -28,10 +28,9 @@ import scala.tools.nsc.settings.ScalaSettings class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { import global._ import definitions._ + import genBCode._ val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global) - val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) - import bCodeAsmCommon._ val backendUtils: BackendUtils[this.type] = new BackendUtils(this) @@ -220,7 +219,101 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) } + def implementedInterfaces(classSym: Symbol): List[Symbol] = { + // Additional interface parents based on annotations and other cues + def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match { + case RemoteAttr => Some(RemoteInterfaceClass.tpe) + case _ => None + } + + // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes. + def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag + + val classParents = { + val parents = classSym.info.parents + // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the + // parents of a java annotations. undo this for the backend (where we need classfile-level information). + if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass) + else parents + } + + val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation) + + // We keep the superClass when computing minimizeParents to eliminate more interfaces. + // Example: T can be eliminated from D + // trait T + // class C extends T + // class D extends C with T + val interfaces = erasure.minimizeParents(allParents) match { + case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) => + ifs + case ifs => + // minimizeParents removes the superclass if it's redundant, for example: + // trait A + // class C extends Object with A // minimizeParents removes Object + ifs + } + interfaces.map(_.typeSymbol) + } + + /** + * The member classes of a class symbol. Note that the result of this method depends on the + * current phase, for example, after lambdalift, all local classes become member of the enclosing + * class. + * + * Impl classes are always considered top-level, see comment in BTypes. + */ + private def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ + case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) => + sym + case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin + val r = exitingPickler(sym.moduleClass) + assert(r != NoSymbol, sym.fullLocationString) + r + })(collection.breakOut) + private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { + /** + * Reconstruct the classfile flags from a Java defined class symbol. + * + * The implementation of this method is slightly different from `javaFlags` in BTypesFromSymbols. + * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags + * that are used in the generated classfiles. For example, all classes emitted by the Scala + * compiler have ACC_PUBLIC. + * + * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have + * to correspond exactly to the flags in the classfile. For example, if the class is package + * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the + * ClassBType. For example, the inliner needs the correct flags for access checks. + * + * Class flags are listed here: + * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1 + */ + def javaClassfileFlags(classSym: Symbol): Int = { + assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}") + import asm.Opcodes._ + def enumFlags = ACC_ENUM | { + // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method. + // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all + // Java enums for exhaustiveness checking. + val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred) + if (hasAbstractMethod) ACC_ABSTRACT else 0 + } + GenBCode.mkFlags( + // SI-9393: the classfile / java source parser make java annotation symbols look like classes. + // here we recover the actual classfile flags. + if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0, + if (classSym.isPublic) ACC_PUBLIC else 0, + if (classSym.isFinal) ACC_FINAL else 0, + // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces. + if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, + // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags) + if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (classSym.isArtifact) ACC_SYNTHETIC else 0, + if (classSym.hasJavaEnumFlag) enumFlags else 0 + ) + } + // Check for isImplClass: trait implementation classes have NoSymbol as superClass // Check for hasAnnotationFlag for SI-9393: the classfile / java source parsers add // scala.annotation.Annotation as superclass to java annotations. In reality, java diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala deleted file mode 100644 index 4424a3796b..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ /dev/null @@ -1,3355 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - -package scala -package tools.nsc -package backend.jvm - -import scala.collection.{ mutable, immutable } -import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } -import scala.tools.nsc.backend.jvm.opt.InlineInfoAttribute -import scala.tools.nsc.symtab._ -import scala.tools.asm -import asm.Label -import scala.annotation.tailrec - -/** - * @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 { self => - import global._ - import icodes._ - import icodes.opcodes._ - import definitions._ - - val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) - import bCodeAsmCommon._ - - // Strangely I can't find this in the asm code - // 255, but reserving 1 for "this" - final val MaximumJvmParameters = 254 - - val phaseName = "jvm" - - /** Create a new phase */ - override def newPhase(p: Phase): Phase = new AsmPhase(p) - - /** 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") - - /** 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") - - // An AsmPhase starts and ends within a Run, thus the caches in question will get populated and cleared within a Run, too), SI-7422 - javaNameCache.clear() - javaNameCache ++= 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. - reverseJavaName.clear() - reverseJavaName ++= List( - binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type. - binarynme.RuntimeNull.toString() -> RuntimeNullClass - ) - - // Lazy val; can't have eager vals in Phase constructors which may - // cause cycles before Global has finished initialization. - lazy val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") - - 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 _ => factoryNonJarBytecodeWriter() - } - } - - private def isJavaEntryPoint(icls: IClass) = { - val sym = icls.symbol - def fail(msg: String, pos: Position = sym.pos) = { - reporter.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. - enteringErasure { - val companion = sym.linkedClassOfClass - - 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 isJavaMainMethod) || { - 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) - } - } - } - } - } - } - - override def run() { - - if (settings.debug) - inform("[running phase " + name + " on icode]") - - if (settings.Xdce) { - val classes = icodes.classes.keys.toList // copy to avoid mutating the map while iterating - for (sym <- classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) { - log(s"Optimizer eliminated ${sym.fullNameString}") - deadCode.elidedClosures += sym - icodes.classes -= sym - } - } - - // For predictably ordered error messages. - var sortedClasses = classes.values.toList sortBy (_.symbol.fullName) - - // Warn when classes will overwrite one another on case-insensitive systems. - for ((_, v1 :: v2 :: _) <- sortedClasses groupBy (_.symbol.javaClassName.toString.toLowerCase)) { - reporter.warning(v1.symbol.pos, - s"Class ${v1.symbol.javaClassName} differs only in case from ${v2.symbol.javaClassName}. " + - "Such classes will overwrite one another on case-insensitive filesystems.") - } - - debuglog(s"Created new bytecode generator for ${classes.size} classes.") - val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) - val needsOutfile = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] - val plainCodeGen = new JPlainBuilder( bytecodeWriter, needsOutfile) - val mirrorCodeGen = new JMirrorBuilder( bytecodeWriter, needsOutfile) - val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter, needsOutfile) - - def emitFor(c: IClass) { - if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) { - if (c.symbol.companionClass == NoSymbol) - mirrorCodeGen genMirrorClass (c.symbol, c.cunit) - else - log(s"No mirror class for module with linked class: ${c.symbol.fullName}") - } - plainCodeGen genClass c - if (c.symbol hasAnnotation BeanInfoAttr) beanInfoCodeGen genBeanInfoClass c - } - - while (!sortedClasses.isEmpty) { - val c = sortedClasses.head - try emitFor(c) - catch { - case e: FileConflictException => - reporter.error(c.symbol.pos, s"error writing ${c.symbol}: ${e.getMessage}") - } - sortedClasses = sortedClasses.tail - classes -= c.symbol // GC opportunity - } - - bytecodeWriter.close() - - /* 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 GenASM 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 - - val javaNameCache = perRunCaches.newAnyRefMap[Symbol, Name]() - - // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. - val reverseJavaName = perRunCaches.newAnyRefMap[String, Symbol]() - - private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) - private def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0 - 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.rawflags & Flags.FINAL) != 0) || 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.isArtifact) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.hasJavaEnumFlag) ACC_ENUM 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 = - exitingPickler { 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.getModuleByName(name.dropModule) - 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 - } - - private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = { - assert(a.isClass) - assert(b.isClass) - - val res = (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.8" => asm.Opcodes.V1_8 - } - - 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") - - /** - * We call many Java varargs methods from ASM library that expect Arra[asm.Type] as argument so - * we override default (compiler-generated) ClassTag so we can provide specialized newArray implementation. - * - * Examples of methods that should pick our definition are: JBuilder.javaType and JPlainBuilder.genMethod. - */ - private implicit val asmTypeTag: scala.reflect.ClassTag[asm.Type] = new scala.reflect.ClassTag[asm.Type] { - def runtimeClass: java.lang.Class[asm.Type] = classOf[asm.Type] - final override def newArray(len: Int): Array[asm.Type] = new Array[asm.Type](len) - } - - /** basic functionality for class file building */ - abstract class JBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) { - - val EMPTY_STRING_ARRAY = Array.empty[String] - - val mdesc_arglessvoid = "()V" - - val CLASS_CONSTRUCTOR_NAME = "" - val INSTANCE_CONSTRUCTOR_NAME = "" - - // ----------------------------------------------------------------------------------------- - // 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 [[Object]]. May be null, but - * only for the [[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) - } - - // ----------------------------------------------------------------------------------------- - // utilities useful when emitting plain, mirror, and beaninfo classes. - // ----------------------------------------------------------------------------------------- - - def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { - try { - val arr = jclass.toByteArray() - val outF: scala.tools.nsc.io.AbstractFile = { - if(needsOutfile) getFile(sym, jclassName, ".class") else null - } - bytecodeWriter.writeClass(label, jclassName, arr, outF) - } catch { - case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") => - reporter.error(sym.pos, - s"Could not write class $jclassName because it exceeds JVM code size limits. ${e.getMessage}") - case e: java.io.IOException if e.getMessage != null && (e.getMessage contains "File name too long") => - reporter.error(sym.pos, e.getMessage + "\n" + - "This can happen on some encrypted or legacy file systems. Please see SI-3623 for more details.") - - } - } - - /** Specialized array conversion to prevent calling - * java.lang.reflect.Array.newInstance via TraversableOnce.toArray - */ - 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 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 enteringFlatten { ... } 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") - // impl classes are considered top-level, see comment in BTypes - val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass - if (isInner) { - innerClassBuffer += x - collectInnerClass(x.rawowner) - } - } - } - - collectInnerClass(sym) - - val hasInternalName = sym.isClass || sym.isModuleNotMethod - 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) => - // TODO: `duplicateOk` seems pretty ad-hoc (a more aggressive version caused SI-9356 because it called oldSym.exists, which failed in the unpickler; see also SI-5031) - def duplicateOk = oldsym == NoSymbol || trackedSym == NoSymbol || (syntheticCoreClasses contains oldsym) || (oldsym.isModuleClass && (oldsym.sourceModule == trackedSym.sourceModule)) - if (oldsym != trackedSym && !duplicateOk) - devWarning(s"""|Different class symbols have the same bytecode-level internal name: - | name: $internalName - | oldsym: ${oldsym.fullNameString} - | tracked: ${trackedSym.fullNameString}""".stripMargin) - } - } - - 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, isMirror: Boolean = false) { - /* 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 (isAnonymousOrLocalClass(innerSym)) - null - else { - val outerName = javaName(innerSym.rawowner) - if (isTopLevelModule(innerSym.rawowner)) "" + TermName(outerName).dropModule - else outerName - } - } - - def innerName(innerSym: Symbol): String = { - // phase travel necessary: after flatten, the name includes the name of outer classes. - // if some outer name contains $anon, a non-anon class is considered anon. - if (exitingPickler(innerSym.isAnonymousClass || innerSym.isAnonymousFunction)) null - else innerSym.rawname + innerSym.moduleSuffix - } - - val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases - - innerClassBuffer ++= { - val members = exitingPickler(memberClassesForInnerClassTable(csym)) - // lambdalift makes all classes (also local, anonymous) members of their enclosing class - val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym)) - val nested = { - // Classes nested in value classes are nested in the companion at this point. For InnerClass / - // EnclosingMethod, we use the value class as the outer class. So we remove nested classes - // from the companion that were originally nested in the value class. - if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) - else allNested - } - - // for the mirror class, we take the members of the companion module class (Java compat, see doc in BTypes.scala). - // for module classes, we filter out those members. - if (isMirror) members - else if (isTopLevelModule(csym)) nested diff members - else nested - } - - if (!considerAsTopLevelImplementationArtifact(csym)) { - // If this is a top-level non-impl class, add members of the companion object. These are the - // classes for which we change the InnerClass entry to allow using them from Java. - // We exclude impl classes: if the classfile for the impl class exists on the classpath, a - // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching - // members of that weird impl-class-module-class-symbol. that search probably cannot return - // any classes, but it's better to exclude it. - if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) { - // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only - // sees member classes, not local classes that were lifted by lambdalift. - innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass)) - } - - // Classes nested in value classes are nested in the companion at this point. For InnerClass / - // EnclosingMethod we use the value class as enclosing class. Here we search nested classes - // in the companion that were originally nested in the value class, and we add them as nested - // in the value class. - if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) { - val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) - innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym)) - } - } - - val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures - - 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 flagsWithFinal: Int = mkFlags( - // See comment in BTypes, when is a class marked static in the InnerClass table. - if (isOriginallyStaticOwner(innerSym.originalOwner)) asm.Opcodes.ACC_STATIC else 0, - (if (innerSym.isJava) javaClassfileFlags(innerSym) else javaFlags(innerSym)) & ~asm.Opcodes.ACC_STATIC, - if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag - ) & (BCodeAsmCommon.INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) - val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding. - 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, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { - - 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 (ThrownException(tp) <- excs.distinct) - yield { - val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp)) - javaName(erased.typeSymbol) - } - - def getCurrentCUnit(): CompilationUnit - - def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit()) - - def emitArgument(av: asm.AnnotationVisitor, - name: String, - arg: ClassfileAnnotArg) { - (arg: @unchecked) 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. - if (sb.fitsInOneString) - av.visit(name, strEncode(sb)) - else { - val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } - arrAnnotV.visitEnd() - } - // 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), isRuntimeVisible(annot)) - 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), isRuntimeVisible(annot)) - 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), isRuntimeVisible(annot)) - emitAssocs(av, assocs) - } - } - - def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) { - val annotationss = pannotss map (_ filter shouldEmitAnnotation) - if (annotationss forall (_.isEmpty)) return - for ((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), isRuntimeVisible(annot)) - emitAssocs(pannVisitor, assocs) - } - } - - /** Adds a @remote annotation, actual use unknown. - * - * Invoked from genMethod() and addForwarder(). - */ - def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { - def hasThrowsRemoteException = meth.annotations.exists { - case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass - case _ => false - } - val needsAnnotation = ( - ( isRemoteClass || - isRemote(meth) && isJMethodPublic - ) && !hasThrowsRemoteException - ) - if (needsAnnotation) { - val c = Constant(RemoteExceptionClass.tpe) - val arg = Literal(c) setType c.tpe - meth.addAnnotation(appliedType(ThrowsClass, c.tpe), arg) - } - } - - // ----------------------------------------------------------------------------------------- - // Static forwarders (related to mirror classes but also present in - // a plain class lacking companion module, for details see `isCandidateForForwarders`). - // ----------------------------------------------------------------------------------------- - - /** 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 = staticForwarderGenericSignature(m, module, getCurrentCUnit()) - 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, false) - 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 - 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(s"No forwarder for '$m' from $jclassName to '$moduleClass'") - else if (conflictingNames(m.name)) - log(s"No forwarder for $m due to conflict with " + linkedClass.info.member(m.name)) - else if (m.hasAccessBoundary) - log(s"No forwarder for non-public member $m") - else { - debuglog(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'") - addForwarder(isRemoteClass, jclass, moduleClass, m) - } - } - } - - } // end of class JCommonBuilder - - - trait JAndroidBuilder { - self: JPlainBuilder => - - 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(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(onInstance = false)) - block emit STORE_FIELD(fieldSymbol, isStatic = true) - } - - def legacyAddCreatorCode(clinit: asm.MethodVisitor) { - val creatorType: asm.Type = javaType(AndroidCreatorClass) - val tdesc_creator = creatorType.getDescriptor - - jclass.visitField( - PublicStaticFinal, - androidFieldName.toString, - 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.toString, - asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*), - false - ) - - // PUTSTATIC `thisName`.CREATOR; - clinit.visitFieldInsn( - asm.Opcodes.PUTSTATIC, - thisName, - androidFieldName.toString, - 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, needsOutfile: Boolean) - extends JCommonBuilder(bytecodeWriter, needsOutfile) - 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] = genBCode.serialVUID(clasz.symbol) - - 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: Array[String] = implementedInterfaces(c.symbol).map(javaName)(collection.breakOut) - - 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 */) - } - - enclosingMethodAttribute(clasz.symbol, javaName, javaType(_).getDescriptor) match { - case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => - jclass.visitOuterClass(className, methodName, methodDescriptor) - case _ => () - } - - // 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) - - if (!settings.YskipInlineInfoAttribute.value) - jclass.visitAttribute(InlineInfoAttribute(buildInlineInfoFromClassSymbol(c.symbol, javaName, javaType(_).getDescriptor))) - - // 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) - 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 = { - exitingPickler { !(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) - } - - 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 = _ - - 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.seq contains sym - } - } - - if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return - - if (m.params.size > MaximumJvmParameters) { - reporter.error(m.symbol.pos, s"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters.") - 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 isAbstractTraitMeth = isJInterface && !m.symbol.hasFlag(Flags.JAVA_DEFAULTMETHOD) - - val flags = mkFlags( - javaFlags(m.symbol), - if (isAbstractTraitMeth) 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: (see ASM Guide, 3.2.1) - // [ 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) - - // visitMaxs needs to be called according to the protocol. The arguments will be ignored - // since maximums (and stack map frames) are computed. See ASM Guide, Section 3.2.1, - // section "ClassWriter options" - jmethod.visitMaxs(0, 0) - } - - 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(onInstance = true)) - } - - if (isParcelableClass) { addCreatorCode(lastBlock) } - - lastBlock emit RETURN(UNIT) - lastBlock.close() - - method = m - jmethod = clinitMethod - jMethodName = CLASS_CONSTRUCTOR_NAME - jmethod.visitCode() - computeLocalVarsIndex(m) - genCode(m, emitVars = false, isStatic = 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, false) - } - - 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.toInt) - case ShortTag => jcode.iconst(const.shortValue.toInt) - 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 - - final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) } - - def iconst(cst: Char) { iconst(cst.toInt) } - 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) - } - } - - - def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) } - def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) } - - def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) } - def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) } - - def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) } - def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) } - def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) } - def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) } - def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) } - def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) } - - def invokespecial(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false) - } - def invokestatic(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false) - } - def invokeinterface(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true) - } - def invokevirtual(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false) - } - - def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } - def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF(), label) } - def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP(), label) } - 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) - } - def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) } - def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) } - - 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.toDouble - 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: scala.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: scala.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) { - final def start = lstart.getOffset - 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 track down the cause for these. - devWarning(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191") - } - } - - def getMerged(): scala.collection.Map[Local, List[Interval]] = { - // TODO should but isn't: unbalanced start(s) of scope(s) - val shouldBeEmpty = pending filter { p => val (_, st) = p; st.nonEmpty } - val merged = mutable.Map[Local, List[Interval]]() - def addToMerged(lv: Local, start: Label, end: Label) { - val intv = Interval(start, end) - merged(lv) = if (merged contains lv) fuse(merged(lv), intv) else intv :: Nil - } - 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((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[Tuple3[String, Local, Interval]] = Nil - for((local, ranges) <- scoping.getMerged()) { - var name = javaName(local.sym) - if (name == null) { - anonCounter += 1 - name = "" - } - for(intrvl <- ranges) { - fltnd ::= (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 (name: String, _, intrvl: Interval) = kr - - (intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name) - } - - for((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 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 needsInterfaceCall(sym: Symbol) = ( - sym.isInterface - || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass) - ) - // whether to reference the type of the receiver or - // the type of the method owner - val useMethodOwner = ( - style != Dynamic - || hostSymbol.isBottomClass - || methodOwner == ObjectClass - ) - 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 needsInterfaceCall(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)) - - 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(iPos.finalPosition.line, lineLab) - } - } - - genInstr(instr, b) - - } - - } - - def genInstr(instr: Instruction, b: BasicBlock) { - import asm.Opcodes - (instr.category: @scala.annotation.switch) match { - - - case icodes.localsCat => - def genLocalInstr() = (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) - } - } - genLocalInstr() - - case icodes.stackCat => - def genStackInstr() = (instr: @unchecked) match { - - case LOAD_MODULE(module) => - // assert(module.isModule, "Expected module: " + module) - debuglog("generating LOAD_MODULE for: " + module + " flags: " + module.flagString) - def inStaticMethod = this.method != null && this.method.symbol.isStaticMember - if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString && !inStaticMethod) { - 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(_) => () - } - genStackInstr() - - case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant) - - case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos) - - case icodes.castsCat => - def genCastInstr() = (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) - } - - } - genCastInstr() - - case icodes.objsCat => - def genObjsInstr() = (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) - } - genObjsInstr() - - case icodes.fldsCat => - def genFldsInstr() = (instr: @unchecked) match { - - case lf @ LOAD_FIELD(field, isStatic) => - val owner = javaName(lf.hostClass) - debuglog("LOAD_FIELD with owner: " + owner + " flags: " + field.owner.flagString) - 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) - - } - genFldsInstr() - - case icodes.mthdsCat => - def genMethodsInstr() = (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) - - } - genMethodsInstr() - - case icodes.arraysCat => - def genArraysInstr() = (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) - } - genArraysInstr() - - case icodes.jumpsCat => - def genJumpInstr() = (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) - // SI-6102: Determine whether eliding this JUMP results in an empty range being covered by some EH. - // If so, emit a NOP in place of the elided JUMP, to avoid "java.lang.ClassFormatError: Illegal exception table range" - else if (newNormal.isJumpOnly(b) && m.exh.exists(eh => eh.covers(b))) { - devWarning("Had a jump only block that wasn't collapsed") - emit(asm.Opcodes.NOP) - } - - 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) } - } - } - - } - genJumpInstr() - - case icodes.retCat => - def genRetInstr() = (instr: @unchecked) match { - case RETURN(kind) => jcode emitRETURN kind - case THROW(_) => emit(Opcodes.ATHROW) - } - genRetInstr() - } - } - - /* - * 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) && isNonUnitValueTK(to), s"Cannot emit primitive conversion from $from 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 } - // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) - assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") - - 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) => - def genArith() = { - 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) - } - } - genArith() - - // 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) => - def genLogical() = op match { - case AND => - kind match { - case LONG => emit(Opcodes.LAND) - case INT => emit(Opcodes.IAND) - case _ => - emit(Opcodes.IAND) - if (kind != BOOL) { emitT2T(INT, kind) } - } - case OR => - kind match { - case LONG => emit(Opcodes.LOR) - case INT => emit(Opcodes.IOR) - case _ => - emit(Opcodes.IOR) - if (kind != BOOL) { emitT2T(INT, kind) } - } - case XOR => - kind match { - case LONG => emit(Opcodes.LXOR) - case INT => emit(Opcodes.IXOR) - case _ => - emit(Opcodes.IXOR) - if (kind != BOOL) { emitT2T(INT, kind) } - } - } - genLogical() - - case Shift(op, kind) => - def genShift() = op match { - case LSL => - kind match { - case LONG => emit(Opcodes.LSHL) - case INT => emit(Opcodes.ISHL) - case _ => - emit(Opcodes.ISHL) - emitT2T(INT, kind) - } - case ASR => - kind match { - case LONG => emit(Opcodes.LSHR) - case INT => emit(Opcodes.ISHR) - case _ => - emit(Opcodes.ISHR) - emitT2T(INT, kind) - } - case LSR => - kind match { - case LONG => emit(Opcodes.LUSHR) - case INT => emit(Opcodes.IUSHR) - case _ => - emit(Opcodes.IUSHR) - emitT2T(INT, kind) - } - } - genShift() - - case Comparison(op, kind) => - def genCompare() = op match { - case CMP => - (kind: @unchecked) match { - case LONG => emit(Opcodes.LCMP) - } - case CMPL => - (kind: @unchecked) match { - case FLOAT => emit(Opcodes.FCMPL) - case DOUBLE => emit(Opcodes.DCMPL) - } - case CMPG => - (kind: @unchecked) match { - case FLOAT => emit(Opcodes.FCMPG) - case DOUBLE => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se6/html/Instructions2.doc3.html - - } - } - genCompare() - - case Conversion(src, dst) => - debuglog("Converting from: " + src + " to: " + dst) - 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(k: TypeKind): Int = if(k.isWideType) 2 else 1 - - 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, needsOutfile: Boolean) extends JCommonBuilder(bytecodeWriter, needsOutfile) { - - 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(s"Dumping mirror class for '$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, isMirror = true) - mirrorClass.visitEnd() - writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) - } - } // end of class JMirrorBuilder - - - /** builder of bean info classes */ - class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { - - /** - * 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.getterIn(clasz.symbol); - s = f.symbol.setterIn(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, false) - 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 particular, 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 { - /** - * True if a block is "jump only" which is defined - * as being a block that consists only of 0 or more instructions that - * won't make it to the JVM followed by a JUMP. - */ - def isJumpOnly(b: BasicBlock): Boolean = { - val nonICode = firstNonIcodeOnlyInstructions(b) - // by definition a block has to have a jump, conditional jump, return, or throw - assert(nonICode.hasNext, "empty block") - nonICode.next.isInstanceOf[JUMP] - } - - /** - * Returns the list of instructions in a block that follow all ICode only instructions, - * where an ICode only instruction is one that won't make it to the JVM - */ - private def firstNonIcodeOnlyInstructions(b: BasicBlock): Iterator[Instruction] = { - def isICodeOnlyInstruction(i: Instruction) = i match { - case LOAD_EXCEPTION(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) => true - case _ => false - } - b.iterator dropWhile isICodeOnlyInstruction - } - - /** - * Returns the target of a block that is "jump only" which is defined - * as being a block that consists only of 0 or more instructions that - * won't make it to the JVM followed by a JUMP. - * - * @param b The basic block to examine - * @return Some(target) if b is a "jump only" block or None if it's not - */ - private def getJumpOnlyTarget(b: BasicBlock): Option[BasicBlock] = { - val nonICode = firstNonIcodeOnlyInstructions(b) - // by definition a block has to have a jump, conditional jump, return, or throw - assert(nonICode.nonEmpty, "empty block") - nonICode.next match { - case JUMP(whereto) => - assert(!nonICode.hasNext, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)") - Some(whereto) - case _ => None - } - } - - /** - * 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 become eligible to removed by the reachability analyzer - * - * 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, - * renders the BasicBlocks described above (to recap, consisting of just a JUMP) unreachable. - */ - private def collapseJumpOnlyBlocks(m: IMethod) { - assert(m.hasCode, "code-less method") - - def rephraseGotos(detour: mutable.Map[BasicBlock, BasicBlock]) { - def lookup(b: BasicBlock) = detour.getOrElse(b, b) - - m.code.startBlock = lookup(m.code.startBlock) - - for(eh <- m.exh) - eh.setStartBlock(lookup(eh.startBlock)) - - for (b <- m.blocks) { - def replaceLastInstruction(i: Instruction) = { - if (b.lastInstruction != i) { - val idxLast = b.size - 1 - debuglog(s"In block $b, replacing last instruction ${b.lastInstruction} with ${i}") - b.replaceInstruction(idxLast, i) - } - } - - b.lastInstruction match { - case JUMP(whereto) => - replaceLastInstruction(JUMP(lookup(whereto))) - case CJUMP(succ, fail, cond, kind) => - replaceLastInstruction(CJUMP(lookup(succ), lookup(fail), cond, kind)) - case CZJUMP(succ, fail, cond, kind) => - replaceLastInstruction(CZJUMP(lookup(succ), lookup(fail), cond, kind)) - case SWITCH(tags, labels) => - val newLabels = (labels map lookup) - replaceLastInstruction(SWITCH(tags, newLabels)) - case _ => () - } - } - } - - /* - * Computes a mapping from jump only block to its - * final destination which is either a non-jump-only - * block or, if it's in a jump-only block cycle, is - * itself - */ - def computeDetour: mutable.Map[BasicBlock, BasicBlock] = { - // fetch the jump only blocks and their immediate destinations - val pairs = for { - block <- m.blocks.toIterator - target <- getJumpOnlyTarget(block) - } yield(block, target) - - // mapping from a jump-only block to our current knowledge of its - // final destination. Initially it's just jump block to immediate jump - // target - val detour = mutable.Map[BasicBlock, BasicBlock](pairs.toSeq:_*) - - // for each jump-only block find its final destination - // taking advantage of the destinations we found for previous - // blocks - for (key <- detour.keySet) { - // we use the Robert Floyd's classic Tortoise and Hare algorithm - @tailrec - def findDestination(tortoise: BasicBlock, hare: BasicBlock): BasicBlock = { - if (tortoise == hare) - // cycle detected, map key to key - key - else if (detour contains hare) { - // advance hare once - val hare1 = detour(hare) - // make sure we can advance hare a second time - if (detour contains hare1) - // advance tortoise once and hare a second time - findDestination(detour(tortoise), detour(hare1)) - else - // hare1 is not in the map so it's not a jump-only block, it's the destination - hare1 - } else - // hare is not in the map so it's not a jump-only block, it's the destination - hare - } - // update the mapping for key based on its final destination - detour(key) = findDestination(key, detour(key)) - } - detour - } - - val detour = computeDetour - rephraseGotos(detour) - - if (settings.debug) { - val (remappings, cycles) = detour partition {case (source, target) => source != target} - for ((source, target) <- remappings) { - debuglog(s"Will elide jump only block $source because it can be jumped around to get to $target.") - if (m.startBlock == source) devWarning("startBlock should have been re-wired by now") - } - val sources = remappings.keySet - val targets = remappings.values.toSet - val intersection = sources intersect targets - - if (intersection.nonEmpty) devWarning(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}") - - for ((source, _) <- cycles) { - debuglog(s"Block $source is in a do-nothing infinite loop. Did the user write 'while(true){}'?") - } - } - } - - /** - * Removes all blocks that are unreachable in a method using a standard reachability analysis. - */ - def elimUnreachableBlocks(m: IMethod) { - assert(m.hasCode, "code-less method") - - // assume nothing is reachable until we prove it can be reached - val reachable = mutable.Set[BasicBlock]() - - // the set of blocks that we know are reachable but have - // yet to be marked reachable, initially only the start block - val worklist = mutable.Set(m.startBlock) - - while (worklist.nonEmpty) { - val block = worklist.head - worklist remove block - // we know that one is reachable - reachable add block - // so are its successors, so go back around and add the ones we still - // think are unreachable - worklist ++= (block.successors filterNot reachable) - } - - // exception handlers need to be told not to cover unreachable blocks - // and exception handlers that no longer cover any blocks need to be - // removed entirely - val unusedExceptionHandlers = mutable.Set[ExceptionHandler]() - for (exh <- m.exh) { - exh.covered = exh.covered filter reachable - if (exh.covered.isEmpty) { - unusedExceptionHandlers += exh - } - } - - // remove the unused exception handler references - if (settings.debug) - for (exh <- unusedExceptionHandlers) debuglog(s"eliding exception handler $exh because it does not cover any reachable blocks") - m.exh = m.exh filterNot unusedExceptionHandlers - - // everything not in the reachable set is unreachable, unused, and unloved. buh bye - for (b <- m.blocks filterNot reachable) { - debuglog(s"eliding block $b because it is unreachable") - m.code removeBlock b - } - } - - def normalize(m: IMethod) { - if(!m.hasCode) { return } - collapseJumpOnlyBlocks(m) - if (settings.optimise) - elimUnreachableBlocks(m) - icodes checkValid m - } - - } - - // @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. - settings.Ynogenericsig - || sym.isArtifact - || sym.isLiftedMethod - || sym.isBridge - || (sym.ownerChain exists (_.isImplClass)) - ) - - final def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol, unit: CompilationUnit): String = { - if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745 - else { - // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to. - // By rights, it should use the signature as-seen-from the module class, and add suitable - // primitive and value-class boxing/unboxing. - // But for now, just like we did in mixin, we just avoid writing a wrong generic signature - // (one that doesn't erase to the actual signature). See run/t3452b for a test case. - val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym)) - val erasedMemberType = erasure.erasure(sym)(memberTpe) - if (erasedMemberType =:= sym.info) - getGenericSignature(sym, moduleClass, memberTpe, unit) - else null - } - } - - /** @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, unit: CompilationUnit): String = { - val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) - getGenericSignature(sym, owner, memberTpe, unit) - } - def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type, unit: CompilationUnit): String = { - if (!needsGenericSignature(sym)) { return null } - - 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 _: Throwable => false } - } - - if (settings.Xverify) { - // 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.CheckClassAdapter - if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar - else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } - else { CheckClassAdapter checkClassSignature sig } - } - - if(!isValidSignature) { - reporter.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 = enteringErasure(erasure.prepareSigMap(memberTpe)) - val bytecodeTpe = owner.thisType.memberInfo(sym) - if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { - reporter.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.length) - var idx = 0 - while(idx < bytes.length) { - val b: Byte = bytes(idx) - assert((b & ~0x7f) == 0) - ca(idx) = b.asInstanceOf[Char] - idx += 1 - } - - ca - } - - final 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.length) { - val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) - val newEncLength = encLength.toLong + deltaEncLength - if(newEncLength >= 65535) { - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - encLength = 0 - prevOffset = offset - } else { - encLength += deltaEncLength - 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) - } -} diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index a9ec8c7b30..567af03f23 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -222,8 +222,6 @@ trait ScalaSettings extends AbsScalaSettings val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly() val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method") - val YskipInlineInfoAttribute = BooleanSetting("-Yskip-inline-info-attribute", "Do not add the ScalaInlineInfo attribute to classfiles generated by -Ybackend:GenASM") - object YoptChoices extends MultiChoiceEnumeration { val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.") val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessary ones.") @@ -354,7 +352,7 @@ trait ScalaSettings extends AbsScalaSettings * Settings motivated by GenBCode */ val Ybackend = ChoiceSetting ("-Ybackend", "choice of bytecode emitter", "Choice of bytecode emitter.", - List("GenASM", "GenBCode"), + List("GenBCode"), "GenBCode") // Feature extensions val XmacroSettings = MultiStringSetting("-Xmacro-settings", "option", "Custom settings for macros.") diff --git a/test/files/instrumented/inline-in-constructors.flags b/test/files/instrumented/inline-in-constructors.flags index d1ebc4c940..65caa3736e 100644 --- a/test/files/instrumented/inline-in-constructors.flags +++ b/test/files/instrumented/inline-in-constructors.flags @@ -1 +1 @@ --optimise -Ydelambdafy:inline -Ybackend:GenASM +-Yopt:l:classpath diff --git a/test/files/jvm/bytecode-test-example/Foo_1.flags b/test/files/jvm/bytecode-test-example/Foo_1.flags deleted file mode 100644 index 49f2d2c4c8..0000000000 --- a/test/files/jvm/bytecode-test-example/Foo_1.flags +++ /dev/null @@ -1 +0,0 @@ --Ybackend:GenASM diff --git a/test/files/jvm/constant-optimization/Foo_1.flags b/test/files/jvm/constant-optimization/Foo_1.flags deleted file mode 100644 index 67a1dbe8da..0000000000 --- a/test/files/jvm/constant-optimization/Foo_1.flags +++ /dev/null @@ -1 +0,0 @@ --Ynooptimise -Yconst-opt -Ybackend:GenASM \ No newline at end of file diff --git a/test/files/jvm/constant-optimization/Foo_1.scala b/test/files/jvm/constant-optimization/Foo_1.scala deleted file mode 100644 index cb67ad4e90..0000000000 --- a/test/files/jvm/constant-optimization/Foo_1.scala +++ /dev/null @@ -1,9 +0,0 @@ -class Foo_1 { - def foo() { - // constant optimization should eliminate all branches - val i = 1 - val x = if (i != 1) null else "good" - val y = if (x == null) "good" else x + "" - println(y) - } -} \ No newline at end of file diff --git a/test/files/jvm/constant-optimization/Test.scala b/test/files/jvm/constant-optimization/Test.scala deleted file mode 100644 index dc0f8f6103..0000000000 --- a/test/files/jvm/constant-optimization/Test.scala +++ /dev/null @@ -1,27 +0,0 @@ - -import scala.tools.partest.BytecodeTest -import scala.tools.asm -import asm.tree.InsnList -import scala.collection.JavaConverters._ - -object Test extends BytecodeTest { - val comparisons = Set(asm.Opcodes.IF_ACMPEQ, asm.Opcodes.IF_ACMPNE, asm.Opcodes.IF_ICMPEQ, asm.Opcodes.IF_ICMPGE, asm.Opcodes.IF_ICMPGT, asm.Opcodes.IF_ICMPLE, - asm.Opcodes.IF_ICMPLT, asm.Opcodes.IF_ICMPNE, asm.Opcodes.IFEQ, asm.Opcodes.IFGE, asm.Opcodes.IFGT, asm.Opcodes.IFLE, asm.Opcodes.IFLT, - asm.Opcodes.IFNE, asm.Opcodes.IFNONNULL, asm.Opcodes.IFNULL) - - def show: Unit = { - val classNode = loadClassNode("Foo_1") - val methodNode = getMethod(classNode, "foo") - // after optimization there should be no comparisons left - val expected = 0 - - val got = countComparisons(methodNode.instructions) - assert(got == expected, s"expected $expected but got $got comparisons") - } - - def countComparisons(insnList: InsnList): Int = { - def isComparison(node: asm.tree.AbstractInsnNode): Boolean = - (comparisons contains node.getOpcode) - insnList.iterator.asScala count isComparison - } -} \ No newline at end of file diff --git a/test/files/jvm/nooptimise/Foo_1.flags b/test/files/jvm/nooptimise/Foo_1.flags deleted file mode 100644 index f493cf9f3f..0000000000 --- a/test/files/jvm/nooptimise/Foo_1.flags +++ /dev/null @@ -1 +0,0 @@ --Ybackend:GenASM -optimise -Ynooptimise \ No newline at end of file diff --git a/test/files/jvm/nooptimise/Foo_1.scala b/test/files/jvm/nooptimise/Foo_1.scala deleted file mode 100644 index 896d5695de..0000000000 --- a/test/files/jvm/nooptimise/Foo_1.scala +++ /dev/null @@ -1,8 +0,0 @@ -class Foo_1 { - def foo() { - // optimization will remove this magic 3 from appearing in the source - // so -Ynooptimize should prevent that - val x = 3 - - } -} diff --git a/test/files/jvm/nooptimise/Test.scala b/test/files/jvm/nooptimise/Test.scala deleted file mode 100644 index 7b7ecd6dbd..0000000000 --- a/test/files/jvm/nooptimise/Test.scala +++ /dev/null @@ -1,23 +0,0 @@ -import scala.tools.partest.BytecodeTest -import scala.tools.asm -import asm.tree.InsnList -import scala.collection.JavaConverters._ - -object Test extends BytecodeTest { - def show: Unit = { - val classNode = loadClassNode("Foo_1") - val methodNode = getMethod(classNode, "foo") - // if optimization didn't run then - // there should be some useless instructions - // with the magic constant 3 - val expected = 1 - val got = countMagicThrees(methodNode.instructions) - assert(got == expected, s"expected $expected but got $got magic threes") - } - - def countMagicThrees(insnList: InsnList): Int = { - def isMagicThree(node: asm.tree.AbstractInsnNode): Boolean = - (node.getOpcode == asm.Opcodes.ICONST_3) - insnList.iterator.asScala.count(isMagicThree) - } -} diff --git a/test/files/jvm/patmat_opt_ignore_underscore.check b/test/files/jvm/patmat_opt_ignore_underscore.check deleted file mode 100644 index 43f53aba12..0000000000 --- a/test/files/jvm/patmat_opt_ignore_underscore.check +++ /dev/null @@ -1 +0,0 @@ -bytecode identical diff --git a/test/files/jvm/patmat_opt_ignore_underscore.flags b/test/files/jvm/patmat_opt_ignore_underscore.flags deleted file mode 100644 index 2cd4b38726..0000000000 --- a/test/files/jvm/patmat_opt_ignore_underscore.flags +++ /dev/null @@ -1 +0,0 @@ --optimize -Ybackend:GenASM \ No newline at end of file diff --git a/test/files/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala b/test/files/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala deleted file mode 100644 index b0506018f6..0000000000 --- a/test/files/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala +++ /dev/null @@ -1,29 +0,0 @@ -// this class's bytecode, compiled under -optimize is analyzed by the test -// method a's bytecode should be identical to method b's bytecode -// this is not the best test for shielding against regressing on this particular issue, -// but it sets the stage for checking the bytecode emitted by the pattern matcher and -// comparing it to manually tuned code using if/then/else etc. -class SameBytecode { - case class Foo(x: Any, y: String) - - def a = - Foo(1, "a") match { - case Foo(_: String, y) => y - } - - // this method's body holds the tree that should be generated by the pattern matcher for method a (-Xprint:patmat) - // the test checks that bytecode for a and b is identical (modulo line numbers) - // we can't diff trees as they are quite different (patmat uses jumps to labels that cannot be expressed in source, for example) - // note that the actual tree is quite bad: we do an unnecessary null check, isInstanceOf and local val (x3) - // some of these will be fixed soon (the initial null check is for the scrutinee, which is harder to fix in patmat) - def b: String = { - val x1 = Foo(1, "a") - if (x1.ne(null)) { - if (x1.x.isInstanceOf[String]) { - return x1.y - } - } - - throw new MatchError(x1) - } -} \ No newline at end of file diff --git a/test/files/jvm/patmat_opt_ignore_underscore/test.scala b/test/files/jvm/patmat_opt_ignore_underscore/test.scala deleted file mode 100644 index d6630e80a0..0000000000 --- a/test/files/jvm/patmat_opt_ignore_underscore/test.scala +++ /dev/null @@ -1,18 +0,0 @@ -/* - * filter: inliner warning; re-run with - */ -import scala.tools.partest.BytecodeTest - -import scala.tools.nsc.util.JavaClassPath -import java.io.InputStream -import scala.tools.asm -import asm.ClassReader -import asm.tree.{ClassNode, InsnList} -import scala.collection.JavaConverters._ - -object Test extends BytecodeTest { - def show: Unit = { - val classNode = loadClassNode("SameBytecode") - sameBytecode(getMethod(classNode, "a"), getMethod(classNode, "b")) - } -} diff --git a/test/files/jvm/patmat_opt_no_nullcheck.check b/test/files/jvm/patmat_opt_no_nullcheck.check deleted file mode 100644 index 43f53aba12..0000000000 --- a/test/files/jvm/patmat_opt_no_nullcheck.check +++ /dev/null @@ -1 +0,0 @@ -bytecode identical diff --git a/test/files/jvm/patmat_opt_no_nullcheck.flags b/test/files/jvm/patmat_opt_no_nullcheck.flags deleted file mode 100644 index 2cd4b38726..0000000000 --- a/test/files/jvm/patmat_opt_no_nullcheck.flags +++ /dev/null @@ -1 +0,0 @@ --optimize -Ybackend:GenASM \ No newline at end of file diff --git a/test/files/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala b/test/files/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala deleted file mode 100644 index 1e4d564cdf..0000000000 --- a/test/files/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala +++ /dev/null @@ -1,24 +0,0 @@ -// this class's bytecode, compiled under -optimize is analyzed by the test -// method a's bytecode should be identical to method b's bytecode -case class Foo(x: Any) - -class SameBytecode { - def a = - (Foo(1): Any) match { - case Foo(_: String) => - } - - // there's no null check - def b: Unit = { - val x1: Any = Foo(1) - if (x1.isInstanceOf[Foo]) { - val x3 = x1.asInstanceOf[Foo] - if (x3.x.isInstanceOf[String]) { - val x = () - return - } - } - - throw new MatchError(x1) - } -} \ No newline at end of file diff --git a/test/files/jvm/patmat_opt_no_nullcheck/test.scala b/test/files/jvm/patmat_opt_no_nullcheck/test.scala deleted file mode 100644 index d02c929e01..0000000000 --- a/test/files/jvm/patmat_opt_no_nullcheck/test.scala +++ /dev/null @@ -1,11 +0,0 @@ -/* - * filter: inliner warning; re-run with - */ -import scala.tools.partest.BytecodeTest - -object Test extends BytecodeTest { - def show: Unit = { - val classNode = loadClassNode("SameBytecode") - sameBytecode(getMethod(classNode, "a"), getMethod(classNode, "b")) - } -} diff --git a/test/files/jvm/patmat_opt_primitive_typetest.check b/test/files/jvm/patmat_opt_primitive_typetest.check deleted file mode 100644 index 43f53aba12..0000000000 --- a/test/files/jvm/patmat_opt_primitive_typetest.check +++ /dev/null @@ -1 +0,0 @@ -bytecode identical diff --git a/test/files/jvm/patmat_opt_primitive_typetest.flags b/test/files/jvm/patmat_opt_primitive_typetest.flags deleted file mode 100644 index b9bb09167e..0000000000 --- a/test/files/jvm/patmat_opt_primitive_typetest.flags +++ /dev/null @@ -1 +0,0 @@ --optimize -Ybackend:GenASM diff --git a/test/files/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala b/test/files/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala deleted file mode 100644 index c961082fa7..0000000000 --- a/test/files/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala +++ /dev/null @@ -1,24 +0,0 @@ -// this class's bytecode, compiled under -optimize is analyzed by the test -// method a's bytecode should be identical to method b's bytecode -class SameBytecode { - case class Foo(x: Int, y: String) - - def a = - Foo(1, "a") match { - case Foo(_: Int, y) => y - } - - // this method's body holds the tree that should be generated by the pattern matcher for method a (-Xprint:patmat) - // the test checks that bytecode for a and b is identical (modulo line numbers) - // we can't diff trees as they are quite different (patmat uses jumps to labels that cannot be expressed in source, for example) - // note that the actual tree is quite bad: we do an unnecessary null check, and local val (x3) - // some of these will be fixed soon (the initial null check is for the scrutinee, which is harder to fix in patmat) - def b: String = { - val x1 = Foo(1, "a") - if (x1.ne(null)) { - return x1.y - } - - throw new MatchError(x1) - } -} \ No newline at end of file diff --git a/test/files/jvm/patmat_opt_primitive_typetest/test.scala b/test/files/jvm/patmat_opt_primitive_typetest/test.scala deleted file mode 100644 index 2927e763d5..0000000000 --- a/test/files/jvm/patmat_opt_primitive_typetest/test.scala +++ /dev/null @@ -1,8 +0,0 @@ -import scala.tools.partest.BytecodeTest - -object Test extends BytecodeTest { - def show: Unit = { - val classNode = loadClassNode("SameBytecode") - sameBytecode(getMethod(classNode, "a"), getMethod(classNode, "b")) - } -} diff --git a/test/files/jvm/t7006.check b/test/files/jvm/t7006.check deleted file mode 100644 index 6294b14d62..0000000000 --- a/test/files/jvm/t7006.check +++ /dev/null @@ -1,29 +0,0 @@ -[running phase parser on Foo_1.scala] -[running phase namer on Foo_1.scala] -[running phase packageobjects on Foo_1.scala] -[running phase typer on Foo_1.scala] -[running phase patmat on Foo_1.scala] -[running phase superaccessors on Foo_1.scala] -[running phase extmethods on Foo_1.scala] -[running phase pickler on Foo_1.scala] -[running phase refchecks on Foo_1.scala] -[running phase uncurry on Foo_1.scala] -[running phase tailcalls on Foo_1.scala] -[running phase specialize on Foo_1.scala] -[running phase explicitouter on Foo_1.scala] -[running phase erasure on Foo_1.scala] -[running phase posterasure on Foo_1.scala] -[running phase lazyvals on Foo_1.scala] -[running phase lambdalift on Foo_1.scala] -[running phase constructors on Foo_1.scala] -[running phase flatten on Foo_1.scala] -[running phase mixin on Foo_1.scala] -[running phase cleanup on Foo_1.scala] -[running phase delambdafy on Foo_1.scala] -[running phase icode on Foo_1.scala] -[running phase inliner on Foo_1.scala] -[running phase inlinehandlers on Foo_1.scala] -[running phase closelim on Foo_1.scala] -[running phase constopt on Foo_1.scala] -[running phase dce on Foo_1.scala] -[running phase jvm on icode] diff --git a/test/files/jvm/t7006/Foo_1.flags b/test/files/jvm/t7006/Foo_1.flags deleted file mode 100644 index 29a9d424f0..0000000000 --- a/test/files/jvm/t7006/Foo_1.flags +++ /dev/null @@ -1 +0,0 @@ --optimise -Ydebug -Xfatal-warnings -Ybackend:GenASM diff --git a/test/files/jvm/t7006/Foo_1.scala b/test/files/jvm/t7006/Foo_1.scala deleted file mode 100644 index 3985557d9f..0000000000 --- a/test/files/jvm/t7006/Foo_1.scala +++ /dev/null @@ -1,10 +0,0 @@ -class Foo_1 { - def foo { - try { - val x = 3 // this will be optimized away, leaving a useless jump only block - } finally { - print("hello") - } - while(true){} // ensure infinite loop doesn't break the algorithm - } -} diff --git a/test/files/jvm/t7006/Test.scala b/test/files/jvm/t7006/Test.scala deleted file mode 100644 index 065a23510e..0000000000 --- a/test/files/jvm/t7006/Test.scala +++ /dev/null @@ -1,19 +0,0 @@ -import scala.tools.partest.BytecodeTest -import scala.tools.asm -import asm.tree.InsnList -import scala.collection.JavaConverters._ - -object Test extends BytecodeTest { - def show: Unit = { - val classNode = loadClassNode("Foo_1") - val methodNode = getMethod(classNode, "foo") - assert(count(methodNode.instructions, asm.Opcodes.NOP) == 0) - assert(count(methodNode.instructions, asm.Opcodes.GOTO) == 1) - } - - def count(insnList: InsnList, opcode: Int): Int = { - def isNop(node: asm.tree.AbstractInsnNode): Boolean = - (node.getOpcode == opcode) - insnList.iterator.asScala.count(isNop) - } -} diff --git a/test/files/neg/case-collision.check b/test/files/neg/case-collision.check index 22cf105a4f..7360833a7d 100644 --- a/test/files/neg/case-collision.check +++ b/test/files/neg/case-collision.check @@ -1,12 +1,12 @@ case-collision.scala:5: warning: Class foo.BIPPY differs only in case from foo.Bippy. Such classes will overwrite one another on case-insensitive filesystems. class BIPPY ^ -case-collision.scala:11: warning: Class foo.HyRaX$ differs only in case from foo.Hyrax$. Such classes will overwrite one another on case-insensitive filesystems. -object HyRaX - ^ case-collision.scala:8: warning: Class foo.DINGO$ differs only in case from foo.Dingo$. Such classes will overwrite one another on case-insensitive filesystems. object DINGO ^ +case-collision.scala:11: warning: Class foo.HyRaX$ differs only in case from foo.Hyrax$. Such classes will overwrite one another on case-insensitive filesystems. +object HyRaX + ^ error: No warnings can be incurred under -Xfatal-warnings. three warnings found one error found diff --git a/test/files/neg/case-collision.flags b/test/files/neg/case-collision.flags index 14c1069dee..85d8eb2ba2 100644 --- a/test/files/neg/case-collision.flags +++ b/test/files/neg/case-collision.flags @@ -1 +1 @@ --Ybackend:GenASM -Xfatal-warnings +-Xfatal-warnings diff --git a/test/files/neg/sealed-final-neg.check b/test/files/neg/sealed-final-neg.check index 500d23f49a..e135f38f8b 100644 --- a/test/files/neg/sealed-final-neg.check +++ b/test/files/neg/sealed-final-neg.check @@ -1,4 +1,9 @@ -sealed-final-neg.scala:41: error: expected class or object definition -"Due to SI-6142 this emits no warnings, so we'll just break it until that's fixed." -^ +sealed-final-neg.scala:17: warning: neg1/Foo::bar(I)I is annotated @inline but cannot be inlined: the method is not final and may be overridden. + def f = Foo.mkFoo() bar 10 + ^ +sealed-final-neg.scala:37: warning: neg2/Foo::bar(I)I is annotated @inline but cannot be inlined: the method is not final and may be overridden. + def f = Foo.mkFoo() bar 10 + ^ +error: No warnings can be incurred under -Xfatal-warnings. +two warnings found one error found diff --git a/test/files/neg/sealed-final-neg.flags b/test/files/neg/sealed-final-neg.flags index 3f0fcd2201..01bd79154a 100644 --- a/test/files/neg/sealed-final-neg.flags +++ b/test/files/neg/sealed-final-neg.flags @@ -1 +1 @@ --Xfatal-warnings -Ybackend:GenASM -Yinline-warnings -optimise \ No newline at end of file +-Xfatal-warnings -Yopt:l:project -Yinline-warnings \ No newline at end of file diff --git a/test/files/neg/sealed-final-neg.scala b/test/files/neg/sealed-final-neg.scala index bc25330e13..ec3b199819 100644 --- a/test/files/neg/sealed-final-neg.scala +++ b/test/files/neg/sealed-final-neg.scala @@ -37,5 +37,3 @@ package neg2 { def f = Foo.mkFoo() bar 10 } } - -"Due to SI-6142 this emits no warnings, so we'll just break it until that's fixed." diff --git a/test/files/neg/t3234.check b/test/files/neg/t3234.check deleted file mode 100644 index 8f0d624ed9..0000000000 --- a/test/files/neg/t3234.check +++ /dev/null @@ -1,6 +0,0 @@ -t3234.scala:17: warning: At the end of the day, could not inline @inline-marked method foo3 - println(foo(42) + foo2(11) + foo3(2)) - ^ -error: No warnings can be incurred under -Xfatal-warnings. -one warning found -one error found diff --git a/test/files/neg/t3234.flags b/test/files/neg/t3234.flags deleted file mode 100644 index 406231bd96..0000000000 --- a/test/files/neg/t3234.flags +++ /dev/null @@ -1 +0,0 @@ --Yinline -Yinline-warnings -Xfatal-warnings -Ybackend:GenASM diff --git a/test/files/neg/t3234.scala b/test/files/neg/t3234.scala deleted file mode 100644 index 1553f1fa05..0000000000 --- a/test/files/neg/t3234.scala +++ /dev/null @@ -1,19 +0,0 @@ -trait Trait1 { - // need more work before this one works - // @inline - def foo2(n: Int) = n*n -} - -trait Trait2 { - @inline def foo3(n: Int) = 1 -} - -class Base extends Trait1 { - @inline def foo(n: Int) = n -} - -object Test extends Base with Trait2 { - def main(args: Array[String]) = { - println(foo(42) + foo2(11) + foo3(2)) - } -} \ No newline at end of file diff --git a/test/files/pos/inline-access-levels.flags b/test/files/pos/inline-access-levels.flags index 9bda07eb6c..d34387c651 100644 --- a/test/files/pos/inline-access-levels.flags +++ b/test/files/pos/inline-access-levels.flags @@ -1 +1 @@ --optimise -Ybackend:GenASM -Xfatal-warnings -Yinline-warnings +-Yopt:l:classpath -Xfatal-warnings -Yinline-warnings diff --git a/test/files/pos/inliner2.flags b/test/files/pos/inliner2.flags deleted file mode 100644 index bff4bb8afa..0000000000 --- a/test/files/pos/inliner2.flags +++ /dev/null @@ -1 +0,0 @@ --optimise -Ybackend:GenASM -Xfatal-warnings \ No newline at end of file diff --git a/test/files/pos/inliner2.scala b/test/files/pos/inliner2.scala deleted file mode 100644 index bc83e04312..0000000000 --- a/test/files/pos/inliner2.scala +++ /dev/null @@ -1,57 +0,0 @@ -// This isn't actually testing much, because no warning is emitted in versions -// before the fix which comes with this because the method isn't even considered -// for inlining due to the bug. -class A { - private var debug = false - @inline private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = - if (cond) ifPart else elsePart - - final def bob1() = ifelse(debug, 1, 2) - final def bob2() = if (debug) 1 else 2 -} -// Cool: -// -// % ls -1 /tmp/2901/ -// A$$anonfun$bob1$1.class -// A$$anonfun$bob1$2.class -// A$$anonfun$bob1$3.class -// A.class -// % ls -1 /tmp/trunk -// A.class -// -// Observations: -// -// (1) The inlined version accesses the field: the explicit one calls the accessor. -// (2) The inlined version fails to eliminate boxing. With reference types it emits -// an unneeded checkcast. -// (3) The private var debug is mangled to A$$debug, but after inlining it is never accessed -// from outside of the class and doesn't need mangling. -// (4) We could forego emitting bytecode for ifelse entirely if it has been -// inlined at all sites. -// -// Generated bytecode for the above: -// -// public final int bob1(); -// Code: -// Stack=1, Locals=1, Args_size=1 -// 0: aload_0 -// 1: getfield #11; //Field A$$debug:Z -// 4: ifeq 14 -// 7: iconst_1 -// 8: invokestatic #41; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; -// 11: goto 18 -// 14: iconst_2 -// 15: invokestatic #41; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; -// 18: invokestatic #45; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I -// 21: ireturn -// -// public final int bob2(); -// Code: -// Stack=1, Locals=1, Args_size=1 -// 0: aload_0 -// 1: invokevirtual #48; //Method A$$debug:()Z -// 4: ifeq 11 -// 7: iconst_1 -// 8: goto 12 -// 11: iconst_2 -// 12: ireturn diff --git a/test/files/pos/sealed-final.flags b/test/files/pos/sealed-final.flags deleted file mode 100644 index 63f5e65527..0000000000 --- a/test/files/pos/sealed-final.flags +++ /dev/null @@ -1 +0,0 @@ --Xfatal-warnings -Yinline-warnings -Ybackend:GenASM -optimise \ No newline at end of file diff --git a/test/files/pos/sealed-final.scala b/test/files/pos/sealed-final.scala deleted file mode 100644 index bdedb5c1f6..0000000000 --- a/test/files/pos/sealed-final.scala +++ /dev/null @@ -1,14 +0,0 @@ -sealed abstract class Foo { - @inline def bar(x: Int) = x + 1 -} -object Foo { - def mkFoo(): Foo = new Baz2 -} - -object Baz1 extends Foo -final class Baz2 extends Foo - -object Test { - // bar should be inlined now - def f = Foo.mkFoo() bar 10 -} diff --git a/test/files/pos/t3234.flags b/test/files/pos/t3234.flags new file mode 100644 index 0000000000..78726f64f7 --- /dev/null +++ b/test/files/pos/t3234.flags @@ -0,0 +1 @@ +-Yopt:l:project -Yinline-warnings -Xfatal-warnings diff --git a/test/files/pos/t3234.scala b/test/files/pos/t3234.scala new file mode 100644 index 0000000000..c3b7366db8 --- /dev/null +++ b/test/files/pos/t3234.scala @@ -0,0 +1,17 @@ +trait Trait1 { + @inline def foo2(n: Int) = n*n +} + +trait Trait2 { + @inline def foo3(n: Int) = 1 +} + +class Base extends Trait1 { + @inline def foo(n: Int) = n +} + +object Test extends Base with Trait2 { + def main(args: Array[String]) = { + println(foo(42) + foo2(11) + foo3(2)) + } +} \ No newline at end of file diff --git a/test/files/pos/t3420.flags b/test/files/pos/t3420.flags index bff4bb8afa..4fbafb7e80 100644 --- a/test/files/pos/t3420.flags +++ b/test/files/pos/t3420.flags @@ -1 +1 @@ --optimise -Ybackend:GenASM -Xfatal-warnings \ No newline at end of file +-Yopt-warnings Yopt:l:project -Xfatal-warnings \ No newline at end of file diff --git a/test/files/pos/t8410.flags b/test/files/pos/t8410.flags index 2f32e3b26a..01fc9cd3ac 100644 --- a/test/files/pos/t8410.flags +++ b/test/files/pos/t8410.flags @@ -1 +1 @@ --optimise -Ybackend:GenASM -Xfatal-warnings -deprecation:false -Yinline-warnings:false +-Yopt:l:project -Xfatal-warnings -deprecation:false -Yinline-warnings:false diff --git a/test/files/run/constant-optimization.flags b/test/files/run/constant-optimization.flags deleted file mode 100644 index 6c9965e749..0000000000 --- a/test/files/run/constant-optimization.flags +++ /dev/null @@ -1 +0,0 @@ --optimise -Ybackend:GenASM diff --git a/test/files/run/dead-code-elimination.flags b/test/files/run/dead-code-elimination.flags deleted file mode 100644 index b9bb09167e..0000000000 --- a/test/files/run/dead-code-elimination.flags +++ /dev/null @@ -1 +0,0 @@ --optimize -Ybackend:GenASM diff --git a/test/files/run/delambdafy-specialized.check b/test/files/run/delambdafy-specialized.check deleted file mode 100644 index c6903b9e29..0000000000 --- a/test/files/run/delambdafy-specialized.check +++ /dev/null @@ -1 +0,0 @@ -scala.runtime.AbstractFunction1$mcII$sp diff --git a/test/files/run/delambdafy-specialized.flags b/test/files/run/delambdafy-specialized.flags deleted file mode 100644 index d6278aa940..0000000000 --- a/test/files/run/delambdafy-specialized.flags +++ /dev/null @@ -1 +0,0 @@ --Ydelambdafy:method -Ybackend:GenASM diff --git a/test/files/run/delambdafy-specialized.scala b/test/files/run/delambdafy-specialized.scala deleted file mode 100644 index 634d4e490b..0000000000 --- a/test/files/run/delambdafy-specialized.scala +++ /dev/null @@ -1,6 +0,0 @@ -object Test { - def main(args: Array[String]): Unit = { - val f = (x: Int) => -x - println(f.getClass.getSuperclass.getName) - } -} diff --git a/test/files/run/elidable-opt.flags b/test/files/run/elidable-opt.flags index 6537d2f57a..93fd3d5317 100644 --- a/test/files/run/elidable-opt.flags +++ b/test/files/run/elidable-opt.flags @@ -1 +1 @@ --optimise -Ybackend:GenASM -Xelide-below 900 +-Xelide-below 900 diff --git a/test/files/run/finalvar.flags b/test/files/run/finalvar.flags index 8d9be3d62e..a8c7600a03 100644 --- a/test/files/run/finalvar.flags +++ b/test/files/run/finalvar.flags @@ -1 +1 @@ --Yoverride-vars -Yinline -Ybackend:GenASM \ No newline at end of file +-Yoverride-vars -Yopt:l:project \ No newline at end of file diff --git a/test/files/run/icode-reader-dead-code.scala b/test/files/run/icode-reader-dead-code.scala index 535ef2a2e2..df31219dd5 100644 --- a/test/files/run/icode-reader-dead-code.scala +++ b/test/files/run/icode-reader-dead-code.scala @@ -36,7 +36,7 @@ object Test extends DirectTest { // If inlining fails, the compiler will issue an inliner warning that is not present in the // check file - compileString(newCompiler("-usejavacp", "-optimise", "-Ybackend:GenASM"))(bCode) + compileString(newCompiler("-usejavacp", "-Yopt:l:classpath"))(bCode) } def readClass(file: String) = { diff --git a/test/files/run/inline-ex-handlers.check b/test/files/run/inline-ex-handlers.check deleted file mode 100644 index 36fc2eefa4..0000000000 --- a/test/files/run/inline-ex-handlers.check +++ /dev/null @@ -1,492 +0,0 @@ ---- a -+++ b -@@ -171,5 +171,5 @@ - def productElement(x$1: Int (INT)): Object { -- locals: value x$1, value x1 -+ locals: value x$1, value x1, variable boxed1 - startBlock: 1 -- blocks: [1,2,3,4] -+ blocks: [1,3,4] - -@@ -186,2 +186,4 @@ - 92 LOAD_LOCAL(value x$1) -+ 92 STORE_LOCAL(variable boxed1) -+ 92 LOAD_LOCAL(variable boxed1) - 92 BOX INT -@@ -194,5 +196,2 @@ - 92 CALL_METHOD MyException.message (dynamic) -- 92 JUMP 2 -- -- 2: - 92 RETURN(REF(class Object)) -@@ -246,3 +245,3 @@ - startBlock: 1 -- blocks: [1,2,3,4,5,6,7,8,11,12,13,14,15,16,17,18] -+ blocks: [1,2,3,4,5,6,8,11,12,13,14,15,16,17,18] - -@@ -257,5 +256,2 @@ - 92 SCOPE_ENTER value x1 -- 92 JUMP 7 -- -- 7: - 92 LOAD_LOCAL(value x1) -@@ -408,5 +404,5 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, variable result, value ex6, value x4, value x5, value message, value x -+ locals: value args, variable result, value ex6, value x4, value x5, value x - startBlock: 1 -- blocks: [1,2,3,4,5,8,10,11,13] -+ blocks: [1,2,3,5,8,10,11,13,14] - -@@ -434,4 +430,13 @@ - 103 CALL_METHOD MyException. (static-instance) -- 103 THROW(MyException) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 14 - -+ 14: -+ 101 LOAD_LOCAL(value ex6) -+ 101 STORE_LOCAL(value x4) -+ 101 SCOPE_ENTER value x4 -+ 106 LOAD_LOCAL(value x4) -+ 106 IS_INSTANCE REF(class MyException) -+ 106 CZJUMP (BOOL)NE ? 5 : 8 -+ - 13: -@@ -447,5 +452,2 @@ - 101 SCOPE_ENTER value x4 -- 101 JUMP 4 -- -- 4: - 106 LOAD_LOCAL(value x4) -@@ -459,8 +461,5 @@ - 106 SCOPE_ENTER value x5 -- 106 LOAD_LOCAL(value x5) -- 106 CALL_METHOD MyException.message (dynamic) -- 106 STORE_LOCAL(value message) -- 106 SCOPE_ENTER value message - 106 LOAD_MODULE object Predef -- 106 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 106 CALL_METHOD MyException.message (dynamic) - 106 CALL_METHOD scala.Predef.println (dynamic) -@@ -536,3 +535,3 @@ - startBlock: 1 -- blocks: [1,2,3,4,6,7,9,10] -+ blocks: [1,3,4,6,7,9,10,11,12,13] - -@@ -565,4 +564,9 @@ - 306 CALL_METHOD MyException. (static-instance) -- 306 THROW(MyException) -+ ? JUMP 11 - -+ 11: -+ ? LOAD_LOCAL(variable monitor4) -+ 305 MONITOR_EXIT -+ ? JUMP 12 -+ - 9: -@@ -571,3 +575,3 @@ - 305 MONITOR_EXIT -- ? THROW(Throwable) -+ ? JUMP 12 - -@@ -577,4 +581,11 @@ - 304 MONITOR_EXIT -- ? THROW(Throwable) -+ ? STORE_LOCAL(value t) -+ ? JUMP 13 - -+ 12: -+ ? LOAD_LOCAL(variable monitor3) -+ 304 MONITOR_EXIT -+ ? STORE_LOCAL(value t) -+ ? JUMP 13 -+ - 3: -@@ -591,5 +602,14 @@ - 310 CALL_METHOD scala.Predef.println (dynamic) -- 310 JUMP 2 -+ 300 RETURN(UNIT) - -- 2: -+ 13: -+ 310 LOAD_MODULE object Predef -+ 310 CALL_PRIMITIVE(StartConcat) -+ 310 CONSTANT("Caught crash: ") -+ 310 CALL_PRIMITIVE(StringConcat(REF(class String))) -+ 310 LOAD_LOCAL(value t) -+ 310 CALL_METHOD java.lang.Throwable.toString (dynamic) -+ 310 CALL_PRIMITIVE(StringConcat(REF(class String))) -+ 310 CALL_PRIMITIVE(EndConcat) -+ 310 CALL_METHOD scala.Predef.println (dynamic) - 300 RETURN(UNIT) -@@ -601,6 +621,6 @@ - with finalizer: null -- catch (Throwable) in Vector(7, 9, 10) starting at: 6 -+ catch (Throwable) in Vector(7, 9, 10, 11) starting at: 6 - consisting of blocks: List(6) - with finalizer: null -- catch (Throwable) in Vector(4, 6, 7, 9, 10) starting at: 3 -+ catch (Throwable) in Vector(4, 6, 7, 9, 10, 11, 12) starting at: 3 - consisting of blocks: List(3) -@@ -636,3 +656,3 @@ - startBlock: 1 -- blocks: [1,3,4,5,6,8,9] -+ blocks: [1,3,4,5,6,8,9,10,11] - -@@ -660,4 +680,10 @@ - 78 CALL_METHOD java.lang.IllegalArgumentException. (static-instance) -- 78 THROW(IllegalArgumentException) -+ ? STORE_LOCAL(value e) -+ ? JUMP 10 - -+ 10: -+ 81 LOAD_LOCAL(value e) -+ ? STORE_LOCAL(variable exc1) -+ ? JUMP 11 -+ - 8: -@@ -686,3 +712,4 @@ - 81 LOAD_LOCAL(value e) -- 81 THROW(Exception) -+ ? STORE_LOCAL(variable exc1) -+ ? JUMP 11 - -@@ -703,2 +730,15 @@ - -+ 11: -+ 83 LOAD_MODULE object Predef -+ 83 CONSTANT("finally") -+ 83 CALL_METHOD scala.Predef.println (dynamic) -+ 84 LOAD_LOCAL(variable result) -+ 84 CONSTANT(1) -+ 84 CALL_PRIMITIVE(Arithmetic(SUB,INT)) -+ 84 CONSTANT(2) -+ 84 CALL_PRIMITIVE(Arithmetic(DIV,INT)) -+ 84 STORE_LOCAL(variable result) -+ 84 LOAD_LOCAL(variable exc1) -+ 84 THROW(Throwable) -+ - } -@@ -708,3 +748,3 @@ - with finalizer: null -- catch () in Vector(4, 5, 6, 8) starting at: 3 -+ catch () in Vector(4, 5, 6, 8, 10) starting at: 3 - consisting of blocks: List(3) -@@ -732,5 +772,5 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, variable result, value ex6, variable exc2, value x4, value x5, value message, value x, value ex6, value x4, value x5, value message, value x -+ locals: value args, variable result, value ex6, variable exc2, value x4, value x5, value x, value ex6, value x4, value x5, value x - startBlock: 1 -- blocks: [1,3,4,5,6,9,13,14,15,18,20,21,23,24] -+ blocks: [1,3,4,5,6,9,13,14,15,18,20,21,23,24,25,26,27] - -@@ -758,4 +798,11 @@ - 172 CALL_METHOD MyException. (static-instance) -- 172 THROW(MyException) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 25 - -+ 25: -+ 170 LOAD_LOCAL(value ex6) -+ 170 STORE_LOCAL(value x4) -+ 170 SCOPE_ENTER value x4 -+ 170 JUMP 14 -+ - 23: -@@ -798,8 +845,5 @@ - 175 SCOPE_ENTER value x5 -- 175 LOAD_LOCAL(value x5) -- 175 CALL_METHOD MyException.message (dynamic) -- 175 STORE_LOCAL(value message) -- 175 SCOPE_ENTER value message - 176 LOAD_MODULE object Predef -- 176 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 176 CALL_METHOD MyException.message (dynamic) - 176 CALL_METHOD scala.Predef.println (dynamic) -@@ -807,5 +851,7 @@ - 177 DUP(REF(class MyException)) -- 177 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 177 CALL_METHOD MyException.message (dynamic) - 177 CALL_METHOD MyException. (static-instance) -- 177 THROW(MyException) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 26 - -@@ -813,3 +859,4 @@ - 170 LOAD_LOCAL(value ex6) -- 170 THROW(Throwable) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 26 - -@@ -823,2 +870,8 @@ - -+ 26: -+ 169 LOAD_LOCAL(value ex6) -+ 169 STORE_LOCAL(value x4) -+ 169 SCOPE_ENTER value x4 -+ 169 JUMP 5 -+ - 5: -@@ -833,8 +886,5 @@ - 180 SCOPE_ENTER value x5 -- 180 LOAD_LOCAL(value x5) -- 180 CALL_METHOD MyException.message (dynamic) -- 180 STORE_LOCAL(value message) -- 180 SCOPE_ENTER value message - 181 LOAD_MODULE object Predef -- 181 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 181 CALL_METHOD MyException.message (dynamic) - 181 CALL_METHOD scala.Predef.println (dynamic) -@@ -842,5 +892,7 @@ - 182 DUP(REF(class MyException)) -- 182 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 182 CALL_METHOD MyException.message (dynamic) - 182 CALL_METHOD MyException. (static-instance) -- 182 THROW(MyException) -+ ? STORE_LOCAL(variable exc2) -+ ? JUMP 27 - -@@ -848,3 +900,4 @@ - 169 LOAD_LOCAL(value ex6) -- 169 THROW(Throwable) -+ ? STORE_LOCAL(variable exc2) -+ ? JUMP 27 - -@@ -865,2 +918,15 @@ - -+ 27: -+ 184 LOAD_MODULE object Predef -+ 184 CONSTANT("finally") -+ 184 CALL_METHOD scala.Predef.println (dynamic) -+ 185 LOAD_LOCAL(variable result) -+ 185 CONSTANT(1) -+ 185 CALL_PRIMITIVE(Arithmetic(SUB,INT)) -+ 185 CONSTANT(2) -+ 185 CALL_PRIMITIVE(Arithmetic(DIV,INT)) -+ 185 STORE_LOCAL(variable result) -+ 185 LOAD_LOCAL(variable exc2) -+ 185 THROW(Throwable) -+ - } -@@ -870,6 +936,6 @@ - with finalizer: null -- catch (Throwable) in Vector(13, 14, 15, 18, 20, 21, 23) starting at: 4 -+ catch (Throwable) in Vector(13, 14, 15, 18, 20, 21, 23, 25) starting at: 4 - consisting of blocks: List(9, 8, 6, 5, 4) - with finalizer: null -- catch () in Vector(4, 5, 6, 9, 13, 14, 15, 18, 20, 21, 23) starting at: 3 -+ catch () in Vector(4, 5, 6, 9, 13, 14, 15, 18, 20, 21, 23, 25, 26) starting at: 3 - consisting of blocks: List(3) -@@ -897,5 +963,5 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, variable result, value e, value ex6, value x4, value x5, value message, value x -+ locals: value args, variable result, value e, value ex6, value x4, value x5, value x - startBlock: 1 -- blocks: [1,2,3,6,7,8,11,13,14,16] -+ blocks: [1,2,3,6,7,8,11,13,14,16,17] - -@@ -923,4 +989,11 @@ - 124 CALL_METHOD MyException. (static-instance) -- 124 THROW(MyException) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 17 - -+ 17: -+ 122 LOAD_LOCAL(value ex6) -+ 122 STORE_LOCAL(value x4) -+ 122 SCOPE_ENTER value x4 -+ 122 JUMP 7 -+ - 16: -@@ -948,8 +1021,5 @@ - 127 SCOPE_ENTER value x5 -- 127 LOAD_LOCAL(value x5) -- 127 CALL_METHOD MyException.message (dynamic) -- 127 STORE_LOCAL(value message) -- 127 SCOPE_ENTER value message - 127 LOAD_MODULE object Predef -- 127 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 127 CALL_METHOD MyException.message (dynamic) - 127 CALL_METHOD scala.Predef.println (dynamic) -@@ -982,3 +1052,3 @@ - with finalizer: null -- catch (IllegalArgumentException) in Vector(6, 7, 8, 11, 13, 14, 16) starting at: 3 -+ catch (IllegalArgumentException) in Vector(6, 7, 8, 11, 13, 14, 16, 17) starting at: 3 - consisting of blocks: List(3) -@@ -1006,5 +1076,5 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, variable result, value ex6, value x4, value x5, value message, value x, value e -+ locals: value args, variable result, value ex6, value x4, value x5, value x, value e - startBlock: 1 -- blocks: [1,2,3,4,5,8,12,13,14,16] -+ blocks: [1,2,3,5,8,12,13,14,16,17] - -@@ -1032,4 +1102,13 @@ - 148 CALL_METHOD MyException. (static-instance) -- 148 THROW(MyException) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 17 - -+ 17: -+ 145 LOAD_LOCAL(value ex6) -+ 145 STORE_LOCAL(value x4) -+ 145 SCOPE_ENTER value x4 -+ 154 LOAD_LOCAL(value x4) -+ 154 IS_INSTANCE REF(class MyException) -+ 154 CZJUMP (BOOL)NE ? 5 : 8 -+ - 16: -@@ -1053,5 +1132,2 @@ - 145 SCOPE_ENTER value x4 -- 145 JUMP 4 -- -- 4: - 154 LOAD_LOCAL(value x4) -@@ -1065,8 +1141,5 @@ - 154 SCOPE_ENTER value x5 -- 154 LOAD_LOCAL(value x5) -- 154 CALL_METHOD MyException.message (dynamic) -- 154 STORE_LOCAL(value message) -- 154 SCOPE_ENTER value message - 154 LOAD_MODULE object Predef -- 154 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 154 CALL_METHOD MyException.message (dynamic) - 154 CALL_METHOD scala.Predef.println (dynamic) -@@ -1287,3 +1360,3 @@ - startBlock: 1 -- blocks: [1,2,3,4,5,7] -+ blocks: [1,2,3,4,5,7,8] - -@@ -1311,4 +1384,11 @@ - 38 CALL_METHOD java.lang.IllegalArgumentException. (static-instance) -- 38 THROW(IllegalArgumentException) -+ ? STORE_LOCAL(value e) -+ ? JUMP 8 - -+ 8: -+ 42 LOAD_MODULE object Predef -+ 42 CONSTANT("IllegalArgumentException") -+ 42 CALL_METHOD scala.Predef.println (dynamic) -+ 42 JUMP 2 -+ - 7: -@@ -1358,5 +1438,5 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, variable result, value ex6, value x4, value x5, value message, value x -+ locals: value args, variable result, value ex6, value x4, value x5, value x - startBlock: 1 -- blocks: [1,2,3,4,5,8,10,11,13,14,16] -+ blocks: [1,2,3,5,8,10,11,13,14,16,17] - -@@ -1384,3 +1464,4 @@ - 203 CALL_METHOD MyException. (static-instance) -- 203 THROW(MyException) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 17 - -@@ -1404,4 +1485,13 @@ - 209 CALL_METHOD MyException. (static-instance) -- 209 THROW(MyException) -+ ? STORE_LOCAL(value ex6) -+ ? JUMP 17 - -+ 17: -+ 200 LOAD_LOCAL(value ex6) -+ 200 STORE_LOCAL(value x4) -+ 200 SCOPE_ENTER value x4 -+ 212 LOAD_LOCAL(value x4) -+ 212 IS_INSTANCE REF(class MyException) -+ 212 CZJUMP (BOOL)NE ? 5 : 8 -+ - 16: -@@ -1417,5 +1507,2 @@ - 200 SCOPE_ENTER value x4 -- 200 JUMP 4 -- -- 4: - 212 LOAD_LOCAL(value x4) -@@ -1429,8 +1516,5 @@ - 212 SCOPE_ENTER value x5 -- 212 LOAD_LOCAL(value x5) -- 212 CALL_METHOD MyException.message (dynamic) -- 212 STORE_LOCAL(value message) -- 212 SCOPE_ENTER value message - 213 LOAD_MODULE object Predef -- 213 LOAD_LOCAL(value message) -+ ? LOAD_LOCAL(value x5) -+ 213 CALL_METHOD MyException.message (dynamic) - 213 CALL_METHOD scala.Predef.println (dynamic) -@@ -1478,3 +1562,3 @@ - startBlock: 1 -- blocks: [1,2,3,4,5,7] -+ blocks: [1,2,3,4,5,7,8] - -@@ -1502,4 +1586,11 @@ - 58 CALL_METHOD java.lang.IllegalArgumentException. (static-instance) -- 58 THROW(IllegalArgumentException) -+ ? STORE_LOCAL(value e) -+ ? JUMP 8 - -+ 8: -+ 62 LOAD_MODULE object Predef -+ 62 CONSTANT("RuntimeException") -+ 62 CALL_METHOD scala.Predef.println (dynamic) -+ 62 JUMP 2 -+ - 7: -@@ -1551,3 +1642,3 @@ - startBlock: 1 -- blocks: [1,3,4] -+ blocks: [1,3,4,5] - -@@ -1571,4 +1662,9 @@ - 229 CALL_METHOD MyException. (static-instance) -- 229 THROW(MyException) -+ ? JUMP 5 - -+ 5: -+ ? LOAD_LOCAL(variable monitor1) -+ 228 MONITOR_EXIT -+ 228 THROW(Throwable) -+ - 3: -@@ -1577,3 +1673,3 @@ - 228 MONITOR_EXIT -- ? THROW(Throwable) -+ 228 THROW(Throwable) - -@@ -1605,5 +1701,5 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, variable result, variable monitor2, variable monitorResult1 -+ locals: value exception$1, value args, variable result, variable monitor2, variable monitorResult1 - startBlock: 1 -- blocks: [1,3,4] -+ blocks: [1,3,4,5] - -@@ -1630,4 +1726,12 @@ - 245 CALL_METHOD MyException. (static-instance) -- 245 THROW(MyException) -+ ? STORE_LOCAL(value exception$1) -+ ? DROP ConcatClass -+ ? LOAD_LOCAL(value exception$1) -+ ? JUMP 5 - -+ 5: -+ ? LOAD_LOCAL(variable monitor2) -+ 244 MONITOR_EXIT -+ 244 THROW(Throwable) -+ - 3: -@@ -1636,3 +1740,3 @@ - 244 MONITOR_EXIT -- ? THROW(Throwable) -+ 244 THROW(Throwable) - diff --git a/test/files/run/inline-ex-handlers.scala b/test/files/run/inline-ex-handlers.scala deleted file mode 100644 index 4095d54e36..0000000000 --- a/test/files/run/inline-ex-handlers.scala +++ /dev/null @@ -1,329 +0,0 @@ -import scala.tools.partest.IcodeComparison - -object Test extends IcodeComparison { - override def printIcodeAfterPhase = "inlinehandlers"; override def extraSettings: String = super.extraSettings + " -Ybackend:GenASM" // same line to minimize check file changs -} - -import scala.util.Random._ - -/** There should be no inlining taking place in this class */ -object TestInlineHandlersNoInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersNoInline") - var result = -1 - - try { - if (nextInt % 2 == 0) - throw new IllegalArgumentException("something") - result = 1 - } catch { - case e: StackOverflowError => - println("Stack overflow") - } - - result - } -} - -/** Just a simple inlining should take place in this class */ -object TestInlineHandlersSimpleInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersSimpleInline") - var result = -1 - - try { - if (nextInt % 2 == 0) - throw new IllegalArgumentException("something") - result = 1 - } catch { - case e: IllegalArgumentException => - println("IllegalArgumentException") - } - - result - } -} - -/** Inlining should take place because the handler is taking a superclass of the exception thrown */ -object TestInlineHandlersSubclassInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersSubclassInline") - var result = -1 - - try { - if (nextInt % 2 == 0) - throw new IllegalArgumentException("something") - result = 1 - } catch { - case e: RuntimeException => - println("RuntimeException") - } - - result - } -} - -/** For this class, the finally handler should be inlined */ -object TestInlineHandlersFinallyInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersFinallyInline") - var result = -1 - - try { - if (nextInt % 2 == 0) - throw new IllegalArgumentException("something") - result = 1 - } catch { - case e: Exception => throw e - } finally { - println("finally") - result = (result - 1) / 2 - } - - result - } -} - - -case class MyException(message: String) extends RuntimeException(message) - -/** For this class, we test inlining for a case class error */ -object TestInlineHandlersCaseClassExceptionInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersCaseClassExceptionInline") - var result = -1 - - try { - if (nextInt % 2 == 0) - throw new MyException("something") - result = 1 - } catch { - case MyException(message) => println(message) - } - - result - } -} - - -/** For this class, inline should take place in the inner handler */ -object TestInlineHandlersNestedHandlerInnerInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersNestedHandlersInnerInline") - var result = -1 - - try { - try { - if (nextInt % 2 == 0) - throw new MyException("something") - result = 1 - } catch { - case MyException(message) => println(message) - } - } catch { - case e: IllegalArgumentException => println("IllegalArgumentException") - } - - result - } -} - - -/** For this class, inline should take place in the outer handler */ -object TestInlineHandlersNestedHandlerOuterInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersNestedHandlersOuterInline") - var result = -1 - - try { - try { - if (nextInt % 2 == 0) - throw new MyException("something") - result = 1 - } catch { - case e: IllegalArgumentException => println("IllegalArgumentException") - } - } catch { - case MyException(message) => println(message) - } - - result - } -} - - -/** For this class, inline should take place in the all handlers (inner, outer and finally) */ -object TestInlineHandlersNestedHandlerAllInline { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersNestedHandlersOuterInline") - var result = -1 - - try { - try { - if (nextInt % 2 == 0) - throw new MyException("something") - result = 1 - } catch { - case MyException(message) => - println(message) - throw MyException(message) - } - } catch { - case MyException(message) => - println(message) - throw MyException(message) - } finally { - println("finally") - result = (result - 1) / 2 - } - - result - } -} - - -/** This class is meant to test whether the inline handler is copied only once for multiple inlines */ -object TestInlineHandlersSingleCopy { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersSingleCopy") - var result = -1 - - try { - - if (nextInt % 2 == 0) - throw new MyException("something") - - println("A side effect in the middle") - result = 3 // another one - - if (nextInt % 3 == 2) - throw new MyException("something else") - result = 1 - } catch { - case MyException(message) => - println(message) - } - - result - } -} - -/** This should test the special exception handler for synchronized blocks */ -object TestInlineHandlersSynchronized { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersSynchronized") - var result = "hello" - - // any exception thrown here will be caught by a default handler that does MONTIOR_EXIT on result :) - result.synchronized { - throw MyException(result) - } - - result.length - } -} - -/** This should test the special exception handler for synchronized blocks with stack */ -object TestInlineHandlersSynchronizedWithStack { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersSynchronizedWithStack") - var result = "hello" - - // any exception thrown here will be caught by a default handler that does MONTIOR_EXIT on result :) - result = "abc" + result.synchronized { - throw MyException(result) - } - - result.length - } -} - -/** This test should trigger a bug in the dead code elimination phase - it actually crashes ICodeCheckers -object TestInlineHandlersSynchronizedWithStackDoubleThrow { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersSynchronizedWithStackDoubleThrow") - var result = "a" - - // any exception thrown here will be caught by a default handler that does MONTIOR_EXIT on result :) - result += result.synchronized { throw MyException(result) } - result += result.synchronized { throw MyException(result) } - - result.length - } -} -*/ - -/** This test should check the preciseness of the inliner: it should not do any inlining here -* as it is not able to discern between the different exceptions -*/ -object TestInlineHandlersPreciseness { - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersCorrectHandler") - - try { - val exception: Throwable = - if (scala.util.Random.nextInt % 2 == 0) - new IllegalArgumentException("even") - else - new StackOverflowError("odd") - throw exception - } catch { - case e: IllegalArgumentException => - println("Correct, IllegalArgumentException") - case e: StackOverflowError => - println("Correct, StackOverflowException") - case t: Throwable => - println("WROOOONG, not Throwable!") - } - } -} - -/** This check should verify that the double no-local exception handler is duplicated correctly */ -object TestInlineHandlersDoubleNoLocal { - - val a1: String = "a" - val a2: String = "b" - - def main(args: Array[String]): Unit = { - println("TestInlineHandlersDoubleNoLocal") - - try { - a1.synchronized { - a2. synchronized { - throw new MyException("crash") - } - } - } catch { - case t: Throwable => println("Caught crash: " + t.toString) - } - - /* try { - val exception: Throwable = - if (scala.util.Random.nextInt % 2 == 0) - new IllegalArgumentException("even") - else - new StackOverflowError("odd") - throw exception - } catch { - case e: IllegalArgumentException => - println("Correct, IllegalArgumentException") - case e: StackOverflowError => - println("Correct, StackOverflowException") - case t: Throwable => - println("WROOOONG, not Throwable!") - }*/ - } -} diff --git a/test/files/run/optimizer-array-load.flags b/test/files/run/optimizer-array-load.flags deleted file mode 100644 index 99bd6c895d..0000000000 --- a/test/files/run/optimizer-array-load.flags +++ /dev/null @@ -1 +0,0 @@ --optimise -Ybackend:GenASM \ No newline at end of file diff --git a/test/files/run/sbt-icode-interface.scala b/test/files/run/sbt-icode-interface.scala index 84d38cc65a..7cd2de5c00 100644 --- a/test/files/run/sbt-icode-interface.scala +++ b/test/files/run/sbt-icode-interface.scala @@ -9,34 +9,32 @@ object Test extends DirectTest { """.trim def show() { - for (b <- List("GenASM", "GenBCode")) { - val global = newCompiler("-usejavacp", s"-Ybackend:$b") - import global._ - val r = new Run - r.compileSources(newSourceFile(code) :: Nil) - - val results = collection.mutable.Buffer[(Boolean, String)]() + val global = newCompiler("-usejavacp") + import global._ + val r = new Run + r.compileSources(newSourceFile(code) :: Nil) - // Nailing down defacto compiler API from SBT's usage - // https://github.com/sbt/sbt/blob/adb41611cf73260938274915d8462d924df200c8/compile/interface/src/main/scala/xsbt/Analyzer.scala#L29-L41 - def isTopLevelModule(sym: Symbol) = sym.isTopLevel && sym.isModule - for (unit <- currentRun.units if !unit.isJava) { - val sourceFile = unit.source.file.file - for (iclass <- unit.icode) { - val sym = iclass.symbol - def addGenerated(separatorRequired: Boolean) { - results += (separatorRequired -> sym.fullName) - } - if (sym.isModuleClass && !sym.isImplClass) { - if (isTopLevelModule(sym) && sym.companionClass == NoSymbol) - addGenerated(false) - addGenerated(true) - } else - addGenerated(false) + val results = collection.mutable.Buffer[(Boolean, String)]() + + // Nailing down defacto compiler API from SBT's usage + // https://github.com/sbt/sbt/blob/adb41611cf73260938274915d8462d924df200c8/compile/interface/src/main/scala/xsbt/Analyzer.scala#L29-L41 + def isTopLevelModule(sym: Symbol) = sym.isTopLevel && sym.isModule + for (unit <- currentRun.units if !unit.isJava) { + val sourceFile = unit.source.file.file + for (iclass <- unit.icode) { + val sym = iclass.symbol + def addGenerated(separatorRequired: Boolean) { + results += (separatorRequired -> sym.fullName) } + if (sym.isModuleClass && !sym.isImplClass) { + if (isTopLevelModule(sym) && sym.companionClass == NoSymbol) + addGenerated(false) + addGenerated(true) + } else + addGenerated(false) } - val expected = List((false, "C"), (true, "O"), (false, "C$D")) - assert(results.toList == expected, b + ": " + results.toList) } + val expected = List((false, "C"), (true, "O"), (false, "C$D")) + assert(results.toList == expected, s"expected: $expected, actual: ${results.toList}") } } diff --git a/test/files/run/synchronized.flags b/test/files/run/synchronized.flags index b9bb09167e..19c578e4ad 100644 --- a/test/files/run/synchronized.flags +++ b/test/files/run/synchronized.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM +-Yopt:l:project diff --git a/test/files/run/t3509.flags b/test/files/run/t3509.flags index 9c59981aa9..422d6be431 100644 --- a/test/files/run/t3509.flags +++ b/test/files/run/t3509.flags @@ -1 +1 @@ --Yinline -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath \ No newline at end of file diff --git a/test/files/run/t3569.flags b/test/files/run/t3569.flags index 9c59981aa9..422d6be431 100644 --- a/test/files/run/t3569.flags +++ b/test/files/run/t3569.flags @@ -1 +1 @@ --Yinline -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath \ No newline at end of file diff --git a/test/files/run/t4285.flags b/test/files/run/t4285.flags index 99bd6c895d..422d6be431 100644 --- a/test/files/run/t4285.flags +++ b/test/files/run/t4285.flags @@ -1 +1 @@ --optimise -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath \ No newline at end of file diff --git a/test/files/run/t4935.flags b/test/files/run/t4935.flags index b9bb09167e..65caa3736e 100644 --- a/test/files/run/t4935.flags +++ b/test/files/run/t4935.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM +-Yopt:l:classpath diff --git a/test/files/run/t5313.check b/test/files/run/t5313.check deleted file mode 100644 index 7a48b2b711..0000000000 --- a/test/files/run/t5313.check +++ /dev/null @@ -1,12 +0,0 @@ -STORE_LOCAL(variable kept1) -STORE_LOCAL(value result) -STORE_LOCAL(variable kept1) -STORE_LOCAL(variable kept2) -STORE_LOCAL(value kept3) -STORE_LOCAL(variable kept2) -STORE_LOCAL(variable kept4) -STORE_LOCAL(variable kept4) -STORE_LOCAL(variable kept5) -STORE_LOCAL(variable kept5) -STORE_LOCAL(variable kept6) -STORE_LOCAL(variable kept6) diff --git a/test/files/run/t5313.scala b/test/files/run/t5313.scala deleted file mode 100644 index 24ed334816..0000000000 --- a/test/files/run/t5313.scala +++ /dev/null @@ -1,54 +0,0 @@ -import scala.tools.partest.IcodeComparison - -object Test extends IcodeComparison { - override def printIcodeAfterPhase = "dce" - - override def extraSettings: String = super.extraSettings + " -optimize -Ybackend:GenASM" - - override def code = - """class Foo { - def randomBoolean = scala.util.Random.nextInt % 2 == 0 - def bar = { - var kept1 = new Object - val result = new java.lang.ref.WeakReference(kept1) - kept1 = null // we can't eliminate this assignment because result can observe - // when the object has no more references. See SI-5313 - kept1 = new Object // but we can eliminate this one because kept1 has already been clobbered - var erased2 = null // we can eliminate this store because it's never used - val erased3 = erased2 // and this - var erased4 = erased2 // and this - val erased5 = erased4 // and this - var kept2: Object = new Object // ultimately can't be eliminated - while(randomBoolean) { - val kept3 = kept2 - kept2 = null // this can't, because it clobbers kept2, which is used - erased4 = null // safe to eliminate - println(kept3) - } - var kept4 = new Object // have to keep, it's used - try - println(kept4) - catch { - case _ : Throwable => kept4 = null // have to keep, it clobbers kept4 which is used - } - var kept5 = new Object - print(kept5) - kept5 = null // can't eliminate it's a clobber and it's used - print(kept5) - kept5 = null // can eliminate because we don't care about clobbers of nulls - while(randomBoolean) { - var kept6: AnyRef = null // not used, but have to keep because it clobbers the next used store - // on the back edge of the loop - kept6 = new Object // used - println(kept6) - } - result - } - }""".stripMargin - - override def show() { - val storeLocal = "STORE_LOCAL" - val lines1 = collectIcode() filter (_ contains storeLocal) map (x => x.drop(x.indexOf(storeLocal))) - println(lines1 mkString "\n") - } -} diff --git a/test/files/run/t5789.scala b/test/files/run/t5789.scala index c8d95f2153..677c9ca229 100644 --- a/test/files/run/t5789.scala +++ b/test/files/run/t5789.scala @@ -5,7 +5,7 @@ import scala.tools.partest.ReplTest object Test extends ReplTest { - override def extraSettings = "-Yinline -Ybackend:GenASM" + override def extraSettings = "-Yopt:l:classpath" def code = """ val n = 2 () => n diff --git a/test/files/run/t6188.flags b/test/files/run/t6188.flags index b9bb09167e..422d6be431 100644 --- a/test/files/run/t6188.flags +++ b/test/files/run/t6188.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM +-Yopt:l:classpath \ No newline at end of file diff --git a/test/files/run/t6546.flags b/test/files/run/t6546.flags deleted file mode 100644 index 6015e7c61f..0000000000 --- a/test/files/run/t6546.flags +++ /dev/null @@ -1 +0,0 @@ --Ybackend:GenASM -optimise \ No newline at end of file diff --git a/test/files/run/t6546/A_1.scala b/test/files/run/t6546/A_1.scala deleted file mode 100644 index bd086c08f8..0000000000 --- a/test/files/run/t6546/A_1.scala +++ /dev/null @@ -1,6 +0,0 @@ -final class Opt { - @inline def getOrElse(x: => String): String = "" -} -class A_1 { - def f(x: Opt): String = x getOrElse null -} diff --git a/test/files/run/t6546/B_2.scala b/test/files/run/t6546/B_2.scala deleted file mode 100644 index 64ec966f75..0000000000 --- a/test/files/run/t6546/B_2.scala +++ /dev/null @@ -1,8 +0,0 @@ -import scala.tools.partest.BytecodeTest - -object Test extends BytecodeTest { - def show: Unit = { - val node = loadClassNode("A_1") - assert(node.innerClasses.isEmpty, node.innerClasses) - } -} diff --git a/test/files/run/t6955.scala b/test/files/run/t6955.scala deleted file mode 100644 index 9ee3ef6bc5..0000000000 --- a/test/files/run/t6955.scala +++ /dev/null @@ -1,36 +0,0 @@ -import scala.tools.partest.IcodeComparison - -// this class should compile to code that uses switches (twice) -class Switches { - type Tag = Byte - - def switchBad(i: Tag): Int = i match { // notice type of i is Tag = Byte - case 1 => 1 - case 2 => 2 - case 3 => 3 - case _ => 0 - } - - // this worked before, should keep working - def switchOkay(i: Byte): Int = i match { - case 1 => 1 - case 2 => 2 - case 3 => 3 - case _ => 0 - } -} - -object Test extends IcodeComparison { - override def extraSettings: String = super.extraSettings + " -Ybackend:GenASM" - - // ensure we get two switches out of this -- ignore the rest of the output for robustness - // exclude the constant we emit for the "SWITCH ..." string below (we get the icode for all the code you see in this file) - override def show() = { - val expected = 2 - val actual = (collectIcode() filter { - x => x.indexOf("SWITCH ...") >= 0 && x.indexOf("CONSTANT(") == -1 - }).size - assert(actual == expected) - } -} - diff --git a/test/files/run/t6956.scala b/test/files/run/t6956.scala deleted file mode 100644 index 594f5c9194..0000000000 --- a/test/files/run/t6956.scala +++ /dev/null @@ -1,33 +0,0 @@ -import scala.tools.partest.IcodeComparison - -class Switches { - private[this] final val ONE = 1 - - def switchBad(i: Byte): Int = i match { - case ONE => 1 - case 2 => 2 - case 3 => 3 - case _ => 0 - } - - def switchOkay(i: Byte): Int = i match { - case 1 => 1 - case 2 => 2 - case 3 => 3 - case _ => 0 - } -} - -object Test extends IcodeComparison { - override def extraSettings: String = super.extraSettings + " -Ybackend:GenASM" - - // ensure we get two switches out of this -- ignore the rest of the output for robustness - // exclude the constant we emit for the "SWITCH ..." string below (we get the icode for all the code you see in this file) - override def show() = { - val expected = 2 - val actual = (collectIcode() filter { - x => x.indexOf("SWITCH ...") >= 0 && x.indexOf("CONSTANT(") == -1 - }).size - assert(actual == expected) - } -} diff --git a/test/files/run/t7008-scala-defined.flags b/test/files/run/t7008-scala-defined.flags index 49f2d2c4c8..e69de29bb2 100644 --- a/test/files/run/t7008-scala-defined.flags +++ b/test/files/run/t7008-scala-defined.flags @@ -1 +0,0 @@ --Ybackend:GenASM diff --git a/test/files/run/t7459b-optimize.flags b/test/files/run/t7459b-optimize.flags index b9bb09167e..65caa3736e 100644 --- a/test/files/run/t7459b-optimize.flags +++ b/test/files/run/t7459b-optimize.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM +-Yopt:l:classpath diff --git a/test/files/run/t7582.flags b/test/files/run/t7582.flags index 2cd4b38726..422d6be431 100644 --- a/test/files/run/t7582.flags +++ b/test/files/run/t7582.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath \ No newline at end of file diff --git a/test/files/run/t7582b.flags b/test/files/run/t7582b.flags index 2cd4b38726..422d6be431 100644 --- a/test/files/run/t7582b.flags +++ b/test/files/run/t7582b.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath \ No newline at end of file diff --git a/test/files/run/t8601-closure-elim.flags b/test/files/run/t8601-closure-elim.flags index 9158076b71..642187ff4c 100644 --- a/test/files/run/t8601-closure-elim.flags +++ b/test/files/run/t8601-closure-elim.flags @@ -1 +1 @@ --optimize -Ydelambdafy:inline -Ybackend:GenASM +-Ydelambdafy:method -Yopt:l:classpath diff --git a/test/files/run/t8601-closure-elim.scala b/test/files/run/t8601-closure-elim.scala index ebeb16e0c7..40fbf1fe0e 100644 --- a/test/files/run/t8601-closure-elim.scala +++ b/test/files/run/t8601-closure-elim.scala @@ -1,4 +1,5 @@ import scala.tools.partest.BytecodeTest +import scala.tools.partest.ASMConverters.instructionsFromMethod import scala.tools.asm import scala.tools.asm.util._ import scala.collection.JavaConverters._ @@ -10,8 +11,9 @@ object Test extends BytecodeTest { def test(methodName: String) { val classNode = loadClassNode("Foo") val methodNode = getMethod(classNode, "b") + val instrs = instructionsFromMethod(methodNode) val ops = methodNode.instructions.iterator.asScala.map(_.getOpcode).toList - assert(!ops.contains(asm.Opcodes.NEW), ops)// should be allocation free if the closure is eliminated + assert(!ops.contains(asm.Opcodes.NEW), instrs)// should be allocation free if the closure is eliminated } test("b") } diff --git a/test/files/run/t8601.flags b/test/files/run/t8601.flags index 2cd4b38726..65caa3736e 100644 --- a/test/files/run/t8601.flags +++ b/test/files/run/t8601.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath diff --git a/test/files/run/t8601b.flags b/test/files/run/t8601b.flags index 2cd4b38726..65caa3736e 100644 --- a/test/files/run/t8601b.flags +++ b/test/files/run/t8601b.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath diff --git a/test/files/run/t8601c.flags b/test/files/run/t8601c.flags index 2cd4b38726..65caa3736e 100644 --- a/test/files/run/t8601c.flags +++ b/test/files/run/t8601c.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath diff --git a/test/files/run/t8601d.flags b/test/files/run/t8601d.flags index 2cd4b38726..65caa3736e 100644 --- a/test/files/run/t8601d.flags +++ b/test/files/run/t8601d.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM \ No newline at end of file +-Yopt:l:classpath diff --git a/test/files/run/t8601e.flags b/test/files/run/t8601e.flags index b9bb09167e..65caa3736e 100644 --- a/test/files/run/t8601e.flags +++ b/test/files/run/t8601e.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM +-Yopt:l:classpath diff --git a/test/files/run/t9003.flags b/test/files/run/t9003.flags index b9bb09167e..65caa3736e 100644 --- a/test/files/run/t9003.flags +++ b/test/files/run/t9003.flags @@ -1 +1 @@ --optimize -Ybackend:GenASM +-Yopt:l:classpath diff --git a/test/files/run/t9403.flags b/test/files/run/t9403.flags index 307668060c..65caa3736e 100644 --- a/test/files/run/t9403.flags +++ b/test/files/run/t9403.flags @@ -1 +1 @@ --Ybackend:GenASM -optimize +-Yopt:l:classpath diff --git a/test/files/run/test-cpp.check b/test/files/run/test-cpp.check deleted file mode 100644 index 40c10e3350..0000000000 --- a/test/files/run/test-cpp.check +++ /dev/null @@ -1,81 +0,0 @@ ---- a -+++ b -@@ -54,3 +54,3 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, value x, value y -+ locals: value args - startBlock: 1 -@@ -59,10 +59,6 @@ - 1: -- 52 CONSTANT(2) -- 52 STORE_LOCAL(value x) - 52 SCOPE_ENTER value x -- 53 LOAD_LOCAL(value x) -- 53 STORE_LOCAL(value y) - 53 SCOPE_ENTER value y - 54 LOAD_MODULE object Predef -- 54 LOAD_LOCAL(value y) -+ 54 CONSTANT(2) - 54 BOX INT -@@ -109,3 +105,3 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, value x, value y -+ locals: value args, value x - startBlock: 1 -@@ -118,7 +114,5 @@ - 81 SCOPE_ENTER value x -- 82 LOAD_LOCAL(value x) -- 82 STORE_LOCAL(value y) - 82 SCOPE_ENTER value y - 83 LOAD_MODULE object Predef -- 83 LOAD_LOCAL(value y) -+ 83 LOAD_LOCAL(value x) - 83 BOX INT -@@ -152,3 +146,3 @@ - def main(args: Array[String] (ARRAY[REF(class String)])): Unit { -- locals: value args, value x, value y -+ locals: value args - startBlock: 1 -@@ -157,10 +151,6 @@ - 1: -- 66 THIS(TestAliasChainDerefThis) -- 66 STORE_LOCAL(value x) - 66 SCOPE_ENTER value x -- 67 LOAD_LOCAL(value x) -- 67 STORE_LOCAL(value y) - 67 SCOPE_ENTER value y - 68 LOAD_MODULE object Predef -- 68 LOAD_LOCAL(value y) -+ 68 THIS(Object) - 68 CALL_METHOD scala.Predef.println (dynamic) -@@ -193,3 +183,3 @@ - def test(x: Int (INT)): Unit { -- locals: value x, value y -+ locals: value x - startBlock: 1 -@@ -198,7 +188,5 @@ - 1: -- 29 LOAD_LOCAL(value x) -- 29 STORE_LOCAL(value y) - 29 SCOPE_ENTER value y - 30 LOAD_MODULE object Predef -- 30 LOAD_LOCAL(value y) -+ 30 LOAD_LOCAL(value x) - 30 BOX INT -@@ -240,7 +228,5 @@ - 96 SCOPE_ENTER variable x -- 97 LOAD_LOCAL(variable x) -- 97 STORE_LOCAL(variable y) - 97 SCOPE_ENTER variable y - 98 LOAD_MODULE object Predef -- 98 LOAD_LOCAL(variable y) -+ 98 LOAD_LOCAL(variable x) - 98 BOX INT -@@ -250,6 +236,4 @@ - 100 STORE_LOCAL(variable y) -- 101 LOAD_LOCAL(variable y) -- 101 STORE_LOCAL(variable x) - 102 LOAD_MODULE object Predef -- 102 LOAD_LOCAL(variable x) -+ 102 LOAD_LOCAL(variable y) - 102 BOX INT diff --git a/test/files/run/test-cpp.scala b/test/files/run/test-cpp.scala deleted file mode 100644 index 80163deb66..0000000000 --- a/test/files/run/test-cpp.scala +++ /dev/null @@ -1,104 +0,0 @@ -/** - * The only change is in the decision to replace a LOAD_LOCAL(l) - * in the copy-propagation performed before ClosureElimination. - * - * In the general case, the local variable 'l' is connected through - * an alias chain with other local variables and at the end of the - * alias chain there may be a Value, call it 'v'. - * - * If 'v' is cheaper to access (it is a Deref(This) or Const(_)), then - * replace the instruction to load it from the cheaper place. - * Otherwise, we use the local variable at the end of the alias chain - * instead of 'l'. - */ - -import scala.tools.partest.IcodeComparison - -object Test extends IcodeComparison { - override def printIcodeAfterPhase = "dce"; override def extraSettings: String = super.extraSettings + " -Ybackend:GenASM" // same line to minimize check file changs -} - -import scala.util.Random._ - -/** - * The example in the bug report (Issue-5321): an alias chain which store - * an Unknown. Should remove local variable 'y'. - */ -object TestBugReport { - def test(x: Int) = { - val y = x - println(y) - } -} - -/** - * The code taken from scala.tools.nsc.settings.Settings: - * After inlining of the setter is performed, there is an opportunity for - * copy-propagation to eliminate some local variables. - */ -object TestSetterInline { - private var _postSetHook: this.type => Unit = (x: this.type) => () - def withPostSetHook(f: this.type => Unit): this.type = { _postSetHook = f ; this } -} - - -/** - * The access of the local variable 'y' should be replaced by the - * constant. - */ -object TestAliasChainConstant { - - def main(args: Array[String]): Unit = { - val x = 2 - val y = x - println(y) - } -} - -/** - * At the end of the alias chain we have a reference to 'this'. - * The local variables should be all discarded and replace by a - * direct reference to this - */ -class TestAliasChainDerefThis { - - def main(args: Array[String]): Unit = { - val x = this - val y = x - println(y) - } -} - -/** - * At the end of the alias chain, there is the value of a field. - * The use of variable 'y' should be replaced by 'x', not by an access - * to the field 'f' since it is more costly. - */ -object TestAliasChainDerefField { - def f = nextInt - - def main(args: Array[String]): Unit = { - val x = f - val y = x - println(y) - } -} - - -/** - * The first time 'println' is called, 'x' is replaced by 'y' - * and the second time, 'y' is replaced by 'x'. But none of them - * can be removed. - */ -object TestDifferentBindings { - - def main(args: Array[String]): Unit = { - var x = nextInt - var y = x - println(y) - - y = nextInt - x = y - println(x) - } -} diff --git a/test/pending/jvm/constant-optimization/Foo_1.flags b/test/pending/jvm/constant-optimization/Foo_1.flags new file mode 100644 index 0000000000..9691c0985d --- /dev/null +++ b/test/pending/jvm/constant-optimization/Foo_1.flags @@ -0,0 +1 @@ +// constant otimization not there yet, -Yopt:nullness-tracking not enough. diff --git a/test/pending/jvm/constant-optimization/Foo_1.scala b/test/pending/jvm/constant-optimization/Foo_1.scala new file mode 100644 index 0000000000..6f408044d7 --- /dev/null +++ b/test/pending/jvm/constant-optimization/Foo_1.scala @@ -0,0 +1,9 @@ +class Foo_1 { + def foo() { + // constant optimization should eliminate all branches + val i = 1 + val x = if (i != 1) null else "good" + val y = if (x == null) "good" else x + "" + println(y) + } +} \ No newline at end of file diff --git a/test/pending/jvm/constant-optimization/Test.scala b/test/pending/jvm/constant-optimization/Test.scala new file mode 100644 index 0000000000..dc0f8f6103 --- /dev/null +++ b/test/pending/jvm/constant-optimization/Test.scala @@ -0,0 +1,27 @@ + +import scala.tools.partest.BytecodeTest +import scala.tools.asm +import asm.tree.InsnList +import scala.collection.JavaConverters._ + +object Test extends BytecodeTest { + val comparisons = Set(asm.Opcodes.IF_ACMPEQ, asm.Opcodes.IF_ACMPNE, asm.Opcodes.IF_ICMPEQ, asm.Opcodes.IF_ICMPGE, asm.Opcodes.IF_ICMPGT, asm.Opcodes.IF_ICMPLE, + asm.Opcodes.IF_ICMPLT, asm.Opcodes.IF_ICMPNE, asm.Opcodes.IFEQ, asm.Opcodes.IFGE, asm.Opcodes.IFGT, asm.Opcodes.IFLE, asm.Opcodes.IFLT, + asm.Opcodes.IFNE, asm.Opcodes.IFNONNULL, asm.Opcodes.IFNULL) + + def show: Unit = { + val classNode = loadClassNode("Foo_1") + val methodNode = getMethod(classNode, "foo") + // after optimization there should be no comparisons left + val expected = 0 + + val got = countComparisons(methodNode.instructions) + assert(got == expected, s"expected $expected but got $got comparisons") + } + + def countComparisons(insnList: InsnList): Int = { + def isComparison(node: asm.tree.AbstractInsnNode): Boolean = + (comparisons contains node.getOpcode) + insnList.iterator.asScala count isComparison + } +} \ No newline at end of file diff --git a/test/pending/jvm/patmat_opt_ignore_underscore.check b/test/pending/jvm/patmat_opt_ignore_underscore.check new file mode 100644 index 0000000000..43f53aba12 --- /dev/null +++ b/test/pending/jvm/patmat_opt_ignore_underscore.check @@ -0,0 +1 @@ +bytecode identical diff --git a/test/pending/jvm/patmat_opt_ignore_underscore.flags b/test/pending/jvm/patmat_opt_ignore_underscore.flags new file mode 100644 index 0000000000..453b6b7895 --- /dev/null +++ b/test/pending/jvm/patmat_opt_ignore_underscore.flags @@ -0,0 +1 @@ +-Yopt:l:project \ No newline at end of file diff --git a/test/pending/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala b/test/pending/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala new file mode 100644 index 0000000000..b0506018f6 --- /dev/null +++ b/test/pending/jvm/patmat_opt_ignore_underscore/Analyzed_1.scala @@ -0,0 +1,29 @@ +// this class's bytecode, compiled under -optimize is analyzed by the test +// method a's bytecode should be identical to method b's bytecode +// this is not the best test for shielding against regressing on this particular issue, +// but it sets the stage for checking the bytecode emitted by the pattern matcher and +// comparing it to manually tuned code using if/then/else etc. +class SameBytecode { + case class Foo(x: Any, y: String) + + def a = + Foo(1, "a") match { + case Foo(_: String, y) => y + } + + // this method's body holds the tree that should be generated by the pattern matcher for method a (-Xprint:patmat) + // the test checks that bytecode for a and b is identical (modulo line numbers) + // we can't diff trees as they are quite different (patmat uses jumps to labels that cannot be expressed in source, for example) + // note that the actual tree is quite bad: we do an unnecessary null check, isInstanceOf and local val (x3) + // some of these will be fixed soon (the initial null check is for the scrutinee, which is harder to fix in patmat) + def b: String = { + val x1 = Foo(1, "a") + if (x1.ne(null)) { + if (x1.x.isInstanceOf[String]) { + return x1.y + } + } + + throw new MatchError(x1) + } +} \ No newline at end of file diff --git a/test/pending/jvm/patmat_opt_ignore_underscore/test.scala b/test/pending/jvm/patmat_opt_ignore_underscore/test.scala new file mode 100644 index 0000000000..d6630e80a0 --- /dev/null +++ b/test/pending/jvm/patmat_opt_ignore_underscore/test.scala @@ -0,0 +1,18 @@ +/* + * filter: inliner warning; re-run with + */ +import scala.tools.partest.BytecodeTest + +import scala.tools.nsc.util.JavaClassPath +import java.io.InputStream +import scala.tools.asm +import asm.ClassReader +import asm.tree.{ClassNode, InsnList} +import scala.collection.JavaConverters._ + +object Test extends BytecodeTest { + def show: Unit = { + val classNode = loadClassNode("SameBytecode") + sameBytecode(getMethod(classNode, "a"), getMethod(classNode, "b")) + } +} diff --git a/test/pending/jvm/patmat_opt_no_nullcheck.check b/test/pending/jvm/patmat_opt_no_nullcheck.check new file mode 100644 index 0000000000..43f53aba12 --- /dev/null +++ b/test/pending/jvm/patmat_opt_no_nullcheck.check @@ -0,0 +1 @@ +bytecode identical diff --git a/test/pending/jvm/patmat_opt_no_nullcheck.flags b/test/pending/jvm/patmat_opt_no_nullcheck.flags new file mode 100644 index 0000000000..453b6b7895 --- /dev/null +++ b/test/pending/jvm/patmat_opt_no_nullcheck.flags @@ -0,0 +1 @@ +-Yopt:l:project \ No newline at end of file diff --git a/test/pending/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala b/test/pending/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala new file mode 100644 index 0000000000..1e4d564cdf --- /dev/null +++ b/test/pending/jvm/patmat_opt_no_nullcheck/Analyzed_1.scala @@ -0,0 +1,24 @@ +// this class's bytecode, compiled under -optimize is analyzed by the test +// method a's bytecode should be identical to method b's bytecode +case class Foo(x: Any) + +class SameBytecode { + def a = + (Foo(1): Any) match { + case Foo(_: String) => + } + + // there's no null check + def b: Unit = { + val x1: Any = Foo(1) + if (x1.isInstanceOf[Foo]) { + val x3 = x1.asInstanceOf[Foo] + if (x3.x.isInstanceOf[String]) { + val x = () + return + } + } + + throw new MatchError(x1) + } +} \ No newline at end of file diff --git a/test/pending/jvm/patmat_opt_no_nullcheck/test.scala b/test/pending/jvm/patmat_opt_no_nullcheck/test.scala new file mode 100644 index 0000000000..5a4a398b67 --- /dev/null +++ b/test/pending/jvm/patmat_opt_no_nullcheck/test.scala @@ -0,0 +1,14 @@ +/* + * filter: inliner warning; re-run with + */ +import scala.tools.partest.{ BytecodeTest, ASMConverters } + +object Test extends BytecodeTest { + def show: Unit = { + val classNode = loadClassNode("SameBytecode") + // ASM and GenBCode assign variable slots slightly differently + val instrsA = ASMConverters.instructionsFromMethod(getMethod(classNode, "a")) + val instrsB = ASMConverters.instructionsFromMethod(getMethod(classNode, "b")) + assert(ASMConverters.equivalentBytecode(instrsA, instrsB), diffInstructions(instrsA, instrsB)) // doesn't work + } +} diff --git a/test/pending/jvm/patmat_opt_primitive_typetest.check b/test/pending/jvm/patmat_opt_primitive_typetest.check new file mode 100644 index 0000000000..43f53aba12 --- /dev/null +++ b/test/pending/jvm/patmat_opt_primitive_typetest.check @@ -0,0 +1 @@ +bytecode identical diff --git a/test/pending/jvm/patmat_opt_primitive_typetest.flags b/test/pending/jvm/patmat_opt_primitive_typetest.flags new file mode 100644 index 0000000000..19c578e4ad --- /dev/null +++ b/test/pending/jvm/patmat_opt_primitive_typetest.flags @@ -0,0 +1 @@ +-Yopt:l:project diff --git a/test/pending/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala b/test/pending/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala new file mode 100644 index 0000000000..c961082fa7 --- /dev/null +++ b/test/pending/jvm/patmat_opt_primitive_typetest/Analyzed_1.scala @@ -0,0 +1,24 @@ +// this class's bytecode, compiled under -optimize is analyzed by the test +// method a's bytecode should be identical to method b's bytecode +class SameBytecode { + case class Foo(x: Int, y: String) + + def a = + Foo(1, "a") match { + case Foo(_: Int, y) => y + } + + // this method's body holds the tree that should be generated by the pattern matcher for method a (-Xprint:patmat) + // the test checks that bytecode for a and b is identical (modulo line numbers) + // we can't diff trees as they are quite different (patmat uses jumps to labels that cannot be expressed in source, for example) + // note that the actual tree is quite bad: we do an unnecessary null check, and local val (x3) + // some of these will be fixed soon (the initial null check is for the scrutinee, which is harder to fix in patmat) + def b: String = { + val x1 = Foo(1, "a") + if (x1.ne(null)) { + return x1.y + } + + throw new MatchError(x1) + } +} \ No newline at end of file diff --git a/test/pending/jvm/patmat_opt_primitive_typetest/test.scala b/test/pending/jvm/patmat_opt_primitive_typetest/test.scala new file mode 100644 index 0000000000..2927e763d5 --- /dev/null +++ b/test/pending/jvm/patmat_opt_primitive_typetest/test.scala @@ -0,0 +1,8 @@ +import scala.tools.partest.BytecodeTest + +object Test extends BytecodeTest { + def show: Unit = { + val classNode = loadClassNode("SameBytecode") + sameBytecode(getMethod(classNode, "a"), getMethod(classNode, "b")) + } +} diff --git a/test/pending/jvm/t7006.check b/test/pending/jvm/t7006.check new file mode 100644 index 0000000000..6294b14d62 --- /dev/null +++ b/test/pending/jvm/t7006.check @@ -0,0 +1,29 @@ +[running phase parser on Foo_1.scala] +[running phase namer on Foo_1.scala] +[running phase packageobjects on Foo_1.scala] +[running phase typer on Foo_1.scala] +[running phase patmat on Foo_1.scala] +[running phase superaccessors on Foo_1.scala] +[running phase extmethods on Foo_1.scala] +[running phase pickler on Foo_1.scala] +[running phase refchecks on Foo_1.scala] +[running phase uncurry on Foo_1.scala] +[running phase tailcalls on Foo_1.scala] +[running phase specialize on Foo_1.scala] +[running phase explicitouter on Foo_1.scala] +[running phase erasure on Foo_1.scala] +[running phase posterasure on Foo_1.scala] +[running phase lazyvals on Foo_1.scala] +[running phase lambdalift on Foo_1.scala] +[running phase constructors on Foo_1.scala] +[running phase flatten on Foo_1.scala] +[running phase mixin on Foo_1.scala] +[running phase cleanup on Foo_1.scala] +[running phase delambdafy on Foo_1.scala] +[running phase icode on Foo_1.scala] +[running phase inliner on Foo_1.scala] +[running phase inlinehandlers on Foo_1.scala] +[running phase closelim on Foo_1.scala] +[running phase constopt on Foo_1.scala] +[running phase dce on Foo_1.scala] +[running phase jvm on icode] diff --git a/test/pending/jvm/t7006/Foo_1.flags b/test/pending/jvm/t7006/Foo_1.flags new file mode 100644 index 0000000000..5d1b6b2644 --- /dev/null +++ b/test/pending/jvm/t7006/Foo_1.flags @@ -0,0 +1 @@ +-Yopt:l:project -Ydebug -Xfatal-warnings diff --git a/test/pending/jvm/t7006/Foo_1.scala b/test/pending/jvm/t7006/Foo_1.scala new file mode 100644 index 0000000000..3985557d9f --- /dev/null +++ b/test/pending/jvm/t7006/Foo_1.scala @@ -0,0 +1,10 @@ +class Foo_1 { + def foo { + try { + val x = 3 // this will be optimized away, leaving a useless jump only block + } finally { + print("hello") + } + while(true){} // ensure infinite loop doesn't break the algorithm + } +} diff --git a/test/pending/jvm/t7006/Test.scala b/test/pending/jvm/t7006/Test.scala new file mode 100644 index 0000000000..7b4a8c45fb --- /dev/null +++ b/test/pending/jvm/t7006/Test.scala @@ -0,0 +1,21 @@ +import scala.tools.partest.BytecodeTest +import scala.tools.asm +import asm.tree.InsnList +import scala.collection.JavaConverters._ + +object Test extends BytecodeTest { + def show: Unit = { + val classNode = loadClassNode("Foo_1") + val methodNode = getMethod(classNode, "foo") + val nopCount = count(methodNode.instructions, asm.Opcodes.NOP) + val gotoCount = count(methodNode.instructions, asm.Opcodes.GOTO) + assert(nopCount == 0, s"NOPs expected: 0, actual: $nopCount") + assert(gotoCount == 1, s"GOTOs expected: 1, actual: $gotoCount") + } + + def count(insnList: InsnList, opcode: Int): Int = { + def isNop(node: asm.tree.AbstractInsnNode): Boolean = + (node.getOpcode == opcode) + insnList.iterator.asScala.count(isNop) + } +} diff --git a/test/pending/pos/inliner2.flags b/test/pending/pos/inliner2.flags new file mode 100644 index 0000000000..4bf93a9c2a --- /dev/null +++ b/test/pending/pos/inliner2.flags @@ -0,0 +1,35 @@ +-optimise -Ybackend:GenASM -Xfatal-warnings +/* +This is what we get with 2.11.2-M3 and -Yopt:l:project: + + public final int bob1(); + Code: + 0: aload_0 + 1: aload_0 + 2: astore 6 + 4: aload 6 + 6: invokedynamic #62, 0 // InvokeDynamic #0:apply$mcZ$sp:(LA;)Lscala/runtime/java8/JFunction0$mcZ$sp; + 11: checkcast #29 // class scala/Function0 + 14: invokedynamic #71, 0 // InvokeDynamic #1:apply$mcI$sp:()Lscala/runtime/java8/JFunction0$mcI$sp; + 19: checkcast #29 // class scala/Function0 + 22: invokedynamic #76, 0 // InvokeDynamic #2:apply$mcI$sp:()Lscala/runtime/java8/JFunction0$mcI$sp; + 27: checkcast #29 // class scala/Function0 + 30: astore 4 + 32: astore_3 + 33: astore_2 + 34: astore_1 + 35: aload_2 + 36: pop + 37: aload 6 + 39: invokevirtual #53 // Method A$$$anonfun$1:()Z + 42: ifeq 54 + 45: aload_3 + 46: invokeinterface #36, 1 // InterfaceMethod scala/Function0.apply:()Ljava/lang/Object; + 51: goto 61 + 54: aload 4 + 56: invokeinterface #36, 1 // InterfaceMethod scala/Function0.apply:()Ljava/lang/Object; + 61: astore 5 + 63: aload 5 + 65: invokestatic #82 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I + 68: ireturn +*/ \ No newline at end of file diff --git a/test/pending/pos/inliner2.scala b/test/pending/pos/inliner2.scala new file mode 100644 index 0000000000..bc83e04312 --- /dev/null +++ b/test/pending/pos/inliner2.scala @@ -0,0 +1,57 @@ +// This isn't actually testing much, because no warning is emitted in versions +// before the fix which comes with this because the method isn't even considered +// for inlining due to the bug. +class A { + private var debug = false + @inline private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = + if (cond) ifPart else elsePart + + final def bob1() = ifelse(debug, 1, 2) + final def bob2() = if (debug) 1 else 2 +} +// Cool: +// +// % ls -1 /tmp/2901/ +// A$$anonfun$bob1$1.class +// A$$anonfun$bob1$2.class +// A$$anonfun$bob1$3.class +// A.class +// % ls -1 /tmp/trunk +// A.class +// +// Observations: +// +// (1) The inlined version accesses the field: the explicit one calls the accessor. +// (2) The inlined version fails to eliminate boxing. With reference types it emits +// an unneeded checkcast. +// (3) The private var debug is mangled to A$$debug, but after inlining it is never accessed +// from outside of the class and doesn't need mangling. +// (4) We could forego emitting bytecode for ifelse entirely if it has been +// inlined at all sites. +// +// Generated bytecode for the above: +// +// public final int bob1(); +// Code: +// Stack=1, Locals=1, Args_size=1 +// 0: aload_0 +// 1: getfield #11; //Field A$$debug:Z +// 4: ifeq 14 +// 7: iconst_1 +// 8: invokestatic #41; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; +// 11: goto 18 +// 14: iconst_2 +// 15: invokestatic #41; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; +// 18: invokestatic #45; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I +// 21: ireturn +// +// public final int bob2(); +// Code: +// Stack=1, Locals=1, Args_size=1 +// 0: aload_0 +// 1: invokevirtual #48; //Method A$$debug:()Z +// 4: ifeq 11 +// 7: iconst_1 +// 8: goto 12 +// 11: iconst_2 +// 12: ireturn diff --git a/test/pending/pos/sealed-final.flags b/test/pending/pos/sealed-final.flags new file mode 100644 index 0000000000..63d024a0ba --- /dev/null +++ b/test/pending/pos/sealed-final.flags @@ -0,0 +1,41 @@ +-Xfatal-warnings -Yinline-warnings -Ybackend:GenASM -optimise +/* +The new flag settings could be + -Yopt-warnings -Yopt:l:project + +The issue here is that things are being inlined, but a lot of +redundant load/store instructions are left behind: + +2.11.7: + + public int f(); + Code: + 0: getstatic #19 // Field Foo$.MODULE$:LFoo$; + 3: invokevirtual #23 // Method Foo$.mkFoo:()LFoo; + 6: pop + 7: bipush 10 + 9: iconst_1 + 10: iadd + 11: ireturn + + +2.12.0-M3: + + public int f(); + Code: + 0: getstatic #19 // Field Foo$.MODULE$:LFoo$; + 3: invokevirtual #23 // Method Foo$.mkFoo:()LFoo; + 6: bipush 10 + 8: istore_2 + 9: dup + 10: ifnonnull 15 + 13: aconst_null + 14: athrow + 15: astore_1 + 16: iload_2 + 17: iconst_1 + 18: iadd + 19: istore_3 + 20: iload_3 + 21: ireturn +*/ \ No newline at end of file diff --git a/test/pending/pos/sealed-final.scala b/test/pending/pos/sealed-final.scala new file mode 100644 index 0000000000..bdedb5c1f6 --- /dev/null +++ b/test/pending/pos/sealed-final.scala @@ -0,0 +1,14 @@ +sealed abstract class Foo { + @inline def bar(x: Int) = x + 1 +} +object Foo { + def mkFoo(): Foo = new Baz2 +} + +object Baz1 extends Foo +final class Baz2 extends Foo + +object Test { + // bar should be inlined now + def f = Foo.mkFoo() bar 10 +} diff --git a/test/pending/run/inline-ex-handlers.check b/test/pending/run/inline-ex-handlers.check new file mode 100644 index 0000000000..fce32771b4 --- /dev/null +++ b/test/pending/run/inline-ex-handlers.check @@ -0,0 +1,491 @@ +--- a ++++ b +@@ -171,5 +171,5 @@ + def productElement(x$1: Int (INT)): Object { +- locals: value x$1, value x1 ++ locals: value x$1, value x1, variable boxed1 + startBlock: 1 +- blocks: [1,2,3,4] ++ blocks: [1,3,4] + +@@ -186,2 +186,4 @@ + 92 LOAD_LOCAL(value x$1) ++ 92 STORE_LOCAL(variable boxed1) ++ 92 LOAD_LOCAL(variable boxed1) + 92 BOX INT +@@ -194,5 +196,2 @@ + 92 CALL_METHOD MyException.message (dynamic) +- 92 JUMP 2 +- +- 2: + 92 RETURN(REF(class Object)) +@@ -246,3 +245,3 @@ + startBlock: 1 +- blocks: [1,2,3,4,5,6,7,8,11,12,13,14,15,16,17,18] ++ blocks: [1,2,3,4,5,6,8,11,12,13,14,15,16,17,18] + +@@ -257,5 +256,2 @@ + 92 SCOPE_ENTER value x1 +- 92 JUMP 7 +- +- 7: + 92 LOAD_LOCAL(value x1) +@@ -408,5 +404,5 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, variable result, value ex6, value x4, value x5, value message, value x ++ locals: value args, variable result, value ex6, value x4, value x5, value x + startBlock: 1 +- blocks: [1,2,3,4,5,8,10,11,13] ++ blocks: [1,2,3,5,8,10,11,13,14] + +@@ -434,4 +430,13 @@ + 103 CALL_METHOD MyException. (static-instance) +- 103 THROW(MyException) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 14 + ++ 14: ++ 101 LOAD_LOCAL(value ex6) ++ 101 STORE_LOCAL(value x4) ++ 101 SCOPE_ENTER value x4 ++ 106 LOAD_LOCAL(value x4) ++ 106 IS_INSTANCE REF(class MyException) ++ 106 CZJUMP (BOOL)NE ? 5 : 8 ++ + 13: +@@ -447,5 +452,2 @@ + 101 SCOPE_ENTER value x4 +- 101 JUMP 4 +- +- 4: + 106 LOAD_LOCAL(value x4) +@@ -459,8 +461,5 @@ + 106 SCOPE_ENTER value x5 +- 106 LOAD_LOCAL(value x5) +- 106 CALL_METHOD MyException.message (dynamic) +- 106 STORE_LOCAL(value message) +- 106 SCOPE_ENTER value message + 106 LOAD_MODULE object Predef +- 106 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 106 CALL_METHOD MyException.message (dynamic) + 106 CALL_METHOD scala.Predef.println (dynamic) +@@ -536,3 +535,3 @@ + startBlock: 1 +- blocks: [1,2,3,4,6,7,9,10] ++ blocks: [1,3,4,6,7,9,10,11,12,13] + +@@ -565,4 +564,9 @@ + 306 CALL_METHOD MyException. (static-instance) +- 306 THROW(MyException) ++ ? JUMP 11 + ++ 11: ++ ? LOAD_LOCAL(variable monitor4) ++ 305 MONITOR_EXIT ++ ? JUMP 12 ++ + 9: +@@ -571,3 +575,3 @@ + 305 MONITOR_EXIT +- ? THROW(Throwable) ++ ? JUMP 12 + +@@ -577,4 +581,11 @@ + 304 MONITOR_EXIT +- ? THROW(Throwable) ++ ? STORE_LOCAL(value t) ++ ? JUMP 13 + ++ 12: ++ ? LOAD_LOCAL(variable monitor3) ++ 304 MONITOR_EXIT ++ ? STORE_LOCAL(value t) ++ ? JUMP 13 ++ + 3: +@@ -591,5 +602,14 @@ + 310 CALL_METHOD scala.Predef.println (dynamic) +- 310 JUMP 2 ++ 300 RETURN(UNIT) + +- 2: ++ 13: ++ 310 LOAD_MODULE object Predef ++ 310 CALL_PRIMITIVE(StartConcat) ++ 310 CONSTANT("Caught crash: ") ++ 310 CALL_PRIMITIVE(StringConcat(REF(class String))) ++ 310 LOAD_LOCAL(value t) ++ 310 CALL_METHOD java.lang.Throwable.toString (dynamic) ++ 310 CALL_PRIMITIVE(StringConcat(REF(class String))) ++ 310 CALL_PRIMITIVE(EndConcat) ++ 310 CALL_METHOD scala.Predef.println (dynamic) + 300 RETURN(UNIT) +@@ -601,6 +621,6 @@ + with finalizer: null +- catch (Throwable) in Vector(7, 9, 10) starting at: 6 ++ catch (Throwable) in Vector(7, 9, 10, 11) starting at: 6 + consisting of blocks: List(6) + with finalizer: null +- catch (Throwable) in Vector(4, 6, 7, 9, 10) starting at: 3 ++ catch (Throwable) in Vector(4, 6, 7, 9, 10, 11, 12) starting at: 3 + consisting of blocks: List(3) +@@ -636,3 +656,3 @@ + startBlock: 1 +- blocks: [1,3,4,5,6,8,9] ++ blocks: [1,3,4,5,6,8,9,10,11] + +@@ -660,4 +680,10 @@ + 78 CALL_METHOD java.lang.IllegalArgumentException. (static-instance) +- 78 THROW(IllegalArgumentException) ++ ? STORE_LOCAL(value e) ++ ? JUMP 10 + ++ 10: ++ 81 LOAD_LOCAL(value e) ++ ? STORE_LOCAL(variable exc1) ++ ? JUMP 11 ++ + 8: +@@ -686,3 +712,4 @@ + 81 LOAD_LOCAL(value e) +- 81 THROW(Exception) ++ ? STORE_LOCAL(variable exc1) ++ ? JUMP 11 + +@@ -703,2 +730,15 @@ + ++ 11: ++ 83 LOAD_MODULE object Predef ++ 83 CONSTANT("finally") ++ 83 CALL_METHOD scala.Predef.println (dynamic) ++ 84 LOAD_LOCAL(variable result) ++ 84 CONSTANT(1) ++ 84 CALL_PRIMITIVE(Arithmetic(SUB,INT)) ++ 84 CONSTANT(2) ++ 84 CALL_PRIMITIVE(Arithmetic(DIV,INT)) ++ 84 STORE_LOCAL(variable result) ++ 84 LOAD_LOCAL(variable exc1) ++ 84 THROW(Throwable) ++ + } +@@ -708,3 +748,3 @@ + with finalizer: null +- catch () in Vector(4, 5, 6, 8) starting at: 3 ++ catch () in Vector(4, 5, 6, 8, 10) starting at: 3 + consisting of blocks: List(3) +@@ -732,5 +772,5 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, variable result, value ex6, variable exc2, value x4, value x5, value message, value x, value ex6, value x4, value x5, value message, value x ++ locals: value args, variable result, value ex6, variable exc2, value x4, value x5, value x, value ex6, value x4, value x5, value x + startBlock: 1 +- blocks: [1,3,4,5,6,9,13,14,15,18,20,21,23,24] ++ blocks: [1,3,4,5,6,9,13,14,15,18,20,21,23,24,25,26,27] + +@@ -758,4 +798,11 @@ + 172 CALL_METHOD MyException. (static-instance) +- 172 THROW(MyException) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 25 + ++ 25: ++ 170 LOAD_LOCAL(value ex6) ++ 170 STORE_LOCAL(value x4) ++ 170 SCOPE_ENTER value x4 ++ 170 JUMP 14 ++ + 23: +@@ -798,8 +845,5 @@ + 175 SCOPE_ENTER value x5 +- 175 LOAD_LOCAL(value x5) +- 175 CALL_METHOD MyException.message (dynamic) +- 175 STORE_LOCAL(value message) +- 175 SCOPE_ENTER value message + 176 LOAD_MODULE object Predef +- 176 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 176 CALL_METHOD MyException.message (dynamic) + 176 CALL_METHOD scala.Predef.println (dynamic) +@@ -807,5 +851,7 @@ + 177 DUP(REF(class MyException)) +- 177 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 177 CALL_METHOD MyException.message (dynamic) + 177 CALL_METHOD MyException. (static-instance) +- 177 THROW(MyException) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 26 + +@@ -813,3 +859,4 @@ + 170 LOAD_LOCAL(value ex6) +- 170 THROW(Throwable) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 26 + +@@ -823,2 +870,8 @@ + ++ 26: ++ 169 LOAD_LOCAL(value ex6) ++ 169 STORE_LOCAL(value x4) ++ 169 SCOPE_ENTER value x4 ++ 169 JUMP 5 ++ + 5: +@@ -833,8 +886,5 @@ + 180 SCOPE_ENTER value x5 +- 180 LOAD_LOCAL(value x5) +- 180 CALL_METHOD MyException.message (dynamic) +- 180 STORE_LOCAL(value message) +- 180 SCOPE_ENTER value message + 181 LOAD_MODULE object Predef +- 181 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 181 CALL_METHOD MyException.message (dynamic) + 181 CALL_METHOD scala.Predef.println (dynamic) +@@ -842,5 +892,7 @@ + 182 DUP(REF(class MyException)) +- 182 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 182 CALL_METHOD MyException.message (dynamic) + 182 CALL_METHOD MyException. (static-instance) +- 182 THROW(MyException) ++ ? STORE_LOCAL(variable exc2) ++ ? JUMP 27 + +@@ -848,3 +900,4 @@ + 169 LOAD_LOCAL(value ex6) +- 169 THROW(Throwable) ++ ? STORE_LOCAL(variable exc2) ++ ? JUMP 27 + +@@ -865,2 +918,15 @@ + ++ 27: ++ 184 LOAD_MODULE object Predef ++ 184 CONSTANT("finally") ++ 184 CALL_METHOD scala.Predef.println (dynamic) ++ 185 LOAD_LOCAL(variable result) ++ 185 CONSTANT(1) ++ 185 CALL_PRIMITIVE(Arithmetic(SUB,INT)) ++ 185 CONSTANT(2) ++ 185 CALL_PRIMITIVE(Arithmetic(DIV,INT)) ++ 185 STORE_LOCAL(variable result) ++ 185 LOAD_LOCAL(variable exc2) ++ 185 THROW(Throwable) ++ + } +@@ -870,6 +936,6 @@ + with finalizer: null +- catch (Throwable) in Vector(13, 14, 15, 18, 20, 21, 23) starting at: 4 ++ catch (Throwable) in Vector(13, 14, 15, 18, 20, 21, 23, 25) starting at: 4 + consisting of blocks: List(9, 8, 6, 5, 4) + with finalizer: null +- catch () in Vector(4, 5, 6, 9, 13, 14, 15, 18, 20, 21, 23) starting at: 3 ++ catch () in Vector(4, 5, 6, 9, 13, 14, 15, 18, 20, 21, 23, 25, 26) starting at: 3 + consisting of blocks: List(3) +@@ -897,5 +963,5 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, variable result, value e, value ex6, value x4, value x5, value message, value x ++ locals: value args, variable result, value e, value ex6, value x4, value x5, value x + startBlock: 1 +- blocks: [1,2,3,6,7,8,11,13,14,16] ++ blocks: [1,2,3,6,7,8,11,13,14,16,17] + +@@ -923,4 +989,11 @@ + 124 CALL_METHOD MyException. (static-instance) +- 124 THROW(MyException) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 17 + ++ 17: ++ 122 LOAD_LOCAL(value ex6) ++ 122 STORE_LOCAL(value x4) ++ 122 SCOPE_ENTER value x4 ++ 122 JUMP 7 ++ + 16: +@@ -948,8 +1021,5 @@ + 127 SCOPE_ENTER value x5 +- 127 LOAD_LOCAL(value x5) +- 127 CALL_METHOD MyException.message (dynamic) +- 127 STORE_LOCAL(value message) +- 127 SCOPE_ENTER value message + 127 LOAD_MODULE object Predef +- 127 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 127 CALL_METHOD MyException.message (dynamic) + 127 CALL_METHOD scala.Predef.println (dynamic) +@@ -982,3 +1052,3 @@ + with finalizer: null +- catch (IllegalArgumentException) in Vector(6, 7, 8, 11, 13, 14, 16) starting at: 3 ++ catch (IllegalArgumentException) in Vector(6, 7, 8, 11, 13, 14, 16, 17) starting at: 3 + consisting of blocks: List(3) +@@ -1006,5 +1076,5 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, variable result, value ex6, value x4, value x5, value message, value x, value e ++ locals: value args, variable result, value ex6, value x4, value x5, value x, value e + startBlock: 1 +- blocks: [1,2,3,4,5,8,12,13,14,16] ++ blocks: [1,2,3,5,8,12,13,14,16,17] + +@@ -1032,4 +1102,13 @@ + 148 CALL_METHOD MyException. (static-instance) +- 148 THROW(MyException) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 17 + ++ 17: ++ 145 LOAD_LOCAL(value ex6) ++ 145 STORE_LOCAL(value x4) ++ 145 SCOPE_ENTER value x4 ++ 154 LOAD_LOCAL(value x4) ++ 154 IS_INSTANCE REF(class MyException) ++ 154 CZJUMP (BOOL)NE ? 5 : 8 ++ + 16: +@@ -1053,5 +1132,2 @@ + 145 SCOPE_ENTER value x4 +- 145 JUMP 4 +- +- 4: + 154 LOAD_LOCAL(value x4) +@@ -1065,8 +1141,5 @@ + 154 SCOPE_ENTER value x5 +- 154 LOAD_LOCAL(value x5) +- 154 CALL_METHOD MyException.message (dynamic) +- 154 STORE_LOCAL(value message) +- 154 SCOPE_ENTER value message + 154 LOAD_MODULE object Predef +- 154 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 154 CALL_METHOD MyException.message (dynamic) + 154 CALL_METHOD scala.Predef.println (dynamic) +@@ -1287,3 +1360,3 @@ + startBlock: 1 +- blocks: [1,2,3,4,5,7] ++ blocks: [1,2,3,4,5,7,8] + +@@ -1311,4 +1384,11 @@ + 38 CALL_METHOD java.lang.IllegalArgumentException. (static-instance) +- 38 THROW(IllegalArgumentException) ++ ? STORE_LOCAL(value e) ++ ? JUMP 8 + ++ 8: ++ 42 LOAD_MODULE object Predef ++ 42 CONSTANT("IllegalArgumentException") ++ 42 CALL_METHOD scala.Predef.println (dynamic) ++ 42 JUMP 2 ++ + 7: +@@ -1358,5 +1438,5 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, variable result, value ex6, value x4, value x5, value message, value x ++ locals: value args, variable result, value ex6, value x4, value x5, value x + startBlock: 1 +- blocks: [1,2,3,4,5,8,10,11,13,14,16] ++ blocks: [1,2,3,5,8,10,11,13,14,16,17] + +@@ -1384,3 +1464,4 @@ + 203 CALL_METHOD MyException. (static-instance) +- 203 THROW(MyException) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 17 + +@@ -1404,4 +1485,13 @@ + 209 CALL_METHOD MyException. (static-instance) +- 209 THROW(MyException) ++ ? STORE_LOCAL(value ex6) ++ ? JUMP 17 + ++ 17: ++ 200 LOAD_LOCAL(value ex6) ++ 200 STORE_LOCAL(value x4) ++ 200 SCOPE_ENTER value x4 ++ 212 LOAD_LOCAL(value x4) ++ 212 IS_INSTANCE REF(class MyException) ++ 212 CZJUMP (BOOL)NE ? 5 : 8 ++ + 16: +@@ -1417,5 +1507,2 @@ + 200 SCOPE_ENTER value x4 +- 200 JUMP 4 +- +- 4: + 212 LOAD_LOCAL(value x4) +@@ -1429,8 +1516,5 @@ + 212 SCOPE_ENTER value x5 +- 212 LOAD_LOCAL(value x5) +- 212 CALL_METHOD MyException.message (dynamic) +- 212 STORE_LOCAL(value message) +- 212 SCOPE_ENTER value message + 213 LOAD_MODULE object Predef +- 213 LOAD_LOCAL(value message) ++ ? LOAD_LOCAL(value x5) ++ 213 CALL_METHOD MyException.message (dynamic) + 213 CALL_METHOD scala.Predef.println (dynamic) +@@ -1478,3 +1562,3 @@ + startBlock: 1 +- blocks: [1,2,3,4,5,7] ++ blocks: [1,2,3,4,5,7,8] + +@@ -1502,4 +1586,11 @@ + 58 CALL_METHOD java.lang.IllegalArgumentException. (static-instance) +- 58 THROW(IllegalArgumentException) ++ ? STORE_LOCAL(value e) ++ ? JUMP 8 + ++ 8: ++ 62 LOAD_MODULE object Predef ++ 62 CONSTANT("RuntimeException") ++ 62 CALL_METHOD scala.Predef.println (dynamic) ++ 62 JUMP 2 ++ + 7: +@@ -1551,3 +1642,3 @@ + startBlock: 1 +- blocks: [1,3,4] ++ blocks: [1,3,4,5] + +@@ -1571,4 +1662,9 @@ + 229 CALL_METHOD MyException. (static-instance) +- 229 THROW(MyException) ++ ? JUMP 5 + ++ 5: ++ ? LOAD_LOCAL(variable monitor1) ++ 228 MONITOR_EXIT ++ 228 THROW(Throwable) ++ + 3: +@@ -1577,3 +1673,3 @@ + 228 MONITOR_EXIT +- ? THROW(Throwable) ++ 228 THROW(Throwable) + +@@ -1605,5 +1701,5 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, variable result, variable monitor2, variable monitorResult1 ++ locals: value exception$1, value args, variable result, variable monitor2, variable monitorResult1 + startBlock: 1 +- blocks: [1,3,4] ++ blocks: [1,3,4,5] + +@@ -1630,4 +1726,12 @@ + 245 CALL_METHOD MyException. (static-instance) +- 245 THROW(MyException) ++ ? STORE_LOCAL(value exception$1) ++ ? DROP ConcatClass ++ ? LOAD_LOCAL(value exception$1) ++ ? JUMP 5 + ++ 5: ++ ? LOAD_LOCAL(variable monitor2) ++ 244 MONITOR_EXIT ++ 244 THROW(Throwable) ++ + 3: +@@ -1636,3 +1740,3 @@ + 244 MONITOR_EXIT +- ? THROW(Throwable) ++ 244 THROW(Throwable) diff --git a/test/pending/run/inline-ex-handlers.scala b/test/pending/run/inline-ex-handlers.scala new file mode 100644 index 0000000000..964594d258 --- /dev/null +++ b/test/pending/run/inline-ex-handlers.scala @@ -0,0 +1,329 @@ +import scala.tools.partest.IcodeComparison + +object Test extends IcodeComparison { + override def printIcodeAfterPhase = "inlinehandlers" +} + +import scala.util.Random._ + +/** There should be no inlining taking place in this class */ +object TestInlineHandlersNoInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersNoInline") + var result = -1 + + try { + if (nextInt % 2 == 0) + throw new IllegalArgumentException("something") + result = 1 + } catch { + case e: StackOverflowError => + println("Stack overflow") + } + + result + } +} + +/** Just a simple inlining should take place in this class */ +object TestInlineHandlersSimpleInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersSimpleInline") + var result = -1 + + try { + if (nextInt % 2 == 0) + throw new IllegalArgumentException("something") + result = 1 + } catch { + case e: IllegalArgumentException => + println("IllegalArgumentException") + } + + result + } +} + +/** Inlining should take place because the handler is taking a superclass of the exception thrown */ +object TestInlineHandlersSubclassInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersSubclassInline") + var result = -1 + + try { + if (nextInt % 2 == 0) + throw new IllegalArgumentException("something") + result = 1 + } catch { + case e: RuntimeException => + println("RuntimeException") + } + + result + } +} + +/** For this class, the finally handler should be inlined */ +object TestInlineHandlersFinallyInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersFinallyInline") + var result = -1 + + try { + if (nextInt % 2 == 0) + throw new IllegalArgumentException("something") + result = 1 + } catch { + case e: Exception => throw e + } finally { + println("finally") + result = (result - 1) / 2 + } + + result + } +} + + +case class MyException(message: String) extends RuntimeException(message) + +/** For this class, we test inlining for a case class error */ +object TestInlineHandlersCaseClassExceptionInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersCaseClassExceptionInline") + var result = -1 + + try { + if (nextInt % 2 == 0) + throw new MyException("something") + result = 1 + } catch { + case MyException(message) => println(message) + } + + result + } +} + + +/** For this class, inline should take place in the inner handler */ +object TestInlineHandlersNestedHandlerInnerInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersNestedHandlersInnerInline") + var result = -1 + + try { + try { + if (nextInt % 2 == 0) + throw new MyException("something") + result = 1 + } catch { + case MyException(message) => println(message) + } + } catch { + case e: IllegalArgumentException => println("IllegalArgumentException") + } + + result + } +} + + +/** For this class, inline should take place in the outer handler */ +object TestInlineHandlersNestedHandlerOuterInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersNestedHandlersOuterInline") + var result = -1 + + try { + try { + if (nextInt % 2 == 0) + throw new MyException("something") + result = 1 + } catch { + case e: IllegalArgumentException => println("IllegalArgumentException") + } + } catch { + case MyException(message) => println(message) + } + + result + } +} + + +/** For this class, inline should take place in the all handlers (inner, outer and finally) */ +object TestInlineHandlersNestedHandlerAllInline { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersNestedHandlersOuterInline") + var result = -1 + + try { + try { + if (nextInt % 2 == 0) + throw new MyException("something") + result = 1 + } catch { + case MyException(message) => + println(message) + throw MyException(message) + } + } catch { + case MyException(message) => + println(message) + throw MyException(message) + } finally { + println("finally") + result = (result - 1) / 2 + } + + result + } +} + + +/** This class is meant to test whether the inline handler is copied only once for multiple inlines */ +object TestInlineHandlersSingleCopy { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersSingleCopy") + var result = -1 + + try { + + if (nextInt % 2 == 0) + throw new MyException("something") + + println("A side effect in the middle") + result = 3 // another one + + if (nextInt % 3 == 2) + throw new MyException("something else") + result = 1 + } catch { + case MyException(message) => + println(message) + } + + result + } +} + +/** This should test the special exception handler for synchronized blocks */ +object TestInlineHandlersSynchronized { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersSynchronized") + var result = "hello" + + // any exception thrown here will be caught by a default handler that does MONTIOR_EXIT on result :) + result.synchronized { + throw MyException(result) + } + + result.length + } +} + +/** This should test the special exception handler for synchronized blocks with stack */ +object TestInlineHandlersSynchronizedWithStack { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersSynchronizedWithStack") + var result = "hello" + + // any exception thrown here will be caught by a default handler that does MONTIOR_EXIT on result :) + result = "abc" + result.synchronized { + throw MyException(result) + } + + result.length + } +} + +/** This test should trigger a bug in the dead code elimination phase - it actually crashes ICodeCheckers +object TestInlineHandlersSynchronizedWithStackDoubleThrow { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersSynchronizedWithStackDoubleThrow") + var result = "a" + + // any exception thrown here will be caught by a default handler that does MONTIOR_EXIT on result :) + result += result.synchronized { throw MyException(result) } + result += result.synchronized { throw MyException(result) } + + result.length + } +} +*/ + +/** This test should check the preciseness of the inliner: it should not do any inlining here +* as it is not able to discern between the different exceptions +*/ +object TestInlineHandlersPreciseness { + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersCorrectHandler") + + try { + val exception: Throwable = + if (scala.util.Random.nextInt % 2 == 0) + new IllegalArgumentException("even") + else + new StackOverflowError("odd") + throw exception + } catch { + case e: IllegalArgumentException => + println("Correct, IllegalArgumentException") + case e: StackOverflowError => + println("Correct, StackOverflowException") + case t: Throwable => + println("WROOOONG, not Throwable!") + } + } +} + +/** This check should verify that the double no-local exception handler is duplicated correctly */ +object TestInlineHandlersDoubleNoLocal { + + val a1: String = "a" + val a2: String = "b" + + def main(args: Array[String]): Unit = { + println("TestInlineHandlersDoubleNoLocal") + + try { + a1.synchronized { + a2. synchronized { + throw new MyException("crash") + } + } + } catch { + case t: Throwable => println("Caught crash: " + t.toString) + } + + /* try { + val exception: Throwable = + if (scala.util.Random.nextInt % 2 == 0) + new IllegalArgumentException("even") + else + new StackOverflowError("odd") + throw exception + } catch { + case e: IllegalArgumentException => + println("Correct, IllegalArgumentException") + case e: StackOverflowError => + println("Correct, StackOverflowException") + case t: Throwable => + println("WROOOONG, not Throwable!") + }*/ + } +} diff --git a/test/pending/run/t5313.check b/test/pending/run/t5313.check new file mode 100644 index 0000000000..7a48b2b711 --- /dev/null +++ b/test/pending/run/t5313.check @@ -0,0 +1,12 @@ +STORE_LOCAL(variable kept1) +STORE_LOCAL(value result) +STORE_LOCAL(variable kept1) +STORE_LOCAL(variable kept2) +STORE_LOCAL(value kept3) +STORE_LOCAL(variable kept2) +STORE_LOCAL(variable kept4) +STORE_LOCAL(variable kept4) +STORE_LOCAL(variable kept5) +STORE_LOCAL(variable kept5) +STORE_LOCAL(variable kept6) +STORE_LOCAL(variable kept6) diff --git a/test/pending/run/t5313.scala b/test/pending/run/t5313.scala new file mode 100644 index 0000000000..4a5b076e6e --- /dev/null +++ b/test/pending/run/t5313.scala @@ -0,0 +1,54 @@ +import scala.tools.partest.IcodeComparison + +object Test extends IcodeComparison { + override def printIcodeAfterPhase = "dce" + + override def extraSettings: String = super.extraSettings + " -Yopt:l:classpath" + + override def code = + """class Foo { + def randomBoolean = scala.util.Random.nextInt % 2 == 0 + def bar = { + var kept1 = new Object + val result = new java.lang.ref.WeakReference(kept1) + kept1 = null // we can't eliminate this assignment because result can observe + // when the object has no more references. See SI-5313 + kept1 = new Object // but we can eliminate this one because kept1 has already been clobbered + var erased2 = null // we can eliminate this store because it's never used + val erased3 = erased2 // and this + var erased4 = erased2 // and this + val erased5 = erased4 // and this + var kept2: Object = new Object // ultimately can't be eliminated + while(randomBoolean) { + val kept3 = kept2 + kept2 = null // this can't, because it clobbers kept2, which is used + erased4 = null // safe to eliminate + println(kept3) + } + var kept4 = new Object // have to keep, it's used + try + println(kept4) + catch { + case _ : Throwable => kept4 = null // have to keep, it clobbers kept4 which is used + } + var kept5 = new Object + print(kept5) + kept5 = null // can't eliminate it's a clobber and it's used + print(kept5) + kept5 = null // can eliminate because we don't care about clobbers of nulls + while(randomBoolean) { + var kept6: AnyRef = null // not used, but have to keep because it clobbers the next used store + // on the back edge of the loop + kept6 = new Object // used + println(kept6) + } + result + } + }""".stripMargin + + override def show() { + val storeLocal = "STORE_LOCAL" + val lines1 = collectIcode() filter (_ contains storeLocal) map (x => x.drop(x.indexOf(storeLocal))) + println(lines1 mkString "\n") + } +} diff --git a/test/pending/run/t6955.scala b/test/pending/run/t6955.scala new file mode 100644 index 0000000000..787617eff1 --- /dev/null +++ b/test/pending/run/t6955.scala @@ -0,0 +1,33 @@ +import scala.tools.partest.IcodeComparison + +// this class should compile to code that uses switches (twice) +class Switches { + type Tag = Byte + + def switchBad(i: Tag): Int = i match { // notice type of i is Tag = Byte + case 1 => 1 + case 2 => 2 + case 3 => 3 + case _ => 0 + } + + // this worked before, should keep working + def switchOkay(i: Byte): Int = i match { + case 1 => 1 + case 2 => 2 + case 3 => 3 + case _ => 0 + } +} + +object Test extends IcodeComparison { + // ensure we get two switches out of this -- ignore the rest of the output for robustness + // exclude the constant we emit for the "SWITCH ..." string below (we get the icode for all the code you see in this file) + override def show() = { + val expected = 2 + val actual = (collectIcode() filter { + x => x.indexOf("SWITCH ...") >= 0 && x.indexOf("CONSTANT(") == -1 + }).size + assert(actual == expected, s"switches expected: $expected, actual: $actual") + } +} diff --git a/test/pending/run/t6956.scala b/test/pending/run/t6956.scala new file mode 100644 index 0000000000..57d721807d --- /dev/null +++ b/test/pending/run/t6956.scala @@ -0,0 +1,31 @@ +import scala.tools.partest.IcodeComparison + +class Switches { + private[this] final val ONE = 1 + + def switchBad(i: Byte): Int = i match { + case ONE => 1 + case 2 => 2 + case 3 => 3 + case _ => 0 + } + + def switchOkay(i: Byte): Int = i match { + case 1 => 1 + case 2 => 2 + case 3 => 3 + case _ => 0 + } +} + +object Test extends IcodeComparison { + // ensure we get two switches out of this -- ignore the rest of the output for robustness + // exclude the constant we emit for the "SWITCH ..." string below (we get the icode for all the code you see in this file) + override def show() = { + val expected = 2 + val actual = (collectIcode() filter { + x => x.indexOf("SWITCH ...") >= 0 && x.indexOf("CONSTANT(") == -1 + }).size + assert(actual == expected, s"switches expected: $expected, actual: $actual") + } +} diff --git a/test/pending/run/test-cpp.check b/test/pending/run/test-cpp.check new file mode 100644 index 0000000000..ff4c9bf2bf --- /dev/null +++ b/test/pending/run/test-cpp.check @@ -0,0 +1,81 @@ +--- a ++++ b +@@ -54,3 +54,3 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, value x, value y ++ locals: value args + startBlock: 1 +@@ -59,10 +59,6 @@ + 1: +- 52 CONSTANT(2) +- 52 STORE_LOCAL(value x) + 52 SCOPE_ENTER value x +- 53 LOAD_LOCAL(value x) +- 53 STORE_LOCAL(value y) + 53 SCOPE_ENTER value y + 54 LOAD_MODULE object Predef +- 54 LOAD_LOCAL(value y) ++ 54 CONSTANT(2) + 54 BOX INT +@@ -109,3 +105,3 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, value x, value y ++ locals: value args, value x + startBlock: 1 +@@ -118,7 +114,5 @@ + 81 SCOPE_ENTER value x +- 82 LOAD_LOCAL(value x) +- 82 STORE_LOCAL(value y) + 82 SCOPE_ENTER value y + 83 LOAD_MODULE object Predef +- 83 LOAD_LOCAL(value y) ++ 83 LOAD_LOCAL(value x) + 83 BOX INT +@@ -152,3 +146,3 @@ + def main(args: Array[String] (ARRAY[REF(class String)])): Unit { +- locals: value args, value x, value y ++ locals: value args + startBlock: 1 +@@ -157,10 +151,6 @@ + 1: +- 66 THIS(TestAliasChainDerefThis) +- 66 STORE_LOCAL(value x) + 66 SCOPE_ENTER value x +- 67 LOAD_LOCAL(value x) +- 67 STORE_LOCAL(value y) + 67 SCOPE_ENTER value y + 68 LOAD_MODULE object Predef +- 68 LOAD_LOCAL(value y) ++ 68 THIS(Object) + 68 CALL_METHOD scala.Predef.println (dynamic) +@@ -193,3 +183,3 @@ + def test(x: Int (INT)): Unit { +- locals: value x, value y ++ locals: value x + startBlock: 1 +@@ -198,7 +188,5 @@ + 1: +- 29 LOAD_LOCAL(value x) +- 29 STORE_LOCAL(value y) + 29 SCOPE_ENTER value y + 30 LOAD_MODULE object Predef +- 30 LOAD_LOCAL(value y) ++ 30 LOAD_LOCAL(value x) + 30 BOX INT +@@ -240,7 +228,5 @@ + 96 SCOPE_ENTER variable x +- 97 LOAD_LOCAL(variable x) +- 97 STORE_LOCAL(variable y) + 97 SCOPE_ENTER variable y + 98 LOAD_MODULE object Predef +- 98 LOAD_LOCAL(variable y) ++ 98 LOAD_LOCAL(variable x) + 98 BOX INT +@@ -250,6 +236,4 @@ + 100 STORE_LOCAL(variable y) +- 101 LOAD_LOCAL(variable y) +- 101 STORE_LOCAL(variable x) + 102 LOAD_MODULE object Predef +- 102 LOAD_LOCAL(variable x) ++ 102 LOAD_LOCAL(variable y) + 102 BOX INT diff --git a/test/pending/run/test-cpp.scala b/test/pending/run/test-cpp.scala new file mode 100644 index 0000000000..4fca67d51e --- /dev/null +++ b/test/pending/run/test-cpp.scala @@ -0,0 +1,104 @@ +/** + * The only change is in the decision to replace a LOAD_LOCAL(l) + * in the copy-propagation performed before ClosureElimination. + * + * In the general case, the local variable 'l' is connected through + * an alias chain with other local variables and at the end of the + * alias chain there may be a Value, call it 'v'. + * + * If 'v' is cheaper to access (it is a Deref(This) or Const(_)), then + * replace the instruction to load it from the cheaper place. + * Otherwise, we use the local variable at the end of the alias chain + * instead of 'l'. + */ + +import scala.tools.partest.IcodeComparison + +object Test extends IcodeComparison { + override def printIcodeAfterPhase = "dce" +} + +import scala.util.Random._ + +/** + * The example in the bug report (Issue-5321): an alias chain which store + * an Unknown. Should remove local variable 'y'. + */ +object TestBugReport { + def test(x: Int) = { + val y = x + println(y) + } +} + +/** + * The code taken from scala.tools.nsc.settings.Settings: + * After inlining of the setter is performed, there is an opportunity for + * copy-propagation to eliminate some local variables. + */ +object TestSetterInline { + private var _postSetHook: this.type => Unit = (x: this.type) => () + def withPostSetHook(f: this.type => Unit): this.type = { _postSetHook = f ; this } +} + + +/** + * The access of the local variable 'y' should be replaced by the + * constant. + */ +object TestAliasChainConstant { + + def main(args: Array[String]): Unit = { + val x = 2 + val y = x + println(y) + } +} + +/** + * At the end of the alias chain we have a reference to 'this'. + * The local variables should be all discarded and replace by a + * direct reference to this + */ +class TestAliasChainDerefThis { + + def main(args: Array[String]): Unit = { + val x = this + val y = x + println(y) + } +} + +/** + * At the end of the alias chain, there is the value of a field. + * The use of variable 'y' should be replaced by 'x', not by an access + * to the field 'f' since it is more costly. + */ +object TestAliasChainDerefField { + def f = nextInt + + def main(args: Array[String]): Unit = { + val x = f + val y = x + println(y) + } +} + + +/** + * The first time 'println' is called, 'x' is replaced by 'y' + * and the second time, 'y' is replaced by 'x'. But none of them + * can be removed. + */ +object TestDifferentBindings { + + def main(args: Array[String]): Unit = { + var x = nextInt + var y = x + println(y) + + y = nextInt + x = y + println(x) + } +} -- cgit v1.2.3