WebAssembly / WebAssembly/binaryen
Inlining exported functions, -O vs -O3
Nobody has claimed this yet.
- Dominant language
- WebAssembly
- Stars
- 8.6k
- Forks
- 885
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 69
Description
Having the following unoptimized module with an exported and an internal function suitable for inlining:
(module
(type $iii (func (param i32 i32) (result i32)))
(export "add" (func $exported_add))
(export "test" (func $test))
(func $exported_add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $0)
(get_local $1)
)
)
(func $internal_sub (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
(i32.sub
(get_local $0)
(get_local $1)
)
)
(func $test (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(call $exported_add
(get_local $0)
(get_local $1)
)
(call $internal_sub
(get_local $0)
(get_local $1)
)
)
)
)
When optimizing with default optimization levels, this produces:
(module
(type $iii (func (param i32 i32) (result i32)))
(export "add" (func $exported_add))
(export "test" (func $test))
(func $exported_add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $0)
(get_local $1)
)
)
(func $test (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(call $exported_add ;; exported call is not inlined
(get_local $0)
(get_local $1)
)
(i32.sub ;; internal call is inlined
(get_local $0)
(get_local $1)
)
)
)
)
Which appears to keep the call to the exported function for size reasons (it inlines it in -O3, see below), but it probably should also inline in -O because an i32.add should always be smaller than a call with an additional function index immediate - is this correct?
For comparison, this is what's produced with -O3:
(module
(type $iii (func (param i32 i32) (result i32)))
(export "add" (func $exported_add))
(export "test" (func $test))
(func $exported_add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $0)
(get_local $1)
)
)
(func $test (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(i32.add ;; inlined as well
(get_local $0)
(get_local $1)
)
(i32.sub
(get_local $0)
(get_local $1)
)
)
)
)
Fwiw, it also doesn't inline this call in -O2 without a shrinklevel, which seems odd, considering that -O3s also doesn't inline it (maybe) for size reasons.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Use the WAT module in the issue as the reproduction and compare output at -O, -O2, -O3, and -O3s, focusing on calls to exported versus internal functions. Determine whether the differing inlining decisions are intentional and whether the size behavior is consistent; done means the behavior is explained or corrected with an appropriate regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- wasm
- Domain
- compilers, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100