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

dotty.tools.backend.jvm.BCodeSkelBuilder.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.tailrec
import scala.collection.{immutable, mutable}
import scala.tools.asm
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.TreeTypeMap
import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.ast.Trees.SyntheticUnit
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.StdNames.*
import dotty.tools.dotc.core.NameKinds.*
import dotty.tools.dotc.core.Names.TermName
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.util.Spans.*
import dotty.tools.dotc.report


/*
 *
 *  @author  Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
 *  @version 1.0
 *
 */
trait BCodeSkelBuilder extends BCodeHelpers {
  import int.{_, given}
  import DottyBackendInterface.{symExtensions, _}
  import tpd.*
  import bTypes.*
  import coreBTypes.*
  import bCodeAsmCommon.*

  lazy val NativeAttr: Symbol = requiredClass[scala.native]

  final class BTypesStack:
    // Anecdotally, growing past 16 to 32 is common; growing past 32 is rare
    private var stack = new Array[BType](32)
    private var size = 0

    def isEmpty: Boolean = size == 0

    def push(btype: BType): Unit =
      if size == stack.length then
        stack = java.util.Arrays.copyOf(stack, stack.length * 2)
      stack(size) = btype
      size += 1

    def pop(): Unit = pop(1)

    def pop(count: Int): Unit =
      assert(size >= count)
      size -= count

    def height: Int = heightBetween(0, size)

    private def heightBetween(start: Int, end: Int): Int =
      var result = 0
      var i = start
      while i != end do
        result += stack(i).size
        i += 1
      result

    def recordSize(): BTypesStack.Size = BTypesStack.intToSize(size)

    def restoreSize(targetSize: BTypesStack.Size): Unit =
      val targetSize1 = BTypesStack.sizeToInt(targetSize)
      assert(size >= targetSize1)
      size = targetSize1

    def heightDiffWrt(targetSize: BTypesStack.Size): Int =
      val targetSize1 = BTypesStack.sizeToInt(targetSize)
      assert(size >= targetSize1)
      heightBetween(targetSize1, size)

    def clear(): Unit =
      size = 0

    def acquireFullStack(): IArray[BType] =
      val res = IArray.unsafeFromArray(stack.slice(0, size))
      size = 0
      res

    def restoreFullStack(fullStack: IArray[BType]): Unit =
      assert(size == 0 && stack.length >= fullStack.length)
      fullStack.copyToArray(stack)
      size = fullStack.length
  end BTypesStack

  object BTypesStack:
    opaque type Size = Int

    private def intToSize(size: Int): Size = size
    private def sizeToInt(size: Size): Int = size
  end BTypesStack

  /** The destination of a value generated by `genLoadTo`. */
  enum LoadDestination:
    /** The value is put on the stack, and control flows through to the next opcode. */
    case FallThrough
    /** The value is put on the stack, and control flow is transferred to the given `label`. */
    case Jump(label: asm.Label, targetStackSize: BTypesStack.Size)
    /** The value is RETURN'ed from the enclosing method. */
    case Return
    /** The value is ATHROW'n. */
    case Throw
  end LoadDestination

  /*
   * There's a dedicated PlainClassBuilder for each CompilationUnit,
   * which simplifies the initialization of per-class data structures in `genPlainClass()` which in turn delegates to `initJClass()`
   *
   * The entry-point to emitting bytecode instructions is `genDefDef()` where the per-method data structures are initialized,
   * including `resetMethodBookkeeping()` and `initJMethod()`.
   * Once that's been done, and assuming the method being visited isn't abstract, `emitNormalMethodBody()` populates
   * the ASM MethodNode instance with ASM AbstractInsnNodes.
   *
   * Given that CleanUp delivers trees that produce values on the stack,
   * the entry-point to all-things instruction-emit is `genLoad()`.
   * There, an operation taking N arguments results in recursively emitting instructions to lead each of them,
   * followed by emitting instructions to process those arguments (to be found at run-time on the operand-stack).
   *
   * In a few cases the above recipe deserves more details, as provided in the documentation for:
   *   - `genLoadTry()`
   *   - `genSynchronized()
   *   - `jumpDest` , `cleanups` , `labelDefsAtOrUnder`
   */
  abstract class PlainSkelBuilder(cunit: CompilationUnit)
    extends BCClassGen
    with    BCAnnotGen
    with    BCInnerClassGen
    with    JAndroidBuilder
    with    BCForwardersGen
    with    BCPickles
    with    BCJGenSigGen {

    // Strangely I can't find this in the asm code 255, but reserving 1 for "this"
    inline val MaximumJvmParameters = 254

    // current class
    var cnode: ClassNode1          = null
    var thisName: String           = null // the internal name of the class being emitted

    var claszSymbol: Symbol        = null
    var isCZParcelable             = false
    var isCZStaticModule           = false

    /* ---------------- idiomatic way to ask questions to typer ---------------- */

    def paramTKs(app: Apply, take: Int = -1): List[BType] = app match {
      case Apply(fun, _) =>
      val funSym = fun.symbol
      (funSym.info.firstParamTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM)
    }

    def symInfoTK(sym: Symbol): BType = {
      toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM)
    }

    def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) }

    override def getCurrentCUnit(): CompilationUnit = { cunit }

    /* ---------------- helper utils for generating classes and fields ---------------- */

    def genPlainClass(cd0: TypeDef) = cd0 match {
      case TypeDef(_, impl: Template) =>
      assert(cnode == null, "GenBCode detected nested methods.")

      claszSymbol       = cd0.symbol
      isCZParcelable    = isAndroidParcelableClass(claszSymbol)
      isCZStaticModule  = claszSymbol.isStaticModuleClass
      thisName          = internalName(claszSymbol)

      cnode = new ClassNode1()

      initJClass(cnode)

      val cd = if (isCZStaticModule) {
        // Move statements from the primary constructor following the superclass constructor call to
        // a newly synthesised tree representing the "", which also assigns the MODULE$ field.
        // Because the assigments to both the module instance fields, and the fields of the module itself
        // are in the , these fields can be static + final.

        // Should we do this transformation earlier, say in Constructors? Or would that just cause
        // pain for scala-{js, native}?
        //
        // @sjrd (https://github.com/lampepfl/dotty/pull/9181#discussion_r457458205):
        // moving that before the back-end would make things significantly more complicated for
        // Scala.js and Native. Both have a first-class concept of ModuleClass, and encode the
        // singleton pattern of MODULE$ in a completely different way. In the Scala.js IR, there
        // even isn't anything that corresponds to MODULE$ per se.
        //
        // So if you move this before the back-end, then Scala.js and Scala Native will have to
        // reverse all the effects of this transformation, which would be counter-productive.


        // TODO: remove `!f.name.is(LazyBitMapName)` once we change lazy val encoding
        //       https://github.com/lampepfl/dotty/issues/7140
        //
        // Lazy val encoding assumes bitmap fields are non-static
        //
        // See `tests/run/given-var.scala`
        //

        // !!! Part of this logic is duplicated in JSCodeGen.genCompilationUnit
        claszSymbol.info.decls.foreach { f =>
          if f.isField && !f.name.is(LazyBitMapName) && !f.name.is(LazyLocalName) then
            f.setFlag(JavaStatic)
        }

        val (clinits, body) = impl.body.partition(stat => stat.isInstanceOf[DefDef] && stat.symbol.isStaticConstructor)

        val (uptoSuperStats, remainingConstrStats) = splitAtSuper(impl.constr.rhs.asInstanceOf[Block].stats)
        val clInitSymbol: TermSymbol =
          if (clinits.nonEmpty) clinits.head.symbol.asTerm
          else newSymbol(
            claszSymbol,
            nme.STATIC_CONSTRUCTOR,
            JavaStatic | Method,
            MethodType(Nil)(_ => Nil, _ => defn.UnitType),
            privateWithin = NoSymbol,
            coord = claszSymbol.coord
          )

        val moduleField = newSymbol(
            claszSymbol,
            str.MODULE_INSTANCE_FIELD.toTermName,
            JavaStatic | Final,
            claszSymbol.typeRef,
            privateWithin = NoSymbol,
            coord = claszSymbol.coord
          ).entered

        val thisMap = new TreeMap {
          override def transform(tree: Tree)(using Context) = {
            val tp = tree.tpe.substThis(claszSymbol.asClass, claszSymbol.sourceModule.termRef)
            tree.withType(tp) match {
              case tree: This if tree.symbol == claszSymbol =>
                ref(claszSymbol.sourceModule)
              case tree =>
                super.transform(tree)
            }
          }
        }

        def rewire(stat: Tree) = thisMap.transform(stat).changeOwner(claszSymbol.primaryConstructor, clInitSymbol)

        val callConstructor = New(claszSymbol.typeRef).select(claszSymbol.primaryConstructor).appliedToTermArgs(Nil)
        val assignModuleField = Assign(ref(moduleField), callConstructor)
        val remainingConstrStatsSubst = remainingConstrStats.map(rewire)
        val clinit = clinits match {
          case (ddef: DefDef) :: _ =>
            cpy.DefDef(ddef)(rhs = Block(ddef.rhs :: assignModuleField :: remainingConstrStatsSubst, unitLiteral))
          case _ =>
            DefDef(clInitSymbol, Block(assignModuleField :: remainingConstrStatsSubst, unitLiteral))
        }

        val constr2 = {
          val rhs = Block(uptoSuperStats, impl.constr.rhs.asInstanceOf[Block].expr)
          cpy.DefDef(impl.constr)(rhs = rhs)
        }

        val impl2 = cpy.Template(impl)(constr = constr2, body = clinit :: body)
        cpy.TypeDef(cd0)(rhs = impl2)
      } else cd0

      val hasStaticCtor = isCZStaticModule || cd.symbol.info.decls.exists(_.isStaticConstructor)
      if (!hasStaticCtor && isCZParcelable) fabricateStaticInitAndroid()

      val optSerial: Option[Long] =
        claszSymbol.getAnnotation(defn.SerialVersionUIDAnnot).flatMap { annot =>
          if (claszSymbol.is(Trait)) {
            report.warning("@SerialVersionUID does nothing on a trait", annot.tree.sourcePos)
            None
          } else {
            val vuid = annot.argumentConstant(0).map(_.longValue)
            if (vuid.isEmpty)
              report.error("The argument passed to @SerialVersionUID must be a constant",
                annot.argument(0).getOrElse(annot.tree).sourcePos)
            vuid
          }
        }
      if (optSerial.isDefined) { addSerialVUID(optSerial.get, cnode)}

      addClassFields()
      gen(cd.rhs)

      if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
        AsmUtils.traceClass(cnode)

      cnode.innerClasses
      assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().")

    } // end of method genPlainClass()

    /*
     * must-single-thread
     */
    private def initJClass(jclass: asm.ClassVisitor): Unit = {

      val ps = claszSymbol.info.parents
      val superClass: String = if (ps.isEmpty) ObjectRef.internalName else internalName(ps.head.typeSymbol)
      val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces.map(_.internalName)
      /* To avoid deadlocks when combining objects, lambdas and multi-threading,
       * lambdas in objects are compiled to instance methods of the module class
       * instead of static methods (see tests/run/deadlock.scala and
       * https://github.com/scala/scala-dev/issues/195 for details).
       * This has worked well for us so far but this is problematic for
       * serialization: serializing a lambda requires serializing all the values
       * it captures, if this lambda is in an object, this means serializing the
       * enclosing object, which fails if the object does not extend
       * Serializable.
       * Because serializing objects is basically free since #5775, it seems like
       * the simplest solution is to simply make all objects Serializable, this
       * certainly seems preferable to deadlocks.
       * This cannot be done earlier because Scala.js would not like it (#9596).
       */
      val interfaceNames =
        if (claszSymbol.is(ModuleClass) && !interfaceNames0.contains("java/io/Serializable"))
          interfaceNames0 :+ "java/io/Serializable"
        else
          interfaceNames0

      val flags = javaFlags(claszSymbol)

      val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner)
      cnode.visit(backendUtils.classfileVersion, flags,
                  thisName, thisSignature,
                  superClass, interfaceNames.toArray)

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

      enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match {
        case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) =>
          cnode.visitOuterClass(className, methodName, methodDescriptor)
        case _ => ()
      }

      val ssa = None // TODO: inlined form `getAnnotPickle(thisName, claszSymbol)`. Should something be done on Dotty?
      cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
      emitAnnotations(cnode, claszSymbol.annotations ++ ssa)

      if (!isCZStaticModule && !isCZParcelable) {
        val skipStaticForwarders = (claszSymbol.is(Module) || ctx.settings.XnoForwarders.value)
        if (!skipStaticForwarders) {
          val lmoc = claszSymbol.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 =  (lmoc.is(Module)) && lmoc.isStatic
            if (isCandidateForForwarders) {
              report.log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'")
              addForwarders(cnode, thisName, lmoc.moduleClass)
            }
          }
        }

      }

      // the invoker is responsible for adding a class-static constructor.

    } // end of method initJClass

    /*
     * must-single-thread
     */
    private def fabricateStaticInitAndroid(): Unit = {

      val clinit: asm.MethodVisitor = cnode.visitMethod(
        GenBCodeOps.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
        CLASS_CONSTRUCTOR_NAME,
        "()V",
        null, // no java-generic-signature
        null  // no throwable exceptions
      )
      clinit.visitCode()

      legacyAddCreatorCode(clinit, cnode, thisName)

      clinit.visitInsn(asm.Opcodes.RETURN)
      clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments
      clinit.visitEnd()
    }

    def addClassFields(): Unit = {
      /*  Non-method term members are fields, except for module members. Module
       *  members can only happen on .NET (no flatten) for inner traits. There,
       *  a module symbol is generated (transformInfo in mixin) which is used
       *  as owner for the members of the implementation class (so that the
       *  backend emits them as static).
       *  No code is needed for this module symbol.
       */
      for (f <- claszSymbol.info.decls.filter(p => p.isTerm && !p.is(Method))) {
        val javagensig = getGenericSignature(f, claszSymbol)
        val flags = javaFieldFlags(f)

        assert(!f.isStaticMember || !claszSymbol.isInterface || !f.is(Mutable),
          s"interface $claszSymbol cannot have non-final static field $f")

        val jfield = new asm.tree.FieldNode(
          flags,
          f.javaSimpleName,
          symInfoTK(f).descriptor,
          javagensig,
          null // no initial value
        )
        cnode.fields.add(jfield)
        emitAnnotations(jfield, f.annotations)
      }

    } // end of method addClassFields()

    // current method
    var mnode: MethodNode1         = null
    var jMethodName: String        = null
    var isMethSymStaticCtor        = false
    var returnType: BType          = null
    var methSymbol: Symbol         = null
    // used by genLoadTry() and genSynchronized()
    var earlyReturnVar: Symbol     = null
    var shouldEmitCleanup          = false
    // stack tracking
    val stack                      = new BTypesStack
    // line numbers
    var lastEmittedLineNr          = -1

    object bc extends JCodeMethodN {
      override def jmethod = PlainSkelBuilder.this.mnode
    }

    /* ---------------- Part 1 of program points, ie Labels in the ASM world ---------------- */

    /*
     *  A jump is represented as a Return node whose `from` symbol denotes a Labeled's Bind node, the target of the jump.
     *  The `jumpDest` map is used to find the `LoadDestination` at the end of the `Labeled` block, as well as the
     *  corresponding expected type. The `LoadDestination` can never be `FallThrough` here.
     */
    var jumpDest: immutable.Map[ /* Labeled */ Symbol, (BType, LoadDestination) ] = null
    def registerJumpDest(labelSym: Symbol, expectedType: BType, dest: LoadDestination): Unit = {
      assert(labelSym.is(Label), s"trying to register a jump-dest for a non-label symbol, at: ${labelSym.span}")
      assert(dest != LoadDestination.FallThrough, s"trying to register a FallThrough dest for label, at: ${labelSym.span}")
      assert(!jumpDest.contains(labelSym), s"trying to register a second jump-dest for label, at: ${labelSym.span}")
      jumpDest += (labelSym -> (expectedType, dest))
    }
    def findJumpDest(labelSym: Symbol): (BType, LoadDestination) = {
      assert(labelSym.is(Label), s"trying to map a non-label symbol to an asm.Label, at: ${labelSym.span}")
      jumpDest.getOrElse(labelSym, {
        abort(s"unknown label symbol, for label at: ${labelSym.span}")
      })
    }

    /*
     *  A program point may be lexically nested (at some depth)
     *    (a) in the try-clause of a try-with-finally expression
     *    (b) in a synchronized block.
     *  Each of the constructs above establishes a "cleanup block" to execute upon
     *  both normal-exit, early-return, and abrupt-termination of the instructions it encloses.
     *
     *  The `cleanups` LIFO queue represents the nesting of active (for the current program point)
     *  pending cleanups. For each such cleanup an asm.Label indicates the start of its cleanup-block.
     *  At any given time during traversal of the method body,
     *  the head of `cleanups` denotes the cleanup-block for the closest enclosing try-with-finally or synchronized-expression.
     *
     *  `cleanups` is used:
     *
     *    (1) upon visiting a Return statement.
     *        In case of pending cleanups, we can't just emit a RETURN instruction, but must instead:
     *          - store the result (if any) in `earlyReturnVar`, and
     *          - jump to the next pending cleanup.
     *        See `genReturn()`
     *
     *    (2) upon emitting a try-with-finally or a synchronized-expr,
     *        In these cases, the targets of the above jumps are emitted,
     *        provided an early exit was actually encountered somewhere in the protected clauses.
     *        See `genLoadTry()` and `genSynchronized()`
     *
     *  The code thus emitted for jumps and targets covers the early-return case.
     *  The case of abrupt (ie exceptional) termination is covered by exception handlers
     *  emitted for that purpose as described in `genLoadTry()` and `genSynchronized()`.
     */
    var cleanups: List[asm.Label] = Nil
    def registerCleanup(finCleanup: asm.Label): Unit = {
      if (finCleanup != null) { cleanups = finCleanup :: cleanups }
    }
    def unregisterCleanup(finCleanup: asm.Label): Unit = {
      if (finCleanup != null) {
        assert(cleanups.head eq finCleanup,
               s"Bad nesting of cleanup operations: $cleanups trying to unregister: $finCleanup")
        cleanups = cleanups.tail
      }
    }

    /* ---------------- local variables and params ---------------- */

    case class Local(tk: BType, name: String, idx: Int, isSynth: Boolean)

    /*
     * Bookkeeping for method-local vars and method-params.
     *
     * TODO: use fewer slots. local variable slots are never re-used in separate blocks.
     * In the following example, x and y could use the same slot.
     *   def foo() = {
     *     { val x = 1 }
     *     { val y = "a" }
     *   }
     */
    object locals {

      private val slots = mutable.AnyRefMap.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth))

      private var nxtIdx = -1 // next available index for local-var

      def reset(isStaticMethod: Boolean): Unit = {
        slots.clear()
        nxtIdx = if (isStaticMethod) 0 else 1
      }

      def contains(locSym: Symbol): Boolean = { slots.contains(locSym) }

      def apply(locSym: Symbol): Local = { slots.apply(locSym) }

      /* Make a fresh local variable, ensuring a unique name.
       * The invoker must make sure inner classes are tracked for the sym's tpe.
       */
      def makeLocal(tk: BType, name: String, tpe: Type, pos: Span): Symbol = {

        val locSym = newSymbol(methSymbol, name.toTermName, Synthetic, tpe, NoSymbol, pos)
        makeLocal(locSym, tk)
        locSym
      }

      def makeLocal(locSym: Symbol): Local = {
        makeLocal(locSym, symInfoTK(locSym))
      }

      def getOrMakeLocal(locSym: Symbol): Local = {
        // `getOrElse` below has the same effect as `getOrElseUpdate` because `makeLocal()` adds an entry to the `locals` map.
        slots.getOrElse(locSym, makeLocal(locSym))
      }

      def reuseLocal(sym: Symbol, loc: Local): Unit =
        val existing = slots.put(sym, loc)
        if (existing.isDefined)
          report.error("attempt to create duplicate local var.", ctx.source.atSpan(sym.span))

      def reuseThisSlot(sym: Symbol): Unit =
        reuseLocal(sym, Local(symInfoTK(sym), sym.javaSimpleName, 0, sym.is(Synthetic)))

      private def makeLocal(sym: Symbol, tk: BType): Local = {
        assert(nxtIdx != -1, "not a valid start index")
        val loc = Local(tk, sym.javaSimpleName, nxtIdx, sym.is(Synthetic))
        val existing = slots.put(sym, loc)
        if (existing.isDefined)
          report.error("attempt to create duplicate local var.", ctx.source.atSpan(sym.span))
        assert(tk.size > 0, "makeLocal called for a symbol whose type is Unit.")
        nxtIdx += tk.size
        loc
      }

      def makeTempLocal(tk: BType): Local =
        assert(nxtIdx != -1, "not a valid start index")
        assert(tk.size > 0, "makeLocal called for a symbol whose type is Unit.")
        val loc = Local(tk, "temp", nxtIdx, isSynth = true)
        nxtIdx += tk.size
        loc

      // not to be confused with `fieldStore` and `fieldLoad` which also take a symbol but a field-symbol.
      def store(locSym: Symbol): Unit = {
        val Local(tk, _, idx, _) = slots(locSym)
        bc.store(idx, tk)
      }

      def load(locSym: Symbol): Unit = {
        val Local(tk, _, idx, _) = slots(locSym)
        bc.load(idx, tk)
      }

    }

    /* ---------------- Part 2 of program points, ie Labels in the ASM world ---------------- */

    // bookkeeping the scopes of non-synthetic local vars, to emit debug info (`emitVars`).
    var varsInScope: List[(Symbol, asm.Label)] = null // (local-var-sym -> start-of-scope)

    // helpers around program-points.
    def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast
    def currProgramPoint(): asm.Label = {
      lastInsn match {
        case labnode: asm.tree.LabelNode => labnode.getLabel
        case _ =>
          val pp = new asm.Label
          mnode visitLabel pp
          pp
      }
    }
    def markProgramPoint(lbl: asm.Label): Unit = {
      val skip = (lbl == null) || isAtProgramPoint(lbl)
      if (!skip) { mnode visitLabel lbl }
    }
    def isAtProgramPoint(lbl: asm.Label): Boolean = {
      def getNonLineNumberNode(a: asm.tree.AbstractInsnNode): asm.tree.AbstractInsnNode  = a match {
        case a: asm.tree.LineNumberNode => getNonLineNumberNode(a.getPrevious) // line numbers aren't part of code itself
        case _ => a
      }
      (getNonLineNumberNode(lastInsn) match {
        case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl);
        case _ => false } )
    }
    def lineNumber(tree: Tree): Unit = {
      @tailrec
      def getNonLabelNode(a: asm.tree.AbstractInsnNode): asm.tree.AbstractInsnNode = a match {
        case a: asm.tree.LabelNode => getNonLabelNode(a.getPrevious)
        case _                     => a
      }

      if (emitLines && tree.span.exists && !tree.hasAttachment(SyntheticUnit)) {
        val nr = ctx.source.offsetToLine(tree.span.point) + 1
        if (nr != lastEmittedLineNr) {
          lastEmittedLineNr = nr
          getNonLabelNode(lastInsn) match {
            case lnn: asm.tree.LineNumberNode =>
              // overwrite previous landmark as no instructions have been emitted for it
              lnn.line = nr
            case _ =>
              mnode.visitLineNumber(nr, currProgramPoint())
          }
        }
      }
    }

    // on entering a method
    def resetMethodBookkeeping(dd: DefDef) = {
      val rhs = dd.rhs
      locals.reset(isStaticMethod = methSymbol.isStaticMember)
      jumpDest = immutable.Map.empty

      // check previous invocation of genDefDef exited as many varsInScope as it entered.
      assert(varsInScope == null, "Unbalanced entering/exiting of GenBCode's genBlock().")
      // check previous invocation of genDefDef unregistered as many cleanups as it registered.
      assert(cleanups == Nil, "Previous invocation of genDefDef didn't unregister as many cleanups as it registered.")
      earlyReturnVar      = null
      shouldEmitCleanup   = false

      stack.clear()

      lastEmittedLineNr = -1
    }

    /* ---------------- top-down traversal invoking ASM Tree API along the way ---------------- */

    def gen(tree: Tree): Unit = {
      tree match {
        case tpd.EmptyTree => ()

        case ValDef(name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`

        case dd: DefDef =>
          /* First generate a static forwarder if this is a non-private trait
           * trait method. This is required for super calls to this method, which
           * go through the static forwarder in order to work around limitations
           * of the JVM.
           *
           * For the $init$ method, we must not leave it as a default method, but
           * instead we must put the whole body in the static method. If we leave
           * it as a default method, Java classes cannot extend Scala classes that
           * extend several Scala traits, since they then inherit unrelated default
           * $init$ methods. See #8599. scalac does the same thing.
           *
           * In theory, this would go in a separate MiniPhase, but it would have to
           * sit in a MegaPhase of its own between GenSJSIR and GenBCode, so the cost
           * is not worth it. We directly do it in this back-end instead, which also
           * kind of makes sense because it is JVM-specific.
           */
          val sym = dd.symbol
          val needsStaticImplMethod =
            claszSymbol.isInterface && !dd.rhs.isEmpty && !sym.isPrivate && !sym.isStaticMember
          if needsStaticImplMethod then
            if sym.name == nme.TRAIT_CONSTRUCTOR then
              genTraitConstructorDefDef(dd)
            else
              genStaticForwarderForDefDef(dd)
              genDefDef(dd)
          else
            genDefDef(dd)

        case tree: Template =>
          val body =
            if (tree.constr.rhs.isEmpty) tree.body
            else tree.constr :: tree.body
          body foreach gen

        case _ => abort(s"Illegal tree in gen: $tree")
      }
    }

    /*
     * must-single-thread
     */
    def initJMethod(flags: Int, params: List[Symbol]): Unit = {

      val jgensig = getGenericSignature(methSymbol, claszSymbol)
      val (excs, others) = methSymbol.annotations.partition(_.symbol eq defn.ThrowsAnnot)
      val thrownExceptions: List[String] = getExceptions(excs)

      val bytecodeName =
        if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME
        else jMethodName

      val mdesc = asmMethodType(methSymbol).descriptor
      mnode = cnode.visitMethod(
        flags,
        bytecodeName,
        mdesc,
        jgensig,
        mkArrayS(thrownExceptions)
      ).asInstanceOf[MethodNode1]

      // TODO param names: (m.params map (p => javaName(p.sym)))

      emitAnnotations(mnode, others)
      emitParamNames(mnode, params)
      emitParamAnnotations(mnode, params.map(_.annotations))

    } // end of method initJMethod

    private def genTraitConstructorDefDef(dd: DefDef): Unit =
      val statifiedDef = makeStatifiedDefDef(dd)
      genDefDef(statifiedDef)

    /** Creates a copy of the given DefDef that is static and where an explicit
     *  self parameter represents the original `this` value.
     *
     *  Example: from
     *  {{{
     *  trait Enclosing {
     *    def foo(x: Int): String = this.toString() + x
     *  }
     *  }}}
     *  the statified version of `foo` would be
     *  {{{
     *  static def foo($self: Enclosing, x: Int): String = $self.toString() + x
     *  }}}
     */
    private def makeStatifiedDefDef(dd: DefDef): DefDef =
      val origSym = dd.symbol.asTerm
      val newSym = makeStatifiedDefSymbol(origSym, origSym.name)
      tpd.DefDef(newSym, { paramRefss =>
        val selfParamRef :: regularParamRefs = paramRefss.head: @unchecked
        val enclosingClass = origSym.owner.asClass
        new TreeTypeMap(
          typeMap = _.substThis(enclosingClass, selfParamRef.symbol.termRef)
            .subst(dd.termParamss.head.map(_.symbol), regularParamRefs.map(_.symbol.termRef)),
          treeMap = {
            case tree: This if tree.symbol == enclosingClass => selfParamRef
            case tree => tree
          },
          oldOwners = origSym :: Nil,
          newOwners = newSym :: Nil
        ).transform(dd.rhs)
      })

    private def genStaticForwarderForDefDef(dd: DefDef): Unit =
      val forwarderDef = makeStaticForwarder(dd)
      genDefDef(forwarderDef)

    /* Generates a synthetic static forwarder for a trait method.
     * For a method such as
     *   def foo(...args: Ts): R
     * in trait X, we generate the following method:
     *   static def foo$($this: X, ...args: Ts): R =
     *     invokespecial $this.X::foo(...args)
     * We force an invokespecial with the attachment UseInvokeSpecial. It is
     * necessary to make sure that the call will not follow overrides of foo()
     * in subtraits and subclasses, since the whole point of this forward is to
     * encode super calls.
     */
    private def makeStaticForwarder(dd: DefDef): DefDef =
      val origSym = dd.symbol.asTerm
      val name = traitSuperAccessorName(origSym).toTermName
      val sym = makeStatifiedDefSymbol(origSym, name)
      tpd.DefDef(sym, { paramss =>
        val params = paramss.head
        tpd.Apply(params.head.select(origSym), params.tail)
          .withAttachment(BCodeHelpers.UseInvokeSpecial, ())
      })

    private def makeStatifiedDefSymbol(origSym: TermSymbol, name: TermName): TermSymbol =
      val info = origSym.info match
        case mt: MethodType =>
          MethodType(nme.SELF :: mt.paramNames, origSym.owner.typeRef :: mt.paramInfos, mt.resType)
      origSym.copy(
        name = name.toTermName,
        flags = Method | JavaStatic,
        info = info
      ).asTerm

    def genDefDef(dd: DefDef): Unit = {
      val rhs = dd.rhs
      val vparamss = dd.termParamss
      // the only method whose implementation is not emitted: getClass()
      if (dd.symbol eq defn.Any_getClass) { return }
      assert(mnode == null, "GenBCode detected nested method.")

      methSymbol  = dd.symbol
      jMethodName = methSymbol.javaSimpleName
      returnType  = asmMethodType(dd.symbol).returnType
      isMethSymStaticCtor = methSymbol.isStaticConstructor

      resetMethodBookkeeping(dd)

      // add method-local vars for params

      assert(vparamss.isEmpty || vparamss.tail.isEmpty, s"Malformed parameter list: $vparamss")
      val params = if (vparamss.isEmpty) Nil else vparamss.head
      for (p <- params) { locals.makeLocal(p.symbol) }
      // debug assert((params.map(p => locals(p.symbol).tk)) == asmMethodType(methSymbol).getArgumentTypes.toList, "debug")

      val paramsSize = params.map { param =>
        val tpeTym = param.symbol.info.typeSymbol
        if tpeTym == defn.LongClass || tpeTym == defn.DoubleClass then 2 else 1
      }.sum
      if (paramsSize > MaximumJvmParameters) {
        // SI-7324
        val info = if paramsSize == params.length then "" else " (Long and Double count as 2)" // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3
        report.error(em"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters$info.", ctx.source.atSpan(methSymbol.span))
        return
      }

      val isNative         = methSymbol.hasAnnotation(NativeAttr)
      val isAbstractMethod = (methSymbol.is(Deferred) || (methSymbol.owner.isInterface && ((methSymbol.is(Deferred))  || methSymbol.isClassConstructor)))
      val flags =
        import GenBCodeOps.addFlagIf
        javaFlags(methSymbol)
          .addFlagIf(isAbstractMethod, asm.Opcodes.ACC_ABSTRACT)
          .addFlagIf(false /*methSymbol.isStrictFP*/, asm.Opcodes.ACC_STRICT)
          .addFlagIf(isNative, asm.Opcodes.ACC_NATIVE) // native methods of objects are generated in mirror classes

      // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
      val paramSyms = params.map(_.symbol)
      initJMethod(flags, paramSyms)


      if (!isAbstractMethod && !isNative) {
        // #14773 Reuse locals slots for tailrec-generated mutable vars
        val trimmedRhs: Tree =
          @tailrec def loop(stats: List[Tree]): List[Tree] =
            stats match
              case (tree @ ValDef(TailLocalName(_, _), _, _)) :: rest if tree.symbol.isAllOf(Mutable | Synthetic) =>
                tree.rhs match
                  case This(_) =>
                    locals.reuseThisSlot(tree.symbol)
                    loop(rest)
                  case rhs: Ident if paramSyms.contains(rhs.symbol) =>
                    locals.reuseLocal(tree.symbol, locals(rhs.symbol))
                    loop(rest)
                  case _ =>
                    stats
              case _ =>
                stats
          end loop

          rhs match
            case Block(stats, expr) =>
              val trimmedStats = loop(stats)
              if trimmedStats eq stats then
                rhs
              else
                Block(trimmedStats, expr)
            case _ =>
              rhs
        end trimmedRhs

        def emitNormalMethodBody(): Unit = {
          val veryFirstProgramPoint = currProgramPoint()

          if trimmedRhs == tpd.EmptyTree then
            report.error(
              em"Concrete method has no definition: $dd${
                  if (ctx.settings.Ydebug.value) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"
                  else ""}",
              ctx.source.atSpan(NoSpan)
            )
          else
            genLoadTo(trimmedRhs, returnType, LoadDestination.Return)

          if (emitVars) {
            // add entries to LocalVariableTable JVM attribute
            val onePastLastProgramPoint = currProgramPoint()
            val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0)
            if (!hasStaticBitSet) {
              mnode.visitLocalVariable(
                "this",
                "L" + thisName + ";",
                null,
                veryFirstProgramPoint,
                onePastLastProgramPoint,
                0
              )
            }
            for (p <- params) { emitLocalVarScope(p.symbol, veryFirstProgramPoint, onePastLastProgramPoint, force = true) }
          }

          if (isMethSymStaticCtor) { appendToStaticCtor(dd) }
        } // end of emitNormalMethodBody()

        lineNumber(rhs)
        emitNormalMethodBody()

        // Note we don't invoke visitMax, thus there are no FrameNode among mnode.instructions.
        // The only non-instruction nodes to be found are LabelNode and LineNumberNode.
      }

      if (AsmUtils.traceMethodEnabled && mnode.name.contains(AsmUtils.traceMethodPattern))
        AsmUtils.traceMethod(mnode)

      mnode = null
    } // end of method genDefDef()

    /*
     *  must-single-thread
     *
     *  TODO document, explain interplay with `fabricateStaticInitAndroid()`
     */
    private def appendToStaticCtor(dd: DefDef): Unit = {

      def insertBefore(
            location: asm.tree.AbstractInsnNode,
            i0: asm.tree.AbstractInsnNode,
            i1: asm.tree.AbstractInsnNode): Unit = {
        if (i0 != null) {
          mnode.instructions.insertBefore(location, i0.clone(null))
          mnode.instructions.insertBefore(location, i1.clone(null))
        }
      }

      // collect all return instructions
      var rets: List[asm.tree.AbstractInsnNode] = Nil
      mnode foreachInsn { i => if (i.getOpcode() == asm.Opcodes.RETURN) { rets ::= i  } }
      if (rets.isEmpty) { return }

      var insnParcA: asm.tree.AbstractInsnNode = null
      var insnParcB: asm.tree.AbstractInsnNode = null
      // android creator code
      if (isCZParcelable) {
        // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator
        val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor
        cnode.visitField(
          asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL,
          "CREATOR",
          andrFieldDescr,
          null,
          null
        )
        // INVOKESTATIC CREATOR(): android.os.Parcelable$Creator; -- TODO where does this Android method come from?
        val callee = claszSymbol.companionModule.info.member(androidFieldName).symbol
        val jowner = internalName(callee.owner)
        val jname  = callee.javaSimpleName
        val jtype  = asmMethodType(callee).descriptor
        insnParcA  = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false)
        // PUTSTATIC `thisName`.CREATOR;
        insnParcB  = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr)
      }

      // insert a few instructions for initialization before each return instruction
      for(r <- rets) {
        insertBefore(r, insnParcA, insnParcB)
      }

    }

    def emitLocalVarScope(sym: Symbol, start: asm.Label, end: asm.Label, force: Boolean = false): Unit = {
      val Local(tk, name, idx, isSynth) = locals(sym)
      if (force || !isSynth) {
        mnode.visitLocalVariable(name, tk.descriptor, null, start, end, idx)
      }
    }

    def genLoadTo(tree: Tree, expectedType: BType, dest: LoadDestination): Unit

  } // end of class PlainSkelBuilder

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy