All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.backend.jvm.BCodeHelpers.scala Maven / Gradle / Ivy

There is a newer version: 3.6.4-RC1-bin-20241220-0bfa1af-NIGHTLY
Show newest version
package dotty.tools
package backend
package jvm

import scala.language.unsafeNulls

import scala.annotation.threadUnsafe
import scala.tools.asm
import scala.tools.asm.AnnotationVisitor
import scala.tools.asm.ClassWriter
import scala.collection.mutable

import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.Trees
import dotty.tools.dotc.core.Annotations.*
import dotty.tools.dotc.core.Constants.*
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Phases.*
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.Names.Name
import dotty.tools.dotc.core.NameKinds.ExpandedName
import dotty.tools.dotc.core.Signature
import dotty.tools.dotc.core.StdNames.*
import dotty.tools.dotc.core.NameKinds
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.TypeErasure
import dotty.tools.dotc.transform.GenericSignatures
import dotty.tools.dotc.transform.ElimErasedValueType
import dotty.tools.io.AbstractFile
import dotty.tools.dotc.report

import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions

/*
 *  Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
 *
 *  @author  Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded
 *  @version 1.0
 *
 */
trait BCodeHelpers extends BCodeIdiomatic {
  // for some reason singleton types aren't allowed in constructor calls. will need several casts in code to enforce
  //import global.*
  import bTypes.*
  import tpd.*
  import coreBTypes.*
  import int.{_, given}
  import DottyBackendInterface.*

  // We need to access GenBCode phase to get access to post-processor components.
  // At this point it should always be initialized already.
  protected lazy val backendUtils = genBCodePhase.asInstanceOf[GenBCode].postProcessor.backendUtils

  def ScalaATTRName: String = "Scala"
  def ScalaSignatureATTRName: String = "ScalaSig"

  @threadUnsafe lazy val AnnotationRetentionAttr: ClassSymbol = requiredClass("java.lang.annotation.Retention")
  @threadUnsafe lazy val AnnotationRetentionSourceAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("SOURCE")
  @threadUnsafe lazy val AnnotationRetentionClassAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("CLASS")
  @threadUnsafe lazy val AnnotationRetentionRuntimeAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("RUNTIME")

  val bCodeAsmCommon: BCodeAsmCommon[int.type] = new BCodeAsmCommon(int)

  final def traitSuperAccessorName(sym: Symbol): String = {
    val nameString = sym.javaSimpleName.toString
    if (sym.name == nme.TRAIT_CONSTRUCTOR) nameString
    else nameString + "$"
  }


  /*
   * can-multi-thread
   */
  def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = {
    new asm.Attribute(name) {
      override def write(classWriter: ClassWriter, code: Array[Byte],
        codeLength: Int, maxStack: Int, maxLocals: Int): asm.ByteVector = {
        val byteVector = new asm.ByteVector(len)
        byteVector.putByteArray(b, offset, len)
        byteVector
      }
    }
  }

  /*
   * 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 dotty.tools.dotc.core.unpickleScala2.{ 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 pickleMarkerLocal = {
      createJAttribute(ScalaSignatureATTRName, versionPickle.bytes, 0, versionPickle.writeIndex)
    }

    /*
     * can-multi-thread
     */
    def pickleMarkerForeign = {
      createJAttribute(ScalaATTRName, new Array[Byte](0), 0, 0)
    }
  } // end of trait BCPickles

  trait BCInnerClassGen {

    def debugLevel = 3 // 0 -> no debug info; 1-> filename; 2-> lines; 3-> varnames

    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 = {
      // For each java class, the scala compiler creates a class and a module (thus a module class).
      // If the `sym` is a java module class, we use the java class instead. This ensures that the
      // ClassBType is created from the main class (instead of the module class).
      // The two symbols have the same name, so the resulting internalName is the same.
      val classSym = if (sym.is(JavaDefined) && sym.is(ModuleClass)) sym.linkedClass else sym
      getClassBType(classSym).internalName
    }

    private def assertClassNotArray(sym: Symbol): Unit = {
      assert(sym.isClass, sym)
      assert(sym != defn.ArrayClass || compilingArray, sym)
    }

    private def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = {
      assertClassNotArray(sym)
      assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym)
    }

    /**
     * The ClassBType for a class symbol.
     *
     * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly,
     * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files
     * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes
     * in the classfile method signature.
     *
     * Note that the referenced class symbol may be an implementation class. For example when
     * compiling a mixed-in method that forwards to the static method in the implementation class,
     * the class descriptor of the receiver (the implementation class) is obtained by creating the
     * ClassBType.
     */
    final def getClassBType(sym: Symbol): ClassBType = {
      assertClassNotArrayNotPrimitive(sym)

      if (sym == defn.NothingClass) srNothingRef
      else if (sym == defn.NullClass) srNullRef
      else classBTypeFromSymbol(sym)
    }

    /*
     * must-single-thread
     */
    final def asmMethodType(msym: Symbol): MethodBType = {
      assert(msym.is(Method), s"not a method-symbol: $msym")
      val resT: BType =
        if (msym.isClassConstructor || msym.isConstructor) UNIT
        else toTypeKind(msym.info.resultType)
      MethodBType(msym.info.firstParamTypes map toTypeKind, resT)
    }

    /**
     * The jvm descriptor of a type.
     */
    final def typeDescriptor(t: Type): String = { toTypeKind(t).descriptor   }

    /**
     * The jvm descriptor for a symbol.
     */
    final def symDescriptor(sym: Symbol): String = getClassBType(sym).descriptor

    final def toTypeKind(tp: Type): BType = typeToTypeKind(tp)(BCodeHelpers.this)(this)

  } // end of trait BCInnerClassGen

  trait BCAnnotGen extends BCInnerClassGen {

    /*
     * must-single-thread
     */
    def emitAnnotations(cw: asm.ClassVisitor, annotations: List[Annotation]): Unit =
      for(annot <- annotations; if shouldEmitAnnotation(annot)) {
        val typ = annot.tree.tpe
        val assocs = assocsFromApply(annot.tree)
        val av = cw.visitAnnotation(typeDescriptor(typ), isRuntimeVisible(annot))
        emitAssocs(av, assocs, BCodeHelpers.this)(this)
      }

    /*
     * must-single-thread
     */
    def emitAnnotations(mw: asm.MethodVisitor, annotations: List[Annotation]): Unit =
      for(annot <- annotations; if shouldEmitAnnotation(annot)) {
        val typ = annot.tree.tpe
        val assocs = assocsFromApply(annot.tree)
        val av = mw.visitAnnotation(typeDescriptor(typ), isRuntimeVisible(annot))
        emitAssocs(av, assocs, BCodeHelpers.this)(this)
      }

    /*
     * must-single-thread
     */
    def emitAnnotations(fw: asm.FieldVisitor, annotations: List[Annotation]): Unit =
      for(annot <- annotations; if shouldEmitAnnotation(annot)) {
        val typ = annot.tree.tpe
        val assocs = assocsFromApply(annot.tree)
        val av = fw.visitAnnotation(typeDescriptor(typ), isRuntimeVisible(annot))
        emitAssocs(av, assocs, BCodeHelpers.this)(this)
      }

    /*
     * must-single-thread
     */
    def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) =
      for param <- params do
        var access = asm.Opcodes.ACC_FINAL
        if param.is(Artifact) then access |= asm.Opcodes.ACC_SYNTHETIC
        jmethod.visitParameter(param.name.mangledString, access)

    /*
     * must-single-thread
     */
    def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[Annotation]]): Unit =
      val annotationss = pannotss map (_ filter shouldEmitAnnotation)
      if (annotationss forall (_.isEmpty)) return
      for ((annots, idx) <- annotationss.zipWithIndex;
        annot <- annots) {
        val typ = annot.tree.tpe
        val assocs = assocsFromApply(annot.tree)
        val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, typeDescriptor(typ.asInstanceOf[Type]), isRuntimeVisible(annot))
        emitAssocs(pannVisitor, assocs, BCodeHelpers.this)(this)
      }


    private def shouldEmitAnnotation(annot: Annotation): Boolean = {
      annot.symbol.is(JavaDefined) &&
        retentionPolicyOf(annot) != AnnotationRetentionSourceAttr
    }

    private def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, Object)], bcodeStore: BCodeHelpers)
        (innerClasesStore: bcodeStore.BCInnerClassGen) = {
      for ((name, value) <- assocs)
        emitArgument(av, name.mangledString, value.asInstanceOf[Tree], bcodeStore)(innerClasesStore)
      av.visitEnd()
    }

    private def emitArgument(av:   AnnotationVisitor,
                           name: String,
                           arg:  Tree, bcodeStore: BCodeHelpers)(innerClasesStore: bcodeStore.BCInnerClassGen): Unit = {
      val narg = normalizeArgument(arg)
      // Transformation phases are not run on annotation trees, so we need to run
      // `constToLiteral` at this point.
      val t = atPhase(erasurePhase)(constToLiteral(narg))
      t match {
        case Literal(const @ Constant(_)) =>
          const.tag match {
            case BooleanTag | ByteTag | ShortTag | CharTag | IntTag | LongTag | FloatTag | DoubleTag => av.visit(name, const.value)
            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, typeToTypeKind(TypeErasure.erasure(const.typeValue))(bcodeStore)(innerClasesStore).toASMType)
          }
        case Ident(nme.WILDCARD) =>
          // An underscore argument indicates that we want to use the default value for this parameter, so do not emit anything
        case t: tpd.RefTree if t.symbol.owner.linkedClass.isAllOf(JavaEnumTrait) =>
          val edesc = innerClasesStore.typeDescriptor(t.tpe) // the class descriptor of the enumeration class.
          val evalue = t.symbol.javaSimpleName // value the actual enumeration value.
          av.visitEnum(name, edesc, evalue)
        case t: SeqLiteral =>
          val arrAnnotV: AnnotationVisitor = av.visitArray(name)
          for (arg <- t.elems) { emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore) }
          arrAnnotV.visitEnd()

        case Apply(fun, args) if fun.symbol == defn.ArrayClass.primaryConstructor ||
          toDenot(fun.symbol).owner == defn.ArrayClass.linkedClass && fun.symbol.name == nme.apply =>
          val arrAnnotV: AnnotationVisitor = av.visitArray(name)

          var actualArgs = if (fun.tpe.isImplicitMethod) {
            // generic array method, need to get implicit argument out of the way
            fun.asInstanceOf[Apply].args
          } else args

          val flatArgs = actualArgs.flatMap { arg =>
            normalizeArgument(arg) match {
              case t: tpd.SeqLiteral => t.elems
              case e => List(e)
            }
          }
          for(arg <- flatArgs) {
            emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore)
          }
          arrAnnotV.visitEnd()
  /*
        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, BCodeAsmCommon.strEncode(sb))
          } else {
            val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
            for(arg <- BCodeAsmCommon.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 t @ Apply(constr, args) if t.tpe.classSymbol.is(JavaAnnotation) =>
          val typ = t.tpe.classSymbol.denot.info
          val assocs = assocsFromApply(t)
          val desc = innerClasesStore.typeDescriptor(typ) // the class descriptor of the nested annotation class
          val nestedVisitor = av.visitAnnotation(name, desc)
          emitAssocs(nestedVisitor, assocs, bcodeStore)(innerClasesStore)

        case t =>
          report.error(em"Annotation argument is not a constant", t.sourcePos)
      }
    }

    private def normalizeArgument(arg: Tree): Tree = arg match {
      case Trees.NamedArg(_, arg1) => normalizeArgument(arg1)
      case Trees.Typed(arg1, _) => normalizeArgument(arg1)
      case _ => arg
    }

    private def isRuntimeVisible(annot: Annotation): Boolean =
      if (toDenot(annot.tree.tpe.typeSymbol).hasAnnotation(AnnotationRetentionAttr))
        retentionPolicyOf(annot) == AnnotationRetentionRuntimeAttr
      else {
        // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
        // annotation is emitted with visibility `RUNTIME`
        // dotty bug: #389
        true
      }

    private def retentionPolicyOf(annot: Annotation): Symbol =
      annot.tree.tpe.typeSymbol.getAnnotation(AnnotationRetentionAttr).
        flatMap(_.argument(0).map(_.tpe.termSymbol)).getOrElse(AnnotationRetentionClassAttr)

    private def assocsFromApply(tree: Tree): List[(Name, Tree)] = {
      tree match {
        case Block(_, expr) => assocsFromApply(expr)
        case Apply(fun, args) =>
          fun.tpe.widen match {
            case MethodType(names) =>
              (names zip args).filter {
                case (_, t: tpd.Ident) if (t.tpe.normalizedPrefix eq NoPrefix) => false
                case _ => true
              }
          }
      }
    }
  } // end of trait BCAnnotGen

  trait BCJGenSigGen {
    import int.given

    def getCurrentCUnit(): CompilationUnit

    /**
     * Generates the generic signature for `sym` before erasure.
     *
     * @param sym   The symbol for which to generate a signature.
     * @param owner The owner of `sym`.
     * @return The generic signature of `sym` before erasure, as specified in the Java Virtual
     *         Machine Specification, §4.3.4, or `null` if `sym` doesn't need a generic signature.
     * @see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4
     */
    def getGenericSignature(sym: Symbol, owner: Symbol): String = {
      atPhase(erasurePhase) {
        val memberTpe =
          if (sym.is(Method)) sym.denot.info
          else owner.denot.thisType.memberInfo(sym)
        getGenericSignatureHelper(sym, owner, memberTpe).orNull
      }
    }

  } // end of trait BCJGenSigGen

  trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen {

    /* Add a forwarder for method m. Used only from addForwarders().
     *
     * must-single-thread
     */
    private def addForwarder(jclass: asm.ClassVisitor, module: Symbol, m: Symbol, isSynthetic: Boolean): Unit = {
      val moduleName     = internalName(module)
      val methodInfo     = module.thisType.memberInfo(m)
      val paramJavaTypes: List[BType] = methodInfo.firstParamTypes map toTypeKind
      // 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.
      val flags = GenBCodeOps.PublicStatic | (
        if (m.is(JavaVarargs)) asm.Opcodes.ACC_VARARGS else 0
      ) | (
        if (isSynthetic) asm.Opcodes.ACC_SYNTHETIC else 0
      )

      // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
      val jgensig = getStaticForwarderGenericSignature(m, module)
      val (throws, others) = m.annotations.partition(_.symbol eq defn.ThrowsAnnot)
      val thrownExceptions: List[String] = getExceptions(throws)

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

      emitAnnotations(mirrorMethod, others)
      val params: List[Symbol] = Nil // backend uses this to emit annotations on parameter lists of forwarders
      // to static methods of companion class
      // Old assumption: in Dotty this link does not exists: there is no way to get from method type
      // to inner symbols of DefDef
      // TODO: now we have paramSymss and could use it here.
      emitParamAnnotations(mirrorMethod, params.map(_.annotations))

      mirrorMethod.visitCode()

      mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, str.MODULE_INSTANCE_FIELD, symDescriptor(module))

      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, asmMethodType(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(jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol): Unit = {
      assert(moduleClass.is(ModuleClass), moduleClass)
      report.debuglog(s"Dumping mirror class for object: $moduleClass")

      val linkedClass  = moduleClass.companionClass
      lazy val conflictingNames: Set[Name] = {
        (linkedClass.info.allMembers.collect { case d if d.name.isTermName => d.name }).toSet
      }
      report.debuglog(s"Potentially conflicting names for forwarders: $conflictingNames")

      for (m0 <- sortedMembersBasedOnFlags(moduleClass.info, required = Method, excluded = ExcludedForwarder)) {
        val m = if (m0.is(Bridge)) m0.nextOverriddenSymbol else m0
        if (m == NoSymbol)
          report.log(s"$m0 is a bridge method that overrides nothing, something went wrong in a previous phase.")
        else if (m.isType || m.is(Deferred) || (m.owner eq defn.ObjectClass) || m.isConstructor || m.name.is(ExpandedName))
          report.debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'")
        else if (conflictingNames(m.name))
          report.log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}")
        else if (m.accessBoundary(defn.RootClass) ne defn.RootClass)
          report.log(s"No forwarder for non-public member $m")
        else {
          report.log(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'")
          // It would be simpler to not generate forwarders for these methods,
          // but that wouldn't be binary-compatible with Scala 3.0.0, so instead
          // we generate ACC_SYNTHETIC forwarders so Java compilers ignore them.
          val isSynthetic =
            m0.name.is(NameKinds.SyntheticSetterName) ||
            // Only hide bridges generated at Erasure, mixin forwarders are also
            // marked as bridge but shouldn't be hidden since they don't have a
            // non-bridge overload.
            m0.is(Bridge) && m0.initial.validFor.firstPhaseId == erasurePhase.next.id
          addForwarder(jclass, moduleClass, m, isSynthetic)
        }
      }
    }

    /** The members of this type that have all of `required` flags but none of `excluded` flags set.
     *  The members are sorted by name and signature to guarantee a stable ordering.
     */
    private def sortedMembersBasedOnFlags(tp: Type, required: Flag, excluded: FlagSet): List[Symbol] = {
      // The output of `memberNames` is a Set, sort it to guarantee a stable ordering.
      val names = tp.memberNames(takeAllFilter).toSeq.sorted
      val buffer = mutable.ListBuffer[Symbol]()
      names.foreach { name =>
        buffer ++= tp.memberBasedOnFlags(name, required, excluded)
          .alternatives.sortBy(_.signature)(Signature.lexicographicOrdering).map(_.symbol)
      }
      buffer.toList
    }

    /*
     * 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[Annotation]): List[String] = {
      for (case ThrownException(exc) <- excs.distinct)
      yield internalName(TypeErasure.erasure(exc).classSymbol)
    }
  } // 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): Unit = {
      // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)`
      jclass.visitField(
        GenBCodeOps.PrivateStaticFinal,
        "serialVersionUID",
        "J",
        null, // no java-generic-signature
        java.lang.Long.valueOf(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 {

    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.
     *
     *  must-single-thread
     */
    def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = {
      assert(moduleClass.is(ModuleClass))
      assert(moduleClass.companionClass == NoSymbol, moduleClass)
      this.cunit = cunit
      val bType      = mirrorClassBTypeFromSymbol(moduleClass)
      val moduleName = internalName(moduleClass) // + "$"
      val mirrorName = bType.internalName

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

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

      val ssa = None // getAnnotPickle(mirrorName, if (moduleClass.is(Module)) moduleClass.companionClass else moduleClass.companionModule)
      mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
      emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa)

      addForwarders(mirrorClass, mirrorName, moduleClass)
      mirrorClass.visitEnd()

      moduleClass.name // this side-effect is necessary, really.

      mirrorClass
    }

  } // end of class JMirrorBuilder

  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 = "CREATOR".toTermName

    lazy val AndroidParcelableInterface : Symbol = NoSymbol // getClassIfDefined("android.os.Parcelable")
    lazy val AndroidCreatorClass        : Symbol = NoSymbol // getClassIfDefined("android.os.Parcelable$Creator")

    /*
     * must-single-thread
     */
    def isAndroidParcelableClass(sym: Symbol) =
      (AndroidParcelableInterface != NoSymbol) &&
      (sym.info.parents.map(_.typeSymbol) contains AndroidParcelableInterface)

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

      cnode.visitField(
        GenBCodeOps.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,
        str.MODULE_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

  /**
   * This method returns the BType for a type reference, for example a parameter type.
   *
   * If the result is a ClassBType for a nested class, it is added to the innerClassBufferASM.
   *
   * If `t` references a class, toTypeKind ensures that the class is not an implementation class.
   * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation
   * classes.
   */
  private def typeToTypeKind(tp: Type)(ct: BCodeHelpers)(storage: ct.BCInnerClassGen): ct.bTypes.BType = {
    import ct.bTypes.*
    val defn = ctx.definitions
    import coreBTypes.*
    import Types.*
    /**
      * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int.
      * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType.
      */
    def primitiveOrClassToBType(sym: Symbol): BType = {
      assert(sym.isClass, sym)
      assert(sym != defn.ArrayClass || compilingArray, sym)
      primitiveTypeMap.getOrElse(sym, storage.getClassBType(sym)).asInstanceOf[BType]
    }

    /**
      * When compiling Array.scala, the type parameter T is not erased and shows up in method
      * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference.
      */
    def nonClassTypeRefToBType(sym: Symbol): ClassBType = {
      assert(sym.isType && compilingArray, sym)
      ObjectRef.asInstanceOf[ct.bTypes.ClassBType]
    }

    tp.widenDealias match {
      case JavaArrayType(el) =>ArrayBType(typeToTypeKind(el)(ct)(storage)) // Array type such as Array[Int] (kept by erasure)
      case t: TypeRef =>
        t.info match {

          case _ =>
            if (!t.symbol.isClass) nonClassTypeRefToBType(t.symbol)  // See comment on nonClassTypeRefToBType
            else primitiveOrClassToBType(t.symbol) // Common reference to a type such as scala.Int or java.lang.String
        }
      case Types.ClassInfo(_, sym, _, _, _)           => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info)

      /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for
        * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning.
        * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala.
        */
      case a @ AnnotatedType(t, _) =>
        report.debuglog(s"typeKind of annotated type $a")
        typeToTypeKind(t)(ct)(storage)

      /* The cases below should probably never occur. They are kept for now to avoid introducing
        * new compiler crashes, but we added a warning. The compiler / library bootstrap and the
        * test suite don't produce any warning.
        */

      case tp =>
        report.warning(
          s"an unexpected type representation reached the compiler backend while compiling ${ctx.compilationUnit}: $tp. " +
            "If possible, please file a bug on https://github.com/lampepfl/dotty/issues")

        tp match {
          case tp: ThisType if tp.cls == defn.ArrayClass => ObjectRef.asInstanceOf[ct.bTypes.ClassBType] // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
          case tp: ThisType                         => storage.getClassBType(tp.cls)
          // case t: SingletonType                   => primitiveOrClassToBType(t.classSymbol)
          case t: SingletonType                     => typeToTypeKind(t.underlying)(ct)(storage)
          case t: RefinedType                       => typeToTypeKind(t.parent)(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b))
        }
    }
  }

  private def getGenericSignatureHelper(sym: Symbol, owner: Symbol, memberTpe: Type)(using Context): Option[String] = {
    if (needsGenericSignature(sym)) {
      val erasedTypeSym = TypeErasure.fullErasure(sym.denot.info).typeSymbol
      if (erasedTypeSym.isPrimitiveValueClass) {
        // Suppress signatures for symbols whose types erase in the end to primitive
        // value types. This is needed to fix #7416.
        None
      } else {
        val jsOpt = GenericSignatures.javaSig(sym, memberTpe)
        if (ctx.settings.XverifySignatures.value) {
          jsOpt.foreach(verifySignature(sym, _))
        }

        jsOpt
      }
    } else {
      None
    }
  }

  private def verifySignature(sym: Symbol, sig: String)(using Context): Unit = {
    import scala.tools.asm.util.CheckClassAdapter
    def wrap(body: => Unit): Unit = {
      try body
      catch {
        case ex: Throwable =>
          report.error(
            em"""|compiler bug: created invalid generic signature for $sym in ${sym.denot.owner.showFullName}
                 |signature: $sig
                 |if this is reproducible, please report bug at https://github.com/lampepfl/dotty/issues
               """, sym.sourcePos)
          throw  ex
      }
    }

    wrap {
      if (sym.is(Method)) {
        CheckClassAdapter.checkMethodSignature(sig)
      }
      else if (sym.isTerm) {
        CheckClassAdapter.checkFieldSignature(sig)
      }
      else {
        CheckClassAdapter.checkClassSignature(sig)
      }
    }
  }

  // @M don't generate java generics sigs for (members of) implementation
  // classes, as they are monomorphic (TODO: ok?)
  private final def needsGenericSignature(sym: Symbol): Boolean = !(
    // 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.
      ctx.base.settings.YnoGenericSig.value
    || sym.is(Artifact)
    || sym.isAllOf(LiftedMethod)
    || sym.is(Bridge)
  )

  private def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): 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 = atPhase(erasurePhase) { moduleClass.denot.thisType.memberInfo(sym) }
    val erasedMemberType = ElimErasedValueType.elimEVT(TypeErasure.transformInfo(sym, memberTpe))
    if (erasedMemberType =:= sym.denot.info)
      getGenericSignatureHelper(sym, moduleClass, memberTpe).orNull
    else null
  }

  def abort(msg: String): Nothing = {
    report.error(msg)
    throw new RuntimeException(msg)
  }

  private def compilingArray(using Context) =
    ctx.compilationUnit.source.file.name == "Array.scala"
}

object BCodeHelpers {

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

  /** An attachment on Apply nodes indicating that it should be compiled with
   *  `invokespecial` instead of `invokevirtual`. This is used for static
   *  forwarders.
   *  See BCodeSkelBuilder.makeStaticForwarder for more details.
   */
  val UseInvokeSpecial = new dotc.util.Property.Key[Unit]

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy