Skip to content

Always inline trivial calls that always shrink #7669

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

osa1
Copy link
Contributor

@osa1 osa1 commented Jun 19, 2025

Currently a "trivial call" is a function that just calls another function.

(func $foo ...
  (call $target ...))

Where the arguments to $target are all flat instructions like local.get, or
consts.

Currently we inline these functions always only when not optimizing for code
size.

When optimizing for code size, these functions can always be inlined when

  1. The arguments to $target are all function argument locals.
  2. Each local is used once.
  3. In the order they appear in $foo's signature.

When these hold, inlining $foo never increases code size as it doesn't cause
introducing more locals (or drops etc.) at the call sites.

$foo above when these hold looks like this:

(func $foo (param $arg1 ...) (param $arg2 ...)
  (call $target (local.get $arg1) (local.get $arg2)))

Update FunctionInfo type and FunctionInfoScanner to annotate functions with
more detailed "trivial call" information that also contains whether inlining
shrinks code size.

If a function shrinks when inlined always inline it even with -Os.

Otherwise inline it as before, i.e. when not optimizing for code size.

A "trivial call" is a function that just calls another function.

    (func $foo ...
      (call $target ...))

Currently we inline these functions always only when not optimizing for
code size.

When optimizing for code size, these functions can always be inlined
when

1. The arguments to `$target` are all function argument locals.
2. The locals are not used more than once
3. The locals are used in the same order they appear in the function
   arguments.

When these hold, inlining `$foo` never increases code size as it doesn't
cause introducing more locals at call sites.

Improve `FunctionInfo` type and `FunctionInfoScanner` to annotate
functions with "trivial call" information that also contains whether
inlining shrinks code size.

If a function shrinks when inlined always inline it even with `-Os`.

Otherwise inline it as before, i.e. when not optimizing for code size.
@osa1 osa1 changed the title Always inline trivial calls that always shrinks Always inline trivial calls that always shrink Jun 19, 2025
;; CHECK-NEXT: (local.get $17)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
Copy link
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 little bit verbose. Should we run a simple pass to clean this up? (eliminate blocks and locals)

Copy link
Member

@kripken kripken Jun 20, 2025

Choose a reason for hiding this comment

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

We have --inlining-optimizing for that, and that is what the normal pass pipeline uses. --inlining without optimizing is mainly useful for debugging and tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. --inlining-optimizing simplifies the wat in this test quite a bit. Should I use it in these tests, or do we prefer testing smaller units of code with just --inlining?

@osa1 osa1 marked this pull request as ready for review June 20, 2025 13:59
}
// Trivial calls are already handled. Inline if
// 1. The function doesn't have calls, and
// 2. The function doesn't have loops, or we allow inlining with loops.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I reworded the old comment block here but it just repeats the single line of code below, so I think we can also just drop this comment.

@osa1
Copy link
Contributor Author

osa1 commented Jun 23, 2025

@kripken It looks like this revealed an existing undefined behavior in this line:

maskLeft = (1ULL << leftMaxBits) - 1;

Apparently leftMaxBits can be 64, there are a few code paths in getMaxBits that return 64, e.g.

binaryen/src/ir/bits.h

Lines 267 to 270 in 8470f1b

case RotLInt64:
case RotRInt64:
case SubInt64:
return 64;

Is this a blocker? It looks like an existing issue that getMaxBits returns 64, which is used in a left shift.

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