Skip to content

Partial inlining via function splitting#4152

Merged
kripken merged 140 commits into
mainfrom
partial
Sep 17, 2021
Merged

Partial inlining via function splitting#4152
kripken merged 140 commits into
mainfrom
partial

Conversation

@kripken

@kripken kripken commented Sep 14, 2021

Copy link
Copy Markdown
Member

(This is the last "big" optimization on my plate atm for wasm GC.)

This PR helps with functions like this:

function foo(x) {
  if (x) {
    ..
    lots of work here
    ..
  }
}

If "lots of work" is large enough, then we won't inline such a
function. However, we may end up calling into the function
only to get a false on that if and immediately exit. So it is useful
to partially inline this function, basically by creating a split
of it into a condition part that is inlineable

function foo$inlineable(x) {
  if (x) {
    foo$outlined();
  }
}

and an outlined part that is not inlineable:

function foo$outlined(x) {
  ..
  lots of work here
  ..
}

We can then inline the inlineable part. That means that a call
like

foo(param);

turns into

if (param) {
  foo$outlined();
}

In other words, we end up replacing a call and then a check with
a check and then a call. Any time that the condition is false, this
will be a speedup.

The cost here is increased size, as we duplicate the condition
into the callsites. For that reason, only do this when heavily
optimizing for size.

This is a 10% speedup on j2cl. This helps two types of functions
there: Java class inits, which often look like "have I been
initialized before? if not, do all this work", and also assertion
methods which look like "if the input is null, throw an
exception".

kripken and others added 30 commits August 24, 2021 14:20
When we catches "Output must be deterministic" we can't see any details. This PR fix
this and now we can see diff of b1.wasm and b2.wasm files.

Example output:

Output must be deterministic.
Diff:

--- expected

+++ actual

@@ -2072,9 +2072,7 @@

          )
          (drop
           (block $label$16 (result funcref)
-           (local.set $10
-            (ref.null func)
-           )
+           (nop)
            (drop
             (call $22
              (f64.const 0.296)
@kripken kripken requested review from aheejin and tlively September 14, 2021 16:07

@tlively tlively left a comment

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 haven't read the tests yet, but here are my comments so far.

Comment thread src/passes/Inlining.cpp Outdated
Comment thread src/passes/Inlining.cpp Outdated
Comment thread src/passes/Inlining.cpp Outdated
// (like limitations on which functions can be inlined into in each iteration,
// the number of iterations, etc.). Therefore this function will only find out
// if we *can* split, but not actually do any splitting.
bool canSplit(Function* func, const FunctionInfo& info) {

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 looks like info is not used in this function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch!

Comment thread src/passes/Inlining.cpp
Comment on lines +536 to +537
// If the body is a block, and we have breaks to that block, then we cannot
// outline any code - we can't outline a break without the break's target.

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.

Do we separately need to check for returns for the same reason?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not necessarily - one of the patterns below allows returns. The other will check for them.

Comment thread src/passes/Inlining.cpp Outdated
Comment thread src/passes/Inlining.cpp Outdated
//
// TODO: support a return value
if (!iff->ifFalse && func->getResults() == Type::none &&
iff->ifTrue->is<Return>() && body->is<Block>()) {

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.

Why do we need to check body->is<Block>() here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point, we don't need to. In fact lower down we assert on that. I'll move the assert up here and remove the if.

Comment thread src/passes/Inlining.cpp
Comment on lines +572 to +573
split.inlineable = copyFunction(func, "inlineable-A");
auto* outlined = copyFunction(func, "outlined-A");

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.

Why the -A in the names?

Also, it seems wasteful to create a full copy of the function for inlineable, which will end up being very small. Could we avoid copying entirely and have the two new functions refer to the original IR nodes directly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

-A is for "pattern A" as opposed to pattern B that appears below.

Yeah, there is a TODO in the copy function itself for avoiding the extra copying work. It's more work though, and potentially error-prone, so I'm not sure it's worth it. The overhead is wasteful but not that bad.

Comment thread src/passes/Inlining.cpp Outdated
Comment thread src/passes/Inlining.cpp Outdated
Comment thread src/passes/Inlining.cpp
// Checks if an expression is very simple - something simple enough that we
// are willing to inline it in this optimization. This should basically take
// almost no cost at all to compute.
bool isSimple(Expression* curr) {

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.

Should the comparison instructions be simple as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do you mean the if conditions? This is called on them.

Or what do you mean here?

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 mean things like i32.lt_u, i32.ge_s, etc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, yes, we may want those as well. For now I just added a tiny set of things, but it already handles the main things that j2cl needs. The rest is a TODO (on line 765).

kripken and others added 7 commits September 15, 2021 12:56
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
Comment on lines +4068 to +4069
;; CHECK-NEXT: (local $24 i32)
;; CHECK-NEXT: (local $25 f64)

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 would be good to replace this dump of real-world code with something more focused at some point. The diff here is not too useful for determining whether the code is working correctly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, sorry about that - for a change like this, inlining more things, the diff on this existing file is quite large.

(Ideally we could somehow make this particular file ignore this change, but otherwise I guess ignoring it in review is second best.)

@@ -31060,3 +31021,87 @@
)
)
)
;; CHECK: (func $byn-split-outlined-B$_fmt_u (param $0 i32) (param $1 i32) (param $2 i32)

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.

Neat to see this work on real-world code, though!

Comment thread test/lit/passes/inlining_splitting.wast Outdated
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $call-reachable-if-body
;; Note that the above contains

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.

Would it be worth putting a loop in to make the test output less surprising?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm, I think it's nice to show this actually. It is a useful result - if we regress this, we want to know.

Comment on lines +1073 to +1074
(if
(i32.const 1)

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's not important that there's an if here, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, we don't actually even scan this inner if - we just look for returns inside it.

kripken and others added 2 commits September 16, 2021 19:28
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
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.

3 participants