emscripten-core / emscripten-core/emscripten
Performance Degradation: Excessive fmaf Calls Generated for __builtin_elementwise_fma Leading to Inefficient Code Generation
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
Calling `__builtin_elementwise_fma` leads to to multiple calls to `fmaf` which is very inefficient.
I created an example with two functions `fma_a` and `fma_b`. Both are performing a `fma` operation on a built-in vector type. `fma_a` generates reasonable wasm output, while the `fma_b` version generates 4 calls to `fmaf` instruction. In a real-life use case, this lead to 20x performance drop.
**Version of emscripten/emsdk:**
```
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.53 (ce5114bdd2175c7297583d3c25a53ca95d22f4ce)
clang version 19.0.0git (https://github.com/llvm/llvm-project febb4c42b192ed7c88c17f91cb903a59acf20baf)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/< my-username>/.conan2/p/b/emsdk4474317abf4a5/p/upstream/bin
```
**Failing command line in full:**
```
em++ -O3 -ffast-math -msimd128 -fenable-matrix -c -o test.o test.cpp
wasm-dis test.o -o test.wat
```
**test.cpp**
```cpp
template < int vectorLength, typename T >
struct VecImpl { using type = T __attribute__(( vector_size( vectorLength * sizeof( T ) ) )); };
template < int vectorLength, typename T = float >
using Vec = typename VecImpl< vectorLength, T >::type;
void fma_a( Vec< 4, float > & c, Vec< 4, float > const a, Vec< 4, float > const b ) noexcept
{
c += a * b;
}
void fma_b( Vec< 4, float > & c, Vec< 4, float > const a, Vec< 4, float > const b ) noexcept
{
c = __builtin_elementwise_fma( a, b, c );
}
```
**Generated output (test.wat)**
```
(module
(type $0 (func (param i32 v128 v128)))
(type $1 (func (param f32 f32 f32) (result f32)))
(import "env" "__linear_memory" (memory $mimport$0 0))
(import "env" "fmaf" (func $fimport$0 (param f32 f32 f32) (result f32)))
(func $0 (param $0 i32) (param $1 v128) (param $2 v128)
(v128.store
(local.get $0)
(f32x4.add
(v128.load
(local.get $0)
)
(f32x4.mul
(local.get $2)
(local.get $1)
)
)
)
)
(func $1 (param $0 i32) (param $1 v128) (param $2 v128)
(local $3 v128)
(v128.store
(local.get $0)
(f32x4.replace_lane 3
(f32x4.replace_lane 2
(f32x4.replace_lane 1
(f32x4.splat
(call $fimport$0
(f32x4.extract_lane 0
(local.get $1)
)
(f32x4.extract_lane 0
(local.get $2)
)
(f32x4.extract_lane 0
(local.tee $3
(v128.load
(local.get $0)
)
)
)
)
)
(call $fimport$0
(f32x4.extract_lane 1
(local.get $1)
)
(f32x4.extract_lane 1
(local.get $2)
)
(f32x4.extract_lane 1
(local.get $3)
)
)
)
(call $fimport$0
(f32x4.extract_lane 2
(local.get $1)
)
(f32x4.extract_lane 2
(local.get $2)
)
(f32x4.extract_lane 2
(local.get $3)
)
)
)
(call $fimport$0
(f32x4.extract_lane 3
(local.get $1)
)
(f32x4.extract_lane 3
(local.get $2)
)
(f32x4.extract_lane 3
(local.get $3)
)
)
)
)
)
;; custom section "linking", size 55
;; custom section "reloc.CODE", size 14
;; custom section "producers", size 111
;; features section: mutable-globals, simd, sign-ext
)
```
**Godbolt link**
I couldn't fix the version of Emscripten in Godbolt. However, as of writing this bug report, the same issue can be reproduced. I've included instructions on how to reproduce the issue locally in the snippets above.
https://godbolt.org/z/6s1dab4es
Contributor guide
Assessment
This issue has not been assessed yet.