/* NSC -- new Scala compiler
 * Copyright 2005-2012 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala
package tools.nsc
package backend.jvm

import scala.tools.asm
import scala.tools.nsc.io.AbstractFile
import GenBCode._
import BackendReporting._
import scala.reflect.internal.Flags
import scala.tools.nsc.backend.jvm.BCodeHelpers.ScalaSigBytes
import scala.tools.nsc.reporters.NoReporter

/*
 *  Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
 *
 *  @author  Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded
 *  @version 1.0
 *
 */
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.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
    }
  }

  def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]

  final def traitSuperAccessorName(sym: Symbol): Name = {
    val name = sym.javaSimpleName
    if (sym.isMixinConstructor) name
    else name.append(nme.NAME_JOIN_STRING)
  }

  /**
   * 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) {
      // lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true.
      // 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() && exitingPickler(sym.rawowner.isAnonymousFunction)) {
      // scala/bug#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) = {
      // 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).
      val enclCls = nextEnclosingClass(method)
      exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls
    }

    def enclosingMethod(sym: Symbol): Option[Symbol] = {
      if (sym.isClass || sym == NoSymbol) None
      else if (sym.isMethod && !sym.isGetter) {
        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))
    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 parameterized 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] = {
    // specialized classes are always top-level, see comment in BTypes
    if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
      val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
      val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym)
      for (m <- methodOpt) assert(m.owner == enclosingClass, s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
      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 scala/bug#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 {
      withoutReporting(sym.info)
      sym.isErroneous
    }

  @inline private def withoutReporting[T](fn : => T) = {
    val currentReporter = reporter
    reporter = NoReporter
    try fn finally reporter = currentReporter
  }


  /*
   * must-single-thread
   */
  def getFileForClassfile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = {
    getFile(base, clsName, suffix)
  }

  /*
   * must-single-thread
   */
  def getOutFolder(csym: Symbol, cName: String, cunit: CompilationUnit): _root_.scala.tools.nsc.io.AbstractFile =
    _root_.scala.util.Try {
      outputDirectory(csym)
    }.recover {
      case ex: Throwable =>
        reporter.error(cunit.body.pos, s"Couldn't create file for class $cName\n${ex.getMessage}")
        null
    }.get

  var pickledBytes = 0 // statistics

  // -----------------------------------------------------------------------------------------
  // 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://github.com/scala/bug/issues/3872
  // -----------------------------------------------------------------------------------------

  /*  An `asm.ClassWriter` that uses `jvmWiseLUB()`
   *  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
   */
  final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {

    /**
     * This method is used by asm when computing stack map frames. It is thread-safe: it depends
     * only on the BTypes component, which does not depend on global.
     * TODO @lry move to a different place where no global is in scope, on bTypes.
     */
    override def getCommonSuperClass(inameA: String, inameB: String): String = {
      // All types that appear in a class node need to have their ClassBType cached, see [[cachedClassBType]].
      val a = cachedClassBType(inameA).get
      val b = cachedClassBType(inameB).get
      val lub = a.jvmWiseLUB(b).get
      val lubName = lub.internalName
      assert(lubName != "scala/Any")
      lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things.
    }
  }

  /*
   * must-single-thread
   */
  object isJavaEntryPoint {

    /*
     * must-single-thread
     */
    def apply(sym: Symbol, csymCompUnit: CompilationUnit): Boolean = {
      def fail(msg: String, pos: Position = sym.pos) = {
        reporter.warning(sym.pos,
          sym.name +
          s" 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(s"$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 == definitions.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 (definitions.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 succeed, or issue some additional warnings for things which look like
          // attempts to be java main methods.
        else (possibles exists definitions.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
                    definitions.isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
                case tp =>
                  fail(s"don't know what this is: $tp", m.pos)
              }
            }
          }
        }
      }
    }

  }

  /*
   * must-single-thread
   */
  def initBytecodeWriter(entryPoints: List[Symbol]): 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 (_.fullName('.')) match {
            case Nil      =>
              log("No Main-Class designated or discovered.")
            case name :: Nil =>
              log(s"Unique entry point: setting Main-Class to $name")
              settings.mainClass.value = name
            case names =>
              log(s"No Main-Class due to multiple entry points:\n  ${names.mkString("\n  ")}")
          }
        }
        else log(s"Main-Class was specified: ${settings.mainClass.value}")

        new DirectToJarfileWriter(f.file)

      case _ => factoryNonJarBytecodeWriter()
    }
  }

  /*
   *  must-single-thread
   */
  def fieldSymbols(cls: Symbol): List[Symbol] = {
    for (f <- cls.info.decls.toList ;
         if !f.isMethod && f.isTerm && !f.isModule
    ) yield f
  }

  /*
   * can-multi-thread
   */
  def methodSymbols(cd: ClassDef): List[Symbol] = {
    cd.impl.body collect { case dd: DefDef => dd.symbol }
  }

  /*
   *  must-single-thread
   */
  def serialVUID(csym: Symbol): Option[Long] = csym getAnnotation definitions.SerialVersionUIDAttr collect {
    case AnnotationInfo(_, _, (_, LiteralAnnotArg(const)) :: Nil) => const.longValue
  }

  /*
   * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner
   * classes in BTypes.scala.
   *
   * `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of
   * each inner class it lists (those are looked up and included).
   *
   * This method serializes in the InnerClasses JVM attribute in an appropriate order, not
   * necessarily that given by `refedInnerClasses`.
   *
   * can-multi-thread
   */
  final def addInnerClasses(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
    val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct

    // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
    for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) {
      // Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes.
      val Some(e) = nestedClass.innerClassAttributeEntry.get
      jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags)
    }
  }

  /*
   * 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.)
   *
   */
  trait BCPickles {

    import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer }

    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
    }

    /*
     * can-multi-thread
     */
    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)
    }

    /*
     * can-multi-thread
     */
    def pickleMarkerLocal = {
      createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex)
    }

    /*
     * can-multi-thread
     */
    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.
     *
     *  must-single-thread
     */
    def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = {
      currentRun.symData get sym match {
        case Some(pickle) if !sym.isModuleClass => // pickles for module classes are in the companion / mirror class
          val scalaAnnot = {
            val sigBytes = new ScalaSigBytes(pickle.bytes.take(pickle.writeIndex))
            val (annTp, arg) = if (sigBytes.fitsInOneString) {
              val tp = definitions.ScalaSignatureAnnotation.tpe
              (tp, LiteralAnnotArg(Constant(sigBytes.strEncode)))
            } else {
              val tp = definitions.ScalaLongSignatureAnnotation.tpe
              (tp, ArrayAnnotArg(sigBytes.arrEncode.map(s => LiteralAnnotArg(Constant(s)))))
            }
            AnnotationInfo(annTp, Nil, (nme.bytes, arg) :: Nil)
          }
          pickledBytes += pickle.writeIndex
          currentRun.symData -= sym
          currentRun.symData -= sym.companionSymbol
          Some(scalaAnnot)
        case _ =>
          None
      }
    }

  } // end of trait BCPickles

  trait BCInnerClassGen {

    def debugLevel = settings.debuginfo.indexOfChoice

    final val emitSource = debugLevel >= 1
    final val emitLines  = debugLevel >= 2
    final val emitVars   = debugLevel >= 3

    /**
     * The class internal name for a given class symbol.
     */
    final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName
  } // 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"))

    /**
     * Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the
     * typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding
     * symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure).
     *
     * For Scala annotations this is OK: they are stored in the pickle and ignored by the backend.
     * Java annotations on the other hand are additionally emitted to the classfile in Java's format.
     *
     * This means that [[Type]] instances within an AnnotationInfo reach the backend non-erased. Examples:
     *   - @(javax.annotation.Resource @annotation.meta.getter) val x = 0
     *     Here, annotationInfo.atp is an AnnotatedType.
     *   - @SomeAnnotation[T] val x = 0
     *     In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot
     *     actually happen because Java annotations cannot be generic.
     *   - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0
     *     The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the
     *     non-erased existential type.
     */
    def erasedType(tp: Type): Type = enteringErasure {
      // make sure we don't erase value class references to the type that the value class boxes
      // this is basically the same logic as in erasure's preTransform, case Literal(classTag).
      tp.dealiasWiden match {
        case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr)
        case tpe => erasure.erasure(tpe.typeSymbol)(tpe)
      }
    }

    def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor

    /** 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 _ =>
          // scala/bug#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)

    /*
     * For arg a LiteralAnnotArg(constt) with const.tag in {ClazzTag, EnumTag}
     * as well as for arg a NestedAnnotArg
     *   must-single-thread
     * Otherwise it's safe to call from multiple threads.
     */
    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, typeToBType(erasedType(const.typeValue)).toASMType)
              case EnumTag =>
                val edesc  = descriptorForErasedType(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 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 = descriptorForErasedType(typ) // the class descriptor of the nested annotation class
          val nestedVisitor = av.visitAnnotation(name, desc)
          emitAssocs(nestedVisitor, assocs)
      }
    }

    /*
     * In general,
     *   must-single-thread
     * but not  necessarily always.
     */
    def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) {
      for ((name, value) <- assocs) {
        emitArgument(av, name.toString(), value)
      }
      av.visitEnd()
    }

    /*
     * must-single-thread
     */
    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(descriptorForErasedType(typ), isRuntimeVisible(annot))
        emitAssocs(av, assocs)
      }
    }

    /*
     * must-single-thread
     */
    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(descriptorForErasedType(typ), isRuntimeVisible(annot))
        emitAssocs(av, assocs)
      }
    }

    /*
     * must-single-thread
     */
    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(descriptorForErasedType(typ), isRuntimeVisible(annot))
        emitAssocs(av, assocs)
      }
    }

    /*
     * must-single-thread
     */
    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, descriptorForErasedType(typ), isRuntimeVisible(annot))
        emitAssocs(pannVisitor, assocs)
      }
    }

    /*
     * must-single-thread
     */
    def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) = {
      for (param <- params) {
        var access = asm.Opcodes.ACC_FINAL
        if (param.isArtifact)
          access |= asm.Opcodes.ACC_SYNTHETIC
        jmethod.visitParameter(param.name.decoded, access)
      }
    }
  } // end of trait BCAnnotGen

  trait BCJGenSigGen {

    // @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
    )

    /* @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 = {
      val memberTpe = enteringErasure(owner.thisType.memberInfo(sym))
      getGenericSignature(sym, owner, memberTpe)
    }

    def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = {
      if (!needsGenericSignature(sym)) { return null }

      // Make sure to build (and cache) a ClassBType for every type that is referenced in
      // a generic signature. Otherwise, looking up the type later (when collecting nested
      // classes, or when computing stack map frames) might fail.
      def enterReferencedClass(sym: Symbol): Unit = enteringJVM(classBTypeFromSymbol(sym))

      val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe, enterReferencedClass)
      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,
            sm"""|compiler bug: created invalid generic signature for $sym in ${sym.owner.skipPackageObject.fullName}
                 |signature: $sig
                 |if this is reproducible, please report bug at https://github.com/scala/bug/issues
              """.trim)
          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,
            sm"""|compiler bug: created generic signature for $sym in ${sym.owner.skipPackageObject.fullName} that does not conform to its erasure
                 |signature: $sig
                 |original type: $memberTpe
                 |normalized type: $normalizedTpe
                 |erasure type: $bytecodeTpe
                 |if this is reproducible, please report bug at https://github.com/scala/bug/issues
              """.trim)
           return null
        }
      }

      sig
    }

  } // end of trait BCJGenSigGen

  trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen {

    /* Adds a @remote annotation, actual use unknown.
     *
     * Invoked from genMethod() and addForwarder().
     *
     * must-single-thread
     */
    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(definitions.RemoteExceptionClass.tpe)
        val arg = Literal(c) setType c.tpe
        meth.addAnnotation(appliedType(definitions.ThrowsClass, c.tpe), arg)
      }
    }

    /* Add a forwarder for method m. Used only from addForwarders().
     *
     * must-single-thread
     */
    private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, moduleClass: Symbol, m: Symbol): Unit = {
      def staticForwarderGenericSignature: String = {
        // scala/bug#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(m))
        val erasedMemberType = erasure.erasure(m)(memberTpe)
        if (erasedMemberType =:= m.info)
          getGenericSignature(m, moduleClass, memberTpe)
        else null
      }

      val moduleName     = internalName(moduleClass)
      val methodInfo     = moduleClass.thisType.memberInfo(m)
      val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType
      // 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 scala/bug#4827.
       */
      // TODO: evaluate the other flags we might be dropping on the floor here.
      // TODO: ACC_SYNTHETIC ?
      val flags = GenBCode.PublicStatic | (
        if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0
      )

      // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
      val jgensig = staticForwarderGenericSignature
      addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
      val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass)
      val thrownExceptions: List[String] = getExceptions(throws)

      val jReturnType = typeToBType(methodInfo.resultType)
      val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor
      val mirrorMethodName = m.javaSimpleName.toString
      val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
        flags,
        mirrorMethodName,
        mdesc,
        jgensig,
        mkArray(thrownExceptions)
      )

      emitAnnotations(mirrorMethod, others)
      emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations))

      mirrorMethod.visitCode()

      mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(moduleClass).descriptor)

      var index = 0
      for(jparamType <- paramJavaTypes) {
        mirrorMethod.visitVarInsn(jparamType.typedOpcode(asm.Opcodes.ILOAD), index)
        assert(!jparamType.isInstanceOf[MethodBType], jparamType)
        index += jparamType.size
      }

      mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, methodBTypeFromSymbol(m).descriptor, false)
      mirrorMethod.visitInsn(jReturnType.typedOpcode(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.
     *
     * must-single-thread
     */
    def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) {
      assert(moduleClass.isModuleClass, moduleClass)
      debuglog(s"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(s"Potentially conflicting names for forwarders: $conflictingNames")

      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': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}")
        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 {
          log(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'")
          addForwarder(isRemoteClass, jclass, moduleClass, m)
        }
      }
    }

    /*
     * 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.
     *
     * must-single-thread
     */
    def getExceptions(excs: List[AnnotationInfo]): List[String] = {
      for (ThrownException(tp) <- excs.distinct)
      yield {
        val erased = erasedType(tp)
        internalName(erased.typeSymbol)
      }
    }

  } // end of trait BCForwardersGen

  trait BCClassGen extends BCInnerClassGen {

    // Used as threshold above which a tableswitch bytecode instruction is preferred over a lookupswitch.
    // There's a space tradeoff between these multi-branch instructions (details in the JVM spec).
    // The particular value in use for `MIN_SWITCH_DENSITY` reflects a heuristic.
    val MIN_SWITCH_DENSITY = 0.7

    /*
     *  Add public static final field serialVersionUID with value `id`
     *
     *  can-multi-thread
     */
    def addSerialVUID(id: Long, jclass: asm.ClassVisitor) {
      // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)`
      jclass.visitField(
        GenBCode.PublicStaticFinal,
        "serialVersionUID",
        "J",
        null, // no java-generic-signature
        new java.lang.Long(id)
      ).visitEnd()
    }
  } // end of trait BCClassGen

  /* functionality for building plain and mirror classes */
  abstract class JCommonBuilder
    extends BCInnerClassGen
    with    BCAnnotGen
    with    BCForwardersGen
    with    BCPickles { }

  /* builder of mirror classes */
  class JMirrorBuilder extends JCommonBuilder {

    /* 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.
     *
     *  must-single-thread
     */
    def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = {
      assert(moduleClass.isModuleClass)
      assert(moduleClass.companionClass == NoSymbol, moduleClass)

      val bType = mirrorClassClassBType(moduleClass)
      val mirrorClass = new asm.tree.ClassNode
      mirrorClass.visit(
        classfileVersion,
        bType.info.get.flags,
        bType.internalName,
        null /* no java-generic-signature */,
        ObjectRef.internalName,
        EMPTY_STRING_ARRAY
      )

      if (emitSource)
        mirrorClass.visitSource("" + cunit.source, null /* SourceDebugExtension */)

      val ssa = getAnnotPickle(bType.internalName, moduleClass.companionSymbol)
      mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
      emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa)

      addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass)

      mirrorClass.visitEnd()

      ("" + moduleClass.name) // this side-effect is necessary, really.

      mirrorClass
    }

  } // end of class JMirrorBuilder

  /* builder of bean info classes */
  class JBeanInfoBuilder extends BCInnerClassGen {

    /*
     * Generate a bean info class that describes the given class.
     *
     * @author Ross Judson (ross.judson@soletta.com)
     *
     * must-single-thread
     */
    def genBeanInfoClass(cls: Symbol, cunit: CompilationUnit, fieldSymbols: List[Symbol], methodSymbols: List[Symbol]): asm.tree.ClassNode = {

      def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString }

      val beanInfoType = beanInfoClassClassBType(cls)

      val beanInfoClass = new asm.tree.ClassNode
      beanInfoClass.visit(
        classfileVersion,
        beanInfoType.info.get.flags,
        beanInfoType.internalName,
        null, // no java-generic-signature
        sbScalaBeanInfoRef.internalName,
        EMPTY_STRING_ARRAY
      )

      beanInfoClass.visitSource(
        cunit.source.toString,
        null /* SourceDebugExtension */
      )

      var fieldList = List[String]()

      for (f <- fieldSymbols if f.hasGetter;
                 g = f.getterIn(cls);
                 s = f.setterIn(cls);
	         if g.isPublic && !(f.name startsWith "$")
          ) {
             // inserting $outer breaks the bean
             fieldList = javaSimpleName(f) :: javaSimpleName(g) :: (if (s != NoSymbol) javaSimpleName(s) else null) :: fieldList
      }

      val methodList: List[String] =
	     for (m <- methodSymbols
	          if !m.isConstructor &&
	          m.isPublic &&
	          !(m.name startsWith "$") &&
	          !m.isGetter &&
	          !m.isSetter)
       yield javaSimpleName(m)

      val constructor = beanInfoClass.visitMethod(
        asm.Opcodes.ACC_PUBLIC,
        INSTANCE_CONSTRUCTOR_NAME,
        "()V",
        null, // no java-generic-signature
        EMPTY_STRING_ARRAY // no throwable exceptions
      )

      val stringArrayJType: BType = ArrayBType(StringRef)
      val conJType: BType = MethodBType(
        classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil,
        UNIT
      )

      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(StringRef.typedOpcode(asm.Opcodes.IASTORE))
          fi += 1
        }
      }

      constructor.visitCode()

      constructor.visitVarInsn(asm.Opcodes.ALOAD, 0)
      // push the class
      constructor.visitLdcInsn(classBTypeFromSymbol(cls).toASMType)

      // push the string array of field information
      constructor.visitLdcInsn(new java.lang.Integer(fieldList.length))
      constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
      push(fieldList)

      // push the string array of method information
      constructor.visitLdcInsn(new java.lang.Integer(methodList.length))
      constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
      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.descriptor, false)
      constructor.visitInsn(asm.Opcodes.RETURN)

      constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
      constructor.visitEnd()

      beanInfoClass.visitEnd()

      beanInfoClass
    }

  } // end of class JBeanInfoBuilder

  trait JAndroidBuilder {
    self: BCInnerClassGen =>

    /* 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.
     */
    val androidFieldName = newTermName("CREATOR")

    /*
     * must-single-thread
     */
    def isAndroidParcelableClass(sym: Symbol) =
      (AndroidParcelableInterface != NoSymbol) &&
      (sym.parentSymbols contains AndroidParcelableInterface)

    /*
     * must-single-thread
     */
    def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) {
      val androidCreatorType = classBTypeFromSymbol(AndroidCreatorClass)
      val tdesc_creator = androidCreatorType.descriptor

      cnode.visitField(
        GenBCode.PublicStaticFinal,
        "CREATOR",
        tdesc_creator,
        null, // no java-generic-signature
        null  // no initial value
      ).visitEnd()

      val moduleName = (thisName + "$")

      // GETSTATIC `moduleName`.MODULE$ : `moduleName`;
      clinit.visitFieldInsn(
        asm.Opcodes.GETSTATIC,
        moduleName,
        strMODULE_INSTANCE_FIELD,
        "L" + moduleName + ";"
      )

      // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator;
      val bt = MethodBType(Nil, androidCreatorType)
      clinit.visitMethodInsn(
        asm.Opcodes.INVOKEVIRTUAL,
        moduleName,
        "CREATOR",
        bt.descriptor,
        false
      )

      // PUTSTATIC `thisName`.CREATOR;
      clinit.visitFieldInsn(
        asm.Opcodes.PUTSTATIC,
        thisName,
        "CREATOR",
        tdesc_creator
      )
    }

  } // 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
  }

  class TestOp(val op: Int) extends AnyVal {
    import TestOp._
    def negate = this match {
      case EQ => NE
      case NE => EQ
      case LT => GE
      case GE => LT
      case GT => LE
      case LE => GT
    }
    def opcodeIF = asm.Opcodes.IFEQ + op
    def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op
  }

  object TestOp {
    // the order here / op numbers are important to get the correct result when calling opcodeIF
    val EQ = new TestOp(0)
    val NE = new TestOp(1)
    val LT = new TestOp(2)
    val GE = new TestOp(3)
    val GT = new TestOp(4)
    val LE = new TestOp(5)
  }

  class InvokeStyle(val style: Int) extends AnyVal {
    import InvokeStyle._
    def isVirtual: Boolean = this == Virtual
    def isStatic : Boolean = this == Static
    def isSpecial: Boolean = this == Special
    def isSuper  : Boolean = this == Super

    def hasInstance = this != Static
  }

  object InvokeStyle {
    val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface
    val Static  = new InvokeStyle(1) // InvokeStatic
    val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors)
    val Super   = new InvokeStyle(3) // InvokeSpecial (super calls)
  }

  /**
   * Contains helpers around converting a Scala signature (array of bytes) into an array of `Long`.
   *  Details about the storage format of pickles at the bytecode level (classfile annotations) can be found in SIP-10.
   */
  class ScalaSigBytes(bytes: Array[Byte]) {
    override def toString = (bytes map { byte => (byte & 0xff).toHexString }).mkString("[ ", " ", " ]")
    lazy val sevenBitsMayBeZero: Array[Byte] = {
      mapToNextModSevenBits(scala.reflect.internal.pickling.ByteCodecs.encode8to7(bytes))
    }

    private def mapToNextModSevenBits(src: Array[Byte]): Array[Byte] = {
      var i = 0
      val srclen = src.length
      while (i < srclen) {
        val in = src(i)
        src(i) = (if (in == 0x7f) 0.toByte else (in + 1).toByte)
        i += 1
      }
      src
    }

    /* In order to store a byte array (the pickle) using a bytecode-level annotation,
     * the most compact representation is used (which happens to be string-constant and not byte array as one would expect).
     * However, a String constant in a classfile annotation is limited to a maximum of 65535 characters.
     * Method `fitsInOneString` tells us whether the pickle can be held by a single classfile-annotation of string-type.
     * Otherwise an array of strings will be used.
     */
    def fitsInOneString: Boolean = {
      // due to escaping, a zero byte in a classfile-annotation of string-type takes actually two characters.
      var i = 0
      var numZeros = 0
      while (i < sevenBitsMayBeZero.length) {
        if (sevenBitsMayBeZero(i) == 0) numZeros += 1
        i += 1
      }

      (sevenBitsMayBeZero.length + numZeros) <= 65535
    }
    def strEncode: String = {
      val ca = ubytesToCharArray(sevenBitsMayBeZero)
      new java.lang.String(ca)
    }

    final def arrEncode: Array[String] = {
      var strs: List[String]  = Nil
      val bSeven: Array[Byte] = 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 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
    }
  }
}
