Skip to content

More JVM backend cleanup#25747

Open
SolalPirelli wants to merge 33 commits intoscala:mainfrom
dotty-staging:solal/jvm-cleanup-more
Open

More JVM backend cleanup#25747
SolalPirelli wants to merge 33 commits intoscala:mainfrom
dotty-staging:solal/jvm-cleanup-more

Conversation

@SolalPirelli
Copy link
Copy Markdown
Contributor

@SolalPirelli SolalPirelli commented Apr 9, 2026

Part of #25218

Review commit by commit, each of them individually passes tests (I think, I did some light rebasing).

  • Like the rest of the compiler, use Context in methods, do not capture it inside classes
    • Includes splitting CoreBTypes into BTypeLoader and WellKnownBTypes; the latter is what CoreBTypesFromSymbols used to be, minus loading stuff that went into the former. Only the latter captures a Context (and it's the only remaining class to do so).
    • This means we can stop modifying (!) the Context in GenBCode
    • We also no longer need to lock while calling genClassDef (it isn't supposed to be multi-threaded anyway, but right now on main if you remove the lock all hell breaks loose)
  • Remove remaining JVM backend references from the frontend.
  • Simplify code, delete dead code, etc.

We can't quite remove frontendLock / PPFA yet, as this happens if we do:

[info] Test dotty.tools.dotc.CompilationTests.parallelBackend started
[                                        ] completed (0/7, 0 failed, 1s)Error while emitting gopher/OChannel
Cyclic reference involving class LambdaDeserialize

 Run with -explain-cyclic for more details.
Compilation failed for: 'compiling 'tests/pos/i10477' in test 'parallelBackend''
[=================================>      ] completed (6/7, 1 failed, 4s)Error while emitting adts/t2$Option$
Cyclic reference involving class LambdaMetafactory

 Run with -explain-cyclic for more details.
[=================================>      ] completed (6/7, 1 failed, 4s)Compilation failed for: 'compiling 'tests/pos/reference' in test 'parallelBackend''
[=======================================>] completed (7/7, 2 failed, 4s)

How much have you relied on LLM-based tools in this contribution?

not

How was the solution tested?

existing tests, this is a pure refactoring

@SolalPirelli SolalPirelli marked this pull request as draft April 10, 2026 07:22
@SolalPirelli SolalPirelli force-pushed the solal/jvm-cleanup-more branch 2 times, most recently from 51479f4 to 9c5119c Compare April 10, 2026 12:33
import java.util.ConcurrentModificationException

object FileWriters {
type InternalName = String
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existence of BTypes.InternalName is already questionable, having a dupe here is really weird IMHO

(This file already does its own stuff too much, like a unique "ReadOnlyContext" abstraction...)


object FileWriters {
type InternalName = String
type NullableFile = AbstractFile | Null
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend needs to always get a file back, and only the JAR writer didn't do that here, so I copied the code from the backend version, and thus we no longer have nullable files

type InternalName = String
type NullableFile = AbstractFile | Null
private def classRelativePath(className: String, suffix: String): String =
className.replace('.', '/') + suffix
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved up because it's used by both the tasty and classfile writers

}

}
/**
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review with "ignore whitespace" on

@SolalPirelli SolalPirelli force-pushed the solal/jvm-cleanup-more branch 2 times, most recently from bd6071a to e29a481 Compare April 13, 2026 11:04
@SolalPirelli SolalPirelli marked this pull request as ready for review April 13, 2026 12:33
@SolalPirelli SolalPirelli requested a review from lrytz April 13, 2026 12:33
import dotty.tools.dotc.core.StdNames.nme

final class BoxUnbox(backendUtils: BackendUtils, callGraph: CallGraph, ts: CoreBTypes) {
final class BoxUnbox(backendUtils: BackendUtils, callGraph: CallGraph, ts: WellKnownBTypes) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a large commit but it's just renaming and moving stuff. Anything loading is in the loader, any known types are in WellKnownBTypes, though the loader also has Object + Null + Nothing well-known types since it needs them as part of loading (and WellKnownBTypes just forwards)

Some(classNode, moduleNode)
} catch {
case ex: Exception =>
frontendAccess.optimizerWarning(em"Error while reading InlineInfoAttribute: ${ex.getMessage}", fullName, NoSourcePosition)
Copy link
Copy Markdown
Contributor Author

@SolalPirelli SolalPirelli Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a useful "warning". It's a try/catch around the entire classloading for a hypothetical problem reading one small part of it. If this loading fails we should fail, imho.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea is that we didn't want to fail the compiler just because something could not be inlined / optimized. Maybe the warning text ("while reading InlineInfoAttribute") is just misleading?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, let's reuse the existing "class not found" return value infrastructure then

@SolalPirelli SolalPirelli force-pushed the solal/jvm-cleanup-more branch 3 times, most recently from b5f6a77 to 91daf55 Compare April 23, 2026 13:01
@SolalPirelli
Copy link
Copy Markdown
Contributor Author

Rebased over the latest changes in main (mostly the unsafeNulls removal outside of the backend, and the entry point collection removal)

Now the only non-backend file referencing the JVM backend is Compiler.scala, as it should be.

Copy link
Copy Markdown
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, a lot of nice cleanups!

Comment thread compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala Outdated
Comment thread compiler/src/dotty/tools/backend/jvm/GenBCode.scala
Some(classNode, moduleNode)
} catch {
case ex: Exception =>
frontendAccess.optimizerWarning(em"Error while reading InlineInfoAttribute: ${ex.getMessage}", fullName, NoSourcePosition)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea is that we didn't want to fail the compiler just because something could not be inlined / optimized. Maybe the warning text ("while reading InlineInfoAttribute") is just misleading?

}
// Don't try to build a BType for `VarHandle`, it doesn't exist on JDK 8
if (owner.name == ts.jliMethodHandleRef.internalName || owner.name == "java/lang/invoke/VarHandle")
if (owner.name == "java/lang/invoke/MethodHandle" || owner.name == "java/lang/invoke/VarHandle")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general we should try to always use a BType instance. The reason is the getCommonSuperClass method, which is invoked by ASM during bytecode writing for computing stack map frames.

The comment in getCommonSuperClass mentions "see [[cachedClassBType]]", which was renamed in one of the refactorings in scala3, it's "classBTypeFromInternalName" now, but I think the principle is the same. BType lookup at this stage will only succeed if the BType was previously constructed while running the backend / optimizer.

If it can't be done here, I would at least add a code comment about it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idea is that if owner.name == ts.jliMethodHandleRef.internalName here, we want to make sure we have ts.jliMethodHandleRef in the "already loaded" classes later?

Do we save a significant amount of effort doing this compared to, e.g., listing these descriptors as vals somewhere and always loading the corresponding BTypes if the optimizer is on, without having to thread a "well-known BTypes" object throughout?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably doesn't matter here, but I tried to stick to the principle across the backend of not using strings for static internal names.

The background is: if a reference type p/C appears in generated code, ASM might invoke getCommonSuperClass for that type. At this point we need to be able to get to the corresponding ClassBType.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case I believe it's fine because if we get here the owner will have been loaded as a BType already. Added a comment.


def srNothingRef(using Context): ClassBType =
if srNothingRefLazy eq null then
srNothingRefLazy = classBTypeFromSymbol(requiredClass("scala.runtime.Nothing$"))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no more lock on initializing srNothingRef and the other types here, is that fine? I think we should not have concurrent operations on the symbol table.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be theoretically possible for this not to be initialized before getting into the optimizer indeed...

The reason these three are not eagerly initialized, even though they'll basically always be needed, is that we have a test with just class java and that causes -Ycheck:all to fail if we load java.lang.Object because there are two things named java. I'll see if I can fix -Ycheck instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, loading the BTypes themselves isn't feasible because the class needs those fields as part of BType loading, but fetching the symbols from the symbol table at construction time is easy, so let's do that. it also avoids the problem from my previous comment.

Comment thread compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala Outdated
@SolalPirelli SolalPirelli force-pushed the solal/jvm-cleanup-more branch from 91daf55 to 258e041 Compare April 29, 2026 11:53
@SolalPirelli SolalPirelli requested a review from lrytz April 29, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants