dotty.tools.backend.jvm.BCodeSkelBuilder.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala3-compiler_3 Show documentation
Show all versions of scala3-compiler_3 Show documentation
scala3-compiler-bootstrapped
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