Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a8f9eb2
Move BackendUtils to opt
SolalPirelli May 27, 2026
8c26d3d
Mechanical rename
SolalPirelli May 27, 2026
1c75313
Add non-optimizer eagerly-computed KnownBTypes
SolalPirelli May 27, 2026
6f25337
Eagerly load WellKnownBTypes
SolalPirelli May 27, 2026
c463faa
Deduplicate known BTypes through inheritance
SolalPirelli May 28, 2026
f1f6a64
Only load optimizer known types if opt is enabled. Also move some stu…
SolalPirelli May 28, 2026
f4c355a
Move optimizer known btypes to their own file and rename
SolalPirelli May 28, 2026
b5d6035
Remove one unneeded Lazy
SolalPirelli May 28, 2026
c42e4d9
Remove one unneeded Lazy, 2
SolalPirelli May 28, 2026
947adf6
Remove one unneeded Lazy, 3
SolalPirelli May 28, 2026
4f4f1d1
Remove one unneeded Lazy, 4
SolalPirelli May 28, 2026
02e5dec
Remove one unneeded Lazy, 5
SolalPirelli May 28, 2026
9860dc2
Delete PPFA.Lazy and clean up
SolalPirelli May 28, 2026
c0ddd34
Avoid analysis dependence on opt by splitting some of OptimizerUtils …
SolalPirelli May 28, 2026
459548b
PR feedback
SolalPirelli Jun 4, 2026
147a135
Add test confirming optimizer warnings are emitted
SolalPirelli Jun 4, 2026
6d635bc
Single entry point for the optimizer
SolalPirelli Jun 4, 2026
7232ebb
Refactor optimizer warning reporting, remove PPFA
SolalPirelli Jun 4, 2026
41c0606
Remove duplicated val
SolalPirelli Jun 5, 2026
87e17e8
Remove custom FileWriters context/reporter abstractions and simplify
SolalPirelli Jun 5, 2026
a757c94
Optimizer warnings have no need for a Context
SolalPirelli Jun 5, 2026
c35b4f0
Clean up indy lambda tracking public interface
SolalPirelli Jun 5, 2026
6562db8
Extract indy tracking to its own class
SolalPirelli Jun 5, 2026
6e34bde
Extract ClassBType caching into its own class
SolalPirelli Jun 5, 2026
33cb415
No more circular dependency
SolalPirelli Jun 5, 2026
b24b9a0
Simplify GenBCode init
SolalPirelli Jun 5, 2026
b253f78
Reuse class builder instance
SolalPirelli Jun 5, 2026
6bd63f2
Slim down CodeGen
SolalPirelli Jun 5, 2026
07c2557
Slim down CodeGen further
SolalPirelli Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import dotty.tools.dotc.util.SrcPos
* @version 1.0
*
*/
trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder {
trait BCodeBodyBuilder(val primitives: ScalaPrimitives, val bTypes: KnownBTypes) extends BCodeSkelBuilder {
/*
* Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions.
*/
Expand Down Expand Up @@ -155,7 +155,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder
// binary operation
case rarg :: Nil =>
val isShift = isShiftOp(code)
resKind = tpeTK(larg).maxType(if (isShift) INT else tpeTK(rarg), bTypes)
resKind = tpeTK(larg).maxType(if (isShift) INT else tpeTK(rarg), bTypes.ObjectRef)

if (isShift || isBitwiseOp(code)) {
assert(resKind.isIntegralType || (resKind == BOOL),
Expand Down Expand Up @@ -522,7 +522,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder
val thrownType = expectedType
// `throw null` is valid although scala.Null (as defined in src/library-aux) isn't a subtype of Throwable.
// Similarly for scala.Nothing (again, as defined in src/library-aux).
assert(thrownType == bTypes.srNullRef || thrownType == bTypes.srNothingRef || thrownType.asClassBType.isSubtypeOf(bTypes.jlThrowableRef))
assert(thrownType.isNull || thrownType.isNothing || thrownType.asClassBType.isSubtypeOf(bTypes.jlThrowableRef))
emit(asm.Opcodes.ATHROW)
end genAdaptAndSendToDest

Expand Down Expand Up @@ -1160,7 +1160,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder
* `varsInScope`, ending at the current program point.
*/
def emitLocalVarScopes(): Unit =
if (BackendUtils.emitVars) {
if (emitVars) {
val end = currProgramPoint()
for ((sym, start) <- varsInScope.nn.reverse) {
emitLocalVarScope(sym, start, end)
Expand All @@ -1169,7 +1169,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder
end emitLocalVarScopes

def adapt(from: BType, to: BType)(using Context): Unit = {
if (from == bTypes.srNothingRef) {
if (from.isNothing) {
/* There are two possibilities for from being Nothing: emitting a "throw e" expressions and
* loading a (phantom) value of type Nothing.
*
Expand Down Expand Up @@ -1216,7 +1216,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder
*/
if (lastInsn.getOpcode != asm.Opcodes.ATHROW)
emit(asm.Opcodes.ATHROW)
} else if (from == bTypes.srNullRef) {
} else if (from.isNull) {
/* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL.
* This is required to pass the verifier: in Scala's type system, Null conforms to any
* reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which
Expand Down Expand Up @@ -1483,7 +1483,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder
val ownerBType = bTypeLoader.bTypeFromType(method.owner.info)
if (isInterface && !method.is(JavaDefined)) {
val staticDesc = MethodBType(ownerBType :: bmType.argumentTypes, bmType.returnType).descriptor
val staticName = BackendUtils.traitSuperAccessorName(method)
val staticName = SymbolUtils.traitSuperAccessorName(method)
bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos)
} else {
if (isInterface) {
Expand Down Expand Up @@ -1607,7 +1607,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder
genLoad(nonNullSide, bTypes.ObjectRef)
genCZJUMP(success, failure, op, bTypes.ObjectRef, targetIfNoJump)
} else {
val tk = tpeTK(l).maxType(tpeTK(r), bTypes)
val tk = tpeTK(l).maxType(tpeTK(r), bTypes.ObjectRef)
genLoad(l, tk)
stack.push(tk)
genLoad(r, tk)
Expand Down
21 changes: 17 additions & 4 deletions compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package dotty.tools
package backend
package jvm

import dotty.tools.backend.jvm.SymbolUtils.symExtensions

import scala.tools.asm
import scala.tools.asm.{AnnotationVisitor, ClassWriter, Opcodes}
import scala.collection.mutable
Expand All @@ -27,6 +29,7 @@ import dotty.tools.dotc.transform.Mixin
import dotty.tools.dotc.report
import tpd.*
import dotty.tools.dotc.config.ScalaSettingsProperties
import dotty.tools.dotc.util.NoSourcePosition

/*
* Encapsulates functionality to convert Scala AST Trees into ASM ClassNodes.
Expand All @@ -35,7 +38,7 @@ import dotty.tools.dotc.config.ScalaSettingsProperties
* @version 1.0
*
*/
trait BCodeHelpers(val bTypeLoader: BTypeLoader, val bTypes: WellKnownBTypes) extends BCodeIdiomatic {
trait BCodeHelpers(val bTypeLoader: BTypeLoader) extends BCodeIdiomatic {

// OK to cache because it won't change across Contexts
private var cachedClassfileVersion: Int | Null = null
Expand Down Expand Up @@ -547,6 +550,16 @@ trait BCodeHelpers(val bTypeLoader: BTypeLoader, val bTypes: WellKnownBTypes) ex
/* builder of mirror classes */
class JMirrorBuilder extends JCommonBuilder {

def genMirrorClassIfNeeded(moduleClass: Symbol)(using Context): asm.tree.ClassNode | Null = {
if !moduleClass.isTopLevelModuleClass then
null
else if moduleClass.companionClass == NoSymbol then
genMirrorClass(moduleClass)
else
report.log(s"No mirror class for module with linked class: ${moduleClass.fullName}", NoSourcePosition)
null
}

/* 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
Expand All @@ -555,7 +568,7 @@ trait BCodeHelpers(val bTypeLoader: BTypeLoader, val bTypes: WellKnownBTypes) ex
*
* must-single-thread
*/
def genMirrorClass(moduleClass: Symbol)(using Context): asm.tree.ClassNode = {
private def genMirrorClass(moduleClass: Symbol)(using Context): asm.tree.ClassNode = {
assert(moduleClass.is(ModuleClass))
assert(moduleClass.companionClass == NoSymbol, moduleClass)
val bType = bTypeLoader.mirrorClassBTypeFromSymbol(moduleClass)
Expand All @@ -570,11 +583,11 @@ trait BCodeHelpers(val bTypeLoader: BTypeLoader, val bTypes: WellKnownBTypes) ex
bType.info.flags,
mirrorName,
null /* no java-generic-signature */,
bTypes.ObjectRef.internalName,
ClassBType.javaLangObjectInternalName,
EMPTY_STRING_ARRAY
)

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

Expand Down
22 changes: 16 additions & 6 deletions compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package dotty.tools
package backend
package jvm

import dotty.tools.backend.jvm.opt.CallGraph
import scala.tools.asm
import scala.annotation.switch
import scala.tools.asm.tree.MethodInsnNode
import dotty.tools.dotc.ast.Positioned
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.util.NoSourcePosition

/*
* A high-level facade to the ASM API for bytecode generation.
Expand All @@ -15,9 +17,17 @@ import dotty.tools.dotc.core.Contexts.Context
* @version 1.0
*
*/
trait BCodeIdiomatic {

def recordCallsitePosition(m: MethodInsnNode, pos: Positioned | Null)(using Context): Unit
trait BCodeIdiomatic(callGraph: Option[CallGraph]) {
private val 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

private def recordCallsitePosition(m: MethodInsnNode, pos: Positioned | Null)(using Context): Unit =
callGraph.foreach(_.recordCallsitePosition(m, pos match {
case p: Positioned => p.sourcePos
case null => NoSourcePosition
}))

val CLASS_CONSTRUCTOR_NAME = "<clinit>"
val INSTANCE_CONSTRUCTOR_NAME = "<init>"
Expand Down Expand Up @@ -172,12 +182,12 @@ trait BCodeIdiomatic {
recipe: String,
argTypes: Seq[asm.Type],
constants: Seq[String],
ts: WellKnownBTypes
bTypes: KnownBTypes
): Unit = {
jmethod.visitInvokeDynamicInsn(
"makeConcatWithConstants",
asm.Type.getMethodDescriptor(ts.StringRef.toASMType, argTypes*),
ts.jliStringConcatFactoryMakeConcatWithConstantsHandle,
asm.Type.getMethodDescriptor(bTypes.StringRef.toASMType, argTypes*),
bTypes.jliStringConcatFactoryMakeConcatWithConstantsHandle,
(recipe +: constants)*
)
}
Expand Down
18 changes: 9 additions & 9 deletions compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ trait BCodeSkelBuilder extends BCodeHelpers {

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

def genPlainClass(cd0: TypeDef)(using Context): ClassNode1 = (cd0: @unchecked) match {
def genPlainClass(cd0: TypeDef, topLevel: Boolean = false)(using Context): ClassNode1 = (cd0: @unchecked) match {
case TypeDef(_, impl: Template) =>
assert(cnode == null, "GenBCode detected nested methods.")
assert(topLevel || cnode == null, "GenBCode detected nested methods.")

claszSymbol = cd0.symbol
isCZStaticModule = claszSymbol.isStaticModuleClass
Expand Down Expand Up @@ -298,7 +298,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
private def initJClass(jclass: asm.ClassVisitor)(using Context): Unit = {

val ps = claszSymbol.info.parents
val superClass: String = if ps.isEmpty then bTypes.ObjectRef.internalName
val superClass: String = if ps.isEmpty then ClassBType.javaLangObjectInternalName
else bTypeLoader.classBTypeFromSymbol(ps.head.typeSymbol).internalName

// We need to emit not only directly implemented interfaces, but also any indirectly implemented ones that are the target of super calls.
Expand Down Expand Up @@ -343,7 +343,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
thisName, thisSignature,
superClass, interfaceNames.toArray)

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

Expand Down Expand Up @@ -619,7 +619,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
case _ => a
}

if (BackendUtils.emitLines && tree.span.exists && !tree.hasAttachment(SyntheticUnit)) {
if (emitLines && tree.span.exists && !tree.hasAttachment(SyntheticUnit)) {
val nr =
val sourcePos = tree.sourcePos
(
Expand Down Expand Up @@ -759,7 +759,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
*/
private def makeStatifiedDefDef(dd: DefDef)(using Context): DefDef =
val origSym = dd.symbol.asTerm
val newSym = BackendUtils.makeStatifiedDefSymbol(origSym, origSym.name)
val newSym = SymbolUtils.makeStatifiedDefSymbol(origSym, origSym.name)
tpd.DefDef(newSym, { paramRefss =>
val selfParamRef :: regularParamRefs = paramRefss.head: @unchecked
val enclosingClass = origSym.owner.asClass
Expand Down Expand Up @@ -799,8 +799,8 @@ trait BCodeSkelBuilder extends BCodeHelpers {
// but remember to change it there if you make changes here
// !!!
val origSym = dd.symbol.asTerm
val name = BackendUtils.traitSuperAccessorName(origSym).toTermName
val sym = BackendUtils.makeStatifiedDefSymbol(origSym, name)
val name = SymbolUtils.traitSuperAccessorName(origSym).toTermName
val sym = SymbolUtils.makeStatifiedDefSymbol(origSym, name)
tpd.DefDef(sym, { paramss =>
val params = paramss.head
tpd.Apply(params.head.select(origSym), params.tail)
Expand Down Expand Up @@ -898,7 +898,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
else
genLoadTo(trimmedRhs, returnType, LoadDestination.Return)

if (BackendUtils.emitVars) {
if (emitVars) {
// add entries to LocalVariableTable JVM attribute
val onePastLastProgramPoint = currProgramPoint()
val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0)
Expand Down
68 changes: 24 additions & 44 deletions compiler/src/dotty/tools/backend/jvm/BTypeLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,13 @@ import scala.annotation.tailrec
import scala.tools.asm
import scala.tools.asm.tree.ClassNode

final class BTypeLoader(primitives: ScalaPrimitives, inlineInfoLoader: () => Option[InlineInfoLoader]) {
// Concurrent map because stack map frames are computed when in the class writer, which
// might run on multiple classes concurrently.
private val classBTypeCache = new ConcurrentHashMap[InternalName, ClassBType]
final class BTypeLoader(primitives: ScalaPrimitives, cache: ClassBType.Cache, inlineInfoLoader: Option[InlineInfoLoader]) {

/** Maps special symbols, including primitive types, to their corresponding BType. */
// It's OK to cache this because all Contexts that go through here share their defns.
// No locking, it's OK if this map gets initialized twice (though a little inefficient).
private var specialBTypes: Map[Symbol, BType] | Null = null


/** See doc of ClassBType.apply. This is where to use that method from. */
def classBType[T](internalName: InternalName)(init: ClassBType => Either[T, ClassInfo]): Either[T, ClassBType] =
ClassBType(internalName, classBTypeCache)(init)

/** See doc of ClassBType.apply. This is where to use that method from. Version that cannot fail. */
def classBType(internalName: InternalName)(init: ClassBType => ClassInfo): ClassBType =
ClassBType(internalName, classBTypeCache)(ct => Right(init(ct))).fold(_ => assert(false), identity)

/** Obtain a previously constructed ClassBType for a given internal name, or None if no such ClassBType was constructed. */
def previouslyConstructedClassBType(internalName: InternalName): Option[ClassBType] =
Option(classBTypeCache.get(internalName))

def bTypeFromSymbol(sym: Symbol)(using Context): BType = {
if specialBTypes eq null then
specialBTypes = Map(
Expand Down Expand Up @@ -76,16 +60,16 @@ final class BTypeLoader(primitives: ScalaPrimitives, inlineInfoLoader: () => Opt
assert(
classSym != defn.NothingClass && classSym != defn.NullClass,
s"Cannot create ClassBType for special class symbol ${classSym.showFullName}")
assert(classSym != defn.ArrayClass || BackendUtils.compilingArray, classSym)
assert(!classSym.isPrimitiveValueClass || BackendUtils.compilingPrimitive, s"Found $classSym while compiling ${ctx.compilationUnit.source.file.name}")
assert(classSym != defn.ArrayClass || compilingArray, classSym)
assert(!classSym.isPrimitiveValueClass || compilingPrimitive, s"Found $classSym while compiling ${ctx.compilationUnit.source.file.name}")

classBType(classSym.javaBinaryName)(ct => createClassInfo(ct, classSym.asClass))
cache(classSym.javaBinaryName)(ct => createClassInfo(ct, classSym.asClass))
}

def mirrorClassBTypeFromSymbol(moduleClassSym: Symbol)(using Context): ClassBType = {
assert(moduleClassSym.isTopLevelModuleClass, s"not a top-level module class: $moduleClassSym")
val internalName = moduleClassSym.javaBinaryName.stripSuffix(StdNames.str.MODULE_SUFFIX)
classBType(internalName)(_ =>
cache(internalName)(_ =>
ClassInfo(
superClass = Some(classBTypeFromSymbol(defn.ObjectClass)),
interfaces = Nil,
Expand Down Expand Up @@ -126,27 +110,6 @@ final class BTypeLoader(primitives: ScalaPrimitives, inlineInfoLoader: () => Opt
throw new AssertionError(s"an unexpected type representation reached the compiler backend while compiling ${ctx.compilationUnit}: $tp.")
}

/**
* Visit the class node and collect all referenced nested classes.
*/
def collectNestedClasses(classNode: ClassNode): (Iterable[ClassBType], Iterable[ClassBType]) = {
val c = new NestedClassesCollector[ClassBType](nestedOnly = true) {
def declaredNestedClasses(internalName: InternalName): List[ClassBType] =
previouslyConstructedClassBType(internalName).get.info.nestedClasses

def getClassIfNested(internalName: InternalName): Option[ClassBType] = {
val c = previouslyConstructedClassBType(internalName).get
Option.when(c.isNestedClass)(c)
}

def raiseError(msg: String, sig: String, e: Option[Throwable]): Unit = {
// don't crash on invalid generic signatures
}
}
c.visit(classNode)
(c.declaredInnerClasses, c.referredInnerClasses)
}

private def createClassInfo(classBType: ClassBType, classSym: Symbol)(using Context): ClassInfo = {
val superClassSym: Symbol = {
val t = classSym.asClass.superClass
Expand Down Expand Up @@ -227,7 +190,7 @@ final class BTypeLoader(primitives: ScalaPrimitives, inlineInfoLoader: () => Opt

val nestedInfo = buildNestedInfo(classSym)

val inlineInfo = inlineInfoLoader() match {
val inlineInfo = inlineInfoLoader match {
case Some(loader) => buildInlineInfo(loader, classSym.asClass, classBType.internalName)
case None => InlineInfo.empty
}
Expand Down Expand Up @@ -354,7 +317,7 @@ final class BTypeLoader(primitives: ScalaPrimitives, inlineInfoLoader: () => Opt
val staticForwarders = if classSym.is(Trait) then
// !!! This logic duplicates PlainSkelBuilder::makeStaticForwarder, copy changes there !!!
classSym.info.decls.filter(s => s.isTerm && !s.isPrivate && !s.isStaticMember && s.name != nme.TRAIT_CONSTRUCTOR).map(s => {
BackendUtils.makeStatifiedDefSymbol(s.asTerm, BackendUtils.traitSuperAccessorName(s).toTermName)
SymbolUtils.makeStatifiedDefSymbol(s.asTerm, SymbolUtils.traitSuperAccessorName(s).toTermName)
})
else Nil
classMethods ++ staticForwarders
Expand Down Expand Up @@ -395,4 +358,21 @@ final class BTypeLoader(primitives: ScalaPrimitives, inlineInfoLoader: () => Opt
@tailrec
private def isOriginallyStaticOwner(sym: Symbol)(using Context): Boolean =
sym.is(PackageClass) || sym.is(ModuleClass) && isOriginallyStaticOwner(sym.originalOwner.originalLexicallyEnclosingClass)

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

private val primitiveCompilationUnits = Set(
"Unit.scala",
"Boolean.scala",
"Char.scala",
"Byte.scala",
"Short.scala",
"Int.scala",
"Float.scala",
"Long.scala",
"Double.scala"
)
private def compilingPrimitive(using Context) =
primitiveCompilationUnits(ctx.compilationUnit.source.file.name)
}
Loading
Loading