Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add a desugaring pass between the base linker and the optimizer.#5101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
sjrd merged 2 commits intoscala-js:mainfromsjrd:desugaring-separate-phase
Feb 6, 2025

Conversation

sjrd
Copy link
Member

@sjrdsjrd commentedJan 4, 2025

Previously, the emitters and the optimizer all had to perform the same desugaring forLinkTimeProperty nodes. Instead, we now perform the desugaring in a dedicated phase, after the base linker.

The reachability analysis records whether each method needs desugaring or not. We mark those that do so that the desugaring pass knows what to process. Methods that do not require desugaring are not processed, and so incur no additional cost.

No caching is perfored inDesugarer. It processes so few methods that caching makes it (slightly)slower.

The machinery is heavy. It definitely outweighs the benefits in terms of duplication forLinkTimeProperty alone. However, the same machinery will be used to desugarNewLambda nodes. This commit serves as a stepping stone in that direction.


Alternative to#5096.

He-Pin reacted with thumbs up emoji
@sjrdsjrdforce-pushed thedesugaring-separate-phase branch fromae9fdc5 toa738094CompareJanuary 9, 2025 10:47
@sjrdsjrd marked this pull request as ready for reviewJanuary 9, 2025 10:48
@sjrdsjrd requested a review fromgzm0January 9, 2025 10:48
@sjrd
Copy link
MemberAuthor

sjrd commentedJan 9, 2025

This is now in a reviewable shape, I think.

The second commit might be over-engineered, though. 😅

@gzm0
Copy link
Contributor

I've had a look at this and the PRs on top of this. I think it fits the overall linker design reasonably nicely.

Some higher level comments:

  • IMO the way we flag for desugaring is not very nice at the moment. I'm wondering if just membermethodsToDesugar: Map[MemberNamespace, Set[MethodName]] inLinkedClass would do the trick.

  • I'm wondering if we could doMore DCE wrt case object equality test #2396 in this phase (fold_ === LoadModule(_) to false in case the module is not instantiated, the analyzer could flag for "desugaring" if it sees this shape, which it will need to recognize anyways).

  • How doKnowledge accessor utilities #5099 /Refactor: Introduce common caching utilities. #5098 factor into this picture? (I have not looked at them in very much detail).

  • The second commit might be over-engineered, though. 😅

    I think there is value in having a better structured configuration value. However, I would not put any validation logic into it (and just "hard-code" that in the checkers, just like it is now).

I'll likely proceed with a more detailed review when I have time, unless you feel comparing this to#5096 needs more higher level discussion.

@sjrd
Copy link
MemberAuthor

sjrd commentedJan 17, 2025
edited
Loading

  • IMO the way we flag for desugaring is not very nice at the moment. I'm wondering if just membermethodsToDesugar: Map[MemberNamespace, Set[MethodName]] inLinkedClass would do the trick.

I can try something like that.

  • I'm wondering if we could doMore DCE wrt case object equality test #2396 in this phase (fold_ === LoadModule(_) to false in case the module is not instantiated, the analyzer could flag for "desugaring" if it sees this shape, which it will need to recognize anyways).

I don't think we can. The problem is that wedon't see the shape_ === Load module(_) at this stage. We see something likeLoadModule.equals(y). We have to be able toinline theequals before we see the shape. That's not something we can do at the desugaring phase. We need the optimizer to do it.

They don't. Those two PRs are unrelated. (I was prompted to look into that because I expected to have to write one more cache with clear after run for the desugaring phase. But it turned out it was so fast that a cache makes it worse, and so there is no relationship with the cache PRs.)

  • The second commit might be over-engineered, though. 😅

    I think there is value in having a better structured configuration value. However, I would not put any validation logic into it (and just "hard-code" that in the checkers, just like it is now).

So basically making it a bare enum-like thing with an ordering, and have the checkers know the acceptable ranges? That's what I started with, but found it unwieldy. I can revert to that state to give a comparison.

I'll likely proceed with a more detailed review when I have time, unless you feel comparing this to#5096 needs more higher level discussion.

No I think it's fine. It seems that this variant wins over#5096 overall.

@gzm0
Copy link
Contributor

So basically making it a bare enum-like thing with an ordering, and have the checkers know the acceptable ranges? That's what I started with, but found it unwieldy. I can revert to that state to give a comparison.

Maybe consider giving the enum ordering to make the range checks easier to grok. Or even expand the features on top of the checker (val newLambdaAllowed = ...).

IMHO the important part is that the logic of what is allowed where is in the checkers and not the phase enum.

@sjrdsjrdforce-pushed thedesugaring-separate-phase branch froma738094 to808638eCompareJanuary 18, 2025 12:24
@sjrd
Copy link
MemberAuthor

sjrd commentedJan 18, 2025
edited
Loading

I pushed a different version of theCheckingPhase refactoring. I removed logic fromCheckingPhase; it is now a pure enum. However, sinceClassDefChecker andIRChecker must agree on "which feature is available when", I still kept that logic in a common place, which is nowFeatureSet.FeatureSet isprivate[checked], so it is only an implementation detail of the two checkers. From the checkers' perspective, we use "logical" features now.

This should make things clearer while leaving the responsibility to the checkers, as you mentioned (although factored out inFeatureSet.supportedBy).

@sjrd
Copy link
MemberAuthor

  • IMO the way we flag for desugaring is not very nice at the moment. I'm wondering if just membermethodsToDesugar: Map[MemberNamespace, Set[MethodName]] inLinkedClass would do the trick.

I can try something like that.

I pushed a commit thast switches to that scheme. I'm not sure I like it better, though. It bothers me that we have fields inLinkedClass andLinkedTopLevelExport that are only useful betweenBaseLinker andDesugarer.

@gzm0
Copy link
Contributor

It bothers me that we have fields in LinkedClass and LinkedTopLevelExport that are only useful between BaseLinker and Desugarer.

Yes, I agree, this is not nice in our design. However, I don't think this is anything new:LinkedClass contains a lot of fields only useful for the frontend pipeline (e.g.fieldsRead, all the module dependency information).

Arguably, this is something we should fix. Ironically, one of the easiest way I know is using structural subtyping like in go (producer exposes concrete type, consumer defines an interface of what it needs). Unfortunately, that's not really available to us in Scala.

Other than that, I find the alternative much cleaner :-/ Do you think it makes sense to figure out how to better decouple the fields we have for individual phases?

@sjrd
Copy link
MemberAuthor

However, I don't think this is anything new:LinkedClass contains a lot of fields only useful for the frontend pipeline (e.g.fieldsRead, all the module dependency information).

Good point. The new desugaring info does not make the situation qualitatively worse.

This is probably fine as is, then.

We can try to improve that afterwards.

gzm0 reacted with thumbs up emoji

Copy link
Contributor

@gzm0gzm0 left a comment

Choose a reason for hiding this comment

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

More in-depth review of check refactoring.

How big is the effort to split this into a separate PR to ease review?

hasRuntimeTypeInfo,
fieldsRead,
staticFieldsRead,
staticDependencies,
Copy link
Contributor

Choose a reason for hiding this comment

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

Note to self: This will not be correct anymore when we add theNewLambda desguaring: there will be a dependency on the class.

Copy link
Contributor

@gzm0gzm0 left a comment

Choose a reason for hiding this comment

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

Review of the new phase (as far as possible given the current commit situation).

@@ -105,6 +107,13 @@ final class BaseLinker(config: CommonPhaseConfig, checkIR: Boolean) {

private[frontend] object BaseLinker {

/** Takes a ClassDef and DCE infos to construct a stripped down LinkedClass.
*/
private[frontend] def refineClassDef(classDef: ClassDef, version: Version,
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need this extra method?

newJSPropertyDef.copy()(jsPropertyDef.version)(newJSPropertyDef.pos)
}

override def transformTopLevelExportDef(exportDef: TopLevelExportDef): TopLevelExportDef = {
Copy link
Contributor

Choose a reason for hiding this comment

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

IIUC this override is (now) unnecessary?

@sjrd
Copy link
MemberAuthor

How big is the effort to split this into a separate PR to ease review?

I can do that, but probably not before Wednesday.

@gzm0
Copy link
Contributor

I can do that, but probably not before Wednesday.

No worries. I will not have time before that for sure.

@sjrd
Copy link
MemberAuthor

Rebased on top of#5120.

@sjrdsjrdforce-pushed thedesugaring-separate-phase branch fromb21e091 toa977798CompareJanuary 25, 2025 13:13
@sjrdsjrdforce-pushed thedesugaring-separate-phase branch froma977798 to2820d22CompareFebruary 3, 2025 05:04
@sjrdsjrd requested a review fromgzm0February 3, 2025 05:04
@sjrd
Copy link
MemberAuthor

sjrd commentedFeb 3, 2025

Rebased. This is now back to being standalone and re-reviewable.

Copy link
Contributor

@gzm0gzm0 left a comment

Choose a reason for hiding this comment

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

Just one comment regarding the name of desugaringInfo.

@@ -89,3 +92,19 @@ final class LinkedClass(

def fullName: String = className.nameString
}

object LinkedClass {
final class DesugaringInfo(
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs somewhere a comment at the very least what the infois. (the fact that desugaring is required).

Otherwise this IMO reads more like "information needed/useful for desugaring".

Ideally this of course goes into the names, but I realize thatmethodsNeedingDesugaring is probably a bit too long :-/

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

If only we had Latin's gérondif 😁 We could call itAbSaccharoTemperandum 😅

PerhapsDesugaringRequirements?

@sjrdsjrdforce-pushed thedesugaring-separate-phase branch from2820d22 to4f75c5aCompareFebruary 5, 2025 09:06
@sjrd
Copy link
MemberAuthor

sjrd commentedFeb 5, 2025

I went forDesugaringRequirements.

Also I changed how we construct instances ofDesugaringRequirements, in a way that provides a fastisEmpty test. This allowsDesugarer to entirely bypass classes that need no desugaring. It's now running in 3.5 ms on my machine.

@sjrdsjrd requested a review fromgzm0February 5, 2025 09:09
Previously, the emitters and the optimizer all had to perform thesame desugaring for `LinkTimeProperty` nodes. Instead, we nowperform the desugaring in a dedicated phase, after the baselinker.The reachability analysis records whether each method needsdesugaring or not. We mark those that do so that the desugaringpass knows what to process. Methods that do not requiredesugaring are not processed, and so incur no additional cost.No caching is performed in `Desugarer`. It processes so fewmethods that caching makes it (slightly) *slower*.The machinery is heavy. It definitely outweighs the benefits interms of duplication for `LinkTimeProperty` alone. However, thesame machinery will be used to desugar `NewLambda` nodes. Thiscommit serves as a stepping stone in that direction.
@sjrdsjrdforce-pushed thedesugaring-separate-phase branch from4f75c5a to1987d87CompareFebruary 6, 2025 13:09
private def desugarClass(linkedClass: LinkedClass): LinkedClass = {
import linkedClass._

if (desugaringRequirements.isEmpty) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

@sjrdsjrd merged commit76f7be7 intoscala-js:mainFeb 6, 2025
3 checks passed
@sjrdsjrd deleted the desugaring-separate-phase branchFebruary 6, 2025 15:46
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@gzm0gzm0gzm0 approved these changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

2 participants
@sjrd@gzm0

[8]ページ先頭

©2009-2025 Movatter.jp