Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ class ClosureOptimizer(optimizerUtils: OptimizerUtils,
receiverProducers.size == 1 && receiverProducers.head == indy
}

def isSpecializedVersion(specName: String, nonSpecName: String) = specName.startsWith(nonSpecName) && specializationSuffix.pattern.matcher(specName.substring(nonSpecName.length)).matches
def isSpecializedVersion(specName: String, nonSpecName: String) =
specName.startsWith(nonSpecName)
&& ((specName == "applyVoid" && nonSpecName == "apply" && invocation.owner.startsWith("scala/Function"))
|| specializationSuffix.pattern.matcher(specName.substring(nonSpecName.length)).matches)

def sameOrSpecializedType(specTp: Type, nonSpecTp: Type) = {
specTp == nonSpecTp || {
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/backend/jvm/opt/CopyProp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import scala.tools.asm

class CopyProp(optimizerUtils: OptimizerUtils, callGraph: CallGraph, inliner: Inliner, ts: OptimizerKnownBTypes, settings: OptimizerSettings) {

private val modulesAllowSkipInitialization =
if settings.optAllowSkipCoreModuleInit then optimizerUtils.modulesAllowSkipInitialization else Set.empty
private val modulesAllowSkipInitialization: InternalName => Boolean =
if settings.optAllowSkipCoreModuleInit then n => n.startsWith("scala/") else Set.empty

/**
* For every `xLOAD n`, find all local variable slots that are aliases of `n` using an
Expand Down
31 changes: 11 additions & 20 deletions compiler/src/dotty/tools/backend/jvm/opt/OptimizerUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import scala.tools.asm.{Handle, Opcodes, Type}
* This component hosts tools and utilities used in the optimizer that require access to an `OptimizerKnownBTypes` instance.
*/
class OptimizerUtils(val ts: OptimizerKnownBTypes) {
private val ScalaPackage = "scala/package$"

private val indyLambdaImplMethods: ConcurrentHashMap[InternalName, mutable.Map[MethodNode, mutable.Map[InvokeDynamicInsnNode, asm.Handle]]] =
new ConcurrentHashMap
Expand Down Expand Up @@ -135,9 +136,9 @@ class OptimizerUtils(val ts: OptimizerKnownBTypes) {

def runtimeRefClassBoxedType(refClass: InternalName): asm.Type = asm.Type.getArgumentTypes(ts.srRefCreateMethods(refClass).methodType.descriptor)(0)

def isSideEffectFreeConstructorCall(insn: MethodInsnNode): Boolean = {
insn.name == BCodeUtils.INSTANCE_CONSTRUCTOR_NAME && sideEffectFreeConstructors((insn.owner, insn.desc))
}
def isSideEffectFreeConstructorCall(insn: MethodInsnNode): Boolean =
insn.owner == ScalaPackage ||
(insn.name == BCodeUtils.INSTANCE_CONSTRUCTOR_NAME && sideEffectFreeConstructors((insn.owner, insn.desc)))

def isNewForSideEffectFreeConstructor(insn: AbstractInsnNode): Boolean = {
insn.getOpcode == Opcodes.NEW && {
Expand All @@ -156,7 +157,7 @@ class OptimizerUtils(val ts: OptimizerKnownBTypes) {
// methods that are known to return a non-null result
def isNonNullMethodInvocation(mi: MethodInsnNode): Boolean = {
isJavaBox(mi) || isScalaBox(mi) || isPredefAutoBox(mi) || isRefCreate(mi) || isRefZero(mi) || AnalysisUtils.isClassTagApply(mi) ||
isTupleApply(mi)
isTupleApply(mi) || mi.owner == ScalaPackage
}

// unused objects created by these constructors are eliminated by pushPop
Expand All @@ -170,19 +171,6 @@ class OptimizerUtils(val ts: OptimizerKnownBTypes) {
(ts.StringRef.internalName, MethodBType(List(ts.StringRef), UNIT).descriptor),
(ts.StringRef.internalName, MethodBType(List(ArrayBType(CHAR)), UNIT).descriptor))

lazy val modulesAllowSkipInitialization: Set[InternalName] =
Set(
"scala/Predef$",
"scala/runtime/ScalaRunTime$",
"scala/runtime/Scala3RunTime$",
"scala/reflect/ClassTag$",
"scala/reflect/ManifestFactory$",
"scala/Array$",
"scala/collection/ArrayOps$",
"scala/collection/StringOps$",
"scala/TupleXXL$"
) ++ (1 to Definitions.MaxTupleArity).map(n => s"scala/Tuple$n$$") ++ AnalysisUtils.primitiveTypes.keysIterator

private lazy val classesOfSideEffectFreeConstructors: Set[String] =
sideEffectFreeConstructors.map(_._1)

Expand Down Expand Up @@ -229,8 +217,10 @@ class OptimizerUtils(val ts: OptimizerKnownBTypes) {
val t = i.getType
if (t == AbstractInsnNode.METHOD_INSN) {
val mi = i.asInstanceOf[MethodInsnNode]
// invokespecial has, well, special semantics that depend on the class it's being invoked in, see, e.g., https://stackoverflow.com/a/8950564
if (!allowPrivateCalls && i.getOpcode == Opcodes.INVOKESPECIAL && mi.name != BCodeUtils.INSTANCE_CONSTRUCTOR_NAME) {
if (mi.owner == ScalaPackage) {
// Ignore calls that, e.g., load the Range module -- that's still part of a forwarder
} else if (!allowPrivateCalls && i.getOpcode == Opcodes.INVOKESPECIAL && mi.name != BCodeUtils.INSTANCE_CONSTRUCTOR_NAME) {
// invokespecial has, well, special semantics that depend on the class it's being invoked in, see, e.g., https://stackoverflow.com/a/8950564
numCallsOrNew = 2 // stop here: don't inline forwarders with a private or super call
} else {
if (isScalaBox(mi) || isScalaUnbox(mi) || isPredefAutoBox(mi) || isPredefAutoUnbox(mi) || isJavaBox(mi) || isJavaUnbox(mi))
Expand All @@ -242,8 +232,9 @@ class OptimizerUtils(val ts: OptimizerKnownBTypes) {
}
} else if (nonForwarderInstructionTypes(t)) {
if (i.getOpcode == Opcodes.GETSTATIC) {
if (!allowPrivateCalls && owner == i.asInstanceOf[FieldInsnNode].owner)
if (!allowPrivateCalls && owner == i.asInstanceOf[FieldInsnNode].owner) {
numCallsOrNew = 2 // stop here: not forwarder or trivial
}
} else {
numCallsOrNew = 2 // stop here: not forwarder or trivial
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class OptimizationBytecodeTests extends DottyBytecodeTest {
val instructions = instructionsFromMethod(meth)
for instr <- instructions do instr match {
case AsmConverters.Invoke(_, owner, name, _, _) => assert(allowedCalls(owner, name), s"Found invoke to $owner.$name in:\n${instructions.mkString("\n")}")
case AsmConverters.InvokeDynamic(_, name, _, _, _) => assert(false, s"Found dynamic invoke to $name in:\n${instructions.mkString("\n")}")
case AsmConverters.InvokeDynamic(_, _, _, bsm, _) => assert(allowedCalls(bsm.owner, bsm.name), s"Found dynamic invoke to ${bsm.owner}.${bsm.name} in:\n${instructions.mkString("\n")}")
case _ => ()
}
}
Expand Down Expand Up @@ -720,6 +720,25 @@ class OptimizationBytecodeTests extends DottyBytecodeTest {
)


@Test def inlineIntTo =
assertCalls(Calls.noneToClasses("Test"),
"extension (n: Int) { def myTo(m: Int): Range.Inclusive = Range.inclusive(n, m) }; (0 myTo 49)",
returnType = "Range.Inclusive"
)

// requires understanding `applyVoid` as a specialization on Function*
@Test def inlineRangeForeachArrayForeach =
assertCalls(Calls.noneToClasses("java/lang/invoke/LambdaMetafactory"),
"""
|final class C() { @noinline def test(): Boolean = false }
|val r = Range.inclusive(0, 49)
|val a = Array.fill(50)(new C())
|var x = 1
|r.foreach { n => a.foreach { c => if (c.test()) { x += 1 } } }
|x""".stripMargin
)


@Test def inlineSingleAbstractMethod =
assertEquivalence(
"if i eq null then throw null; 42",
Expand Down
Loading