8000 Codegen: Inline the target of `Function` nodes in their `js.Closure`s. by sjrd · Pull Request #5081 · scala-js/scala-js · GitHub
[go: up one dir, main page]

Skip to content

Codegen: Inline the target of Function nodes in their js.Closures. #5081

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 1, 2024

Conversation

sjrd
Copy link
Member
@sjrd sjrd commented Nov 25, 2024

The body of Function nodes always has a simple shape that calls a helper method. We previously generated that call in the body of the js.Closure, and marked the target method @inline so that the optimizer would always inline it.

Instead, we now directly "inline" it from the codegen, by generating the js.MethodDef right inside the js.Closure scope.

As is, this does not change the generated code. However, it may speed up (cold) linker runs, since it will have less work to do. Notably, it performs two fewer knowledge queries to find and inline the target method. It also reduces the total amount of methods to manipulate in the incremental analysis.

More importantly, this will be necessary later if we want to add support for async/await or function*/yield. Indeed, for those, we will need await/yield expressions to be lexically scoped in the body of their enclosing closure. That won't work if they are in the body of a separate helper method.

@sjrd sjrd requested a review from gzm0 November 25, 2024 22:57
@sjrd sjrd force-pushed the inline-delambdafy-targets branch from f2c03d6 to bb61516 Compare November 26, 2024 07:56
Copy link
Contributor
@gzm0 gzm0 left a comment

Choose a reason for hiding this comment

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

Nice! Just some details.

def gen(tree: Tree): Unit = {
tree match {
case EmptyTree => ()
case Template(_, _, body) => body foreach gen
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to double check: This was dead code?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not strictly dead code, but it would only happen for the top-level "recursive" call (we cannot have a Template inside a Temp 8000 late, and would always happen for the top-level call (the input is statically typed as Template, so we know that). The new code directly iterates over impl.body instead.

def gen(tree: Tree): Unit = {
tree match {
case EmptyTree => ()
case Template(_, _, body) => body foreach gen
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here: This was dead-code?

/* Declare each method param as a VarDef, initialized to the corresponding arg.
* In practice, all the args are `This` nodes of `VarRef` nodes, so the
* optimizer will alias those VarDefs away.
* We do it in two steps because we have different Symbols, hence different
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* We do it in two steps because we have different Symbols, hence different
* We do this because we have different Symbols, hence different

WDYT? The current wording confused me.

val patchedBody =
js.Block(paramsLocals :+ ensureResultBoxed(body, target))
/* Declare each method param as a VarDef, initialized to the corresponding arg.
* In practice, all the args are `This` nodes of `VarRef` nodes, so the
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* In practice, all the args are `This` nodes of `VarRef` nodes, so the
* In practice, all the args are `This` nodes or `VarRef` nodes, so the

*/
val formalArgs = paramTrees.map(p => genParamDef(p.symbol))
val (patchedFormalArgs, paramsLocals) =
patchFunParamsWithBoxes(target, formalArgs, useParamsBeforeLambdaLift = true)
Copy link
Contributor

Choose a reason for hiding this comment

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

It is a bit ugly IMO that we introduce another set of VarDefs here for unboxing.

Copy link
Member Author
@sjrd sjrd Dec 1, 2024

Choose a reason for hiding this comment

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

This will definitely change again with #5003. That said per se the additional VarDefs will still be there, since types don't always align.

The changes in this PR are neutral in terms of number of assignments that need to be processed by the optimizer: we traded Bindings for ParamDefs in a method call for Bindings to local VarDefs.

We can look into improving this a bit further after #5003 if we think it's worth it.

@sjrd sjrd force-pushed the inline-delambdafy-targets branch from bb61516 to 6737d97 Compare December 1, 2024 08:26
The body of `Function` nodes always has a simple shape that calls a
helper method. We previously generated that call in the body of the
`js.Closure`, and marked the target method `@inline` so that the
optimizer would always inline it.

Instead, we now directly "inline" it from the codegen, by
generating the `js.MethodDef` right inside the `js.Closure` scope.

As is, this does not change the generated code. However, it may
speed up (cold) linker runs, since it will have less work to do.
Notably, it performs two fewer knowledge queries to find and inline
the target method. It also reduces the total amount of methods to
manipulate in the incremental analysis.

More importantly, this will be necessary later if we want to add
support for `async/await` or `function*/yield`. Indeed, for those,
we will need `await`/`yield` expressions to be lexically scoped
in the body of their enclosing closure. That won't work if they are
in the body of a separate helper method.
@sjrd sjrd force-pushed the inline-delambdafy-targets branch from 6737d97 to 0d16b42 Compare December 1, 2024 08:31
@sjrd sjrd merged commit 030eff0 into scala-js:main Dec 1, 2024
3 checks passed
@sjrd sjrd deleted the inline-delambdafy-targets branch December 1, 2024 11:58
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
0