Skip to content
5 changes: 5 additions & 0 deletions src/settings_internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ var ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = [];

var WARN_DEPRECATED = true;

// Enable fast math optimizations in wasm-opt when -ffast-math is passed.
// This enables aggressive floating-point optimizations that may violate
// IEEE 754 semantics but can improve performance.
var FAST_MATH = 0;

// WebGL 2 provides new garbage-free entry points to call to WebGL. Use
// those always when possible.
// We currently set this to false for certain browser when large memory sizes
Expand Down
47 changes: 47 additions & 0 deletions test/other/test_fast_math.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <math.h>
#include <stdio.h>

static double mandelbrot_iter(double cx, double cy, int max_iter) {
double x = 0.0, y = 0.0;
for (int i = 0; i < max_iter; i++) {
if (x*x + y*y > 4.0) return (double)i;
double tx = x*x - y*y + cx;
y = 2.0*x*y + cy;
x = tx;
}
return (double)max_iter;
}

static double newton_raphson(double x, int iterations) {
for (int i = 0; i < iterations; i++) {
double fx = x*x*x - x - 1.0;
double fpx = 3.0*x*x - 1.0;
if (fabs(fpx) < 1e-10) break;
x = x - fx / fpx;
}
return x;
}

int main() {
double result = 0.0;

for (int i = 0; i < 100; i++) {
double x = (i - 50) * 0.02;
for (int j = 0; j < 100; j++) {
double y = (j - 50) * 0.02;
result += mandelbrot_iter(x, y, 50);
}
}

for (int i = 0; i < 50; i++) {
result += newton_raphson(1.5 + i * 0.1, 20);
}

for (int i = 0; i < 1000; i++) {
double angle = i * 0.01;
result += sin(angle) * cos(angle) + tan(angle);
}

printf("Result: %f\n", result);
return 0;
}
17 changes: 17 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -15796,3 +15796,20 @@ def has_defined_function(file, func):
self.assertIn('main.cpp', out)
self.assertIn('foo.cpp', out)
self.assertIn('/emsdk/emscripten/system/lib/libc/musl/src/string/strcmp.c', out)

def test_binaryen_fast_math(self):
# Use a simple input; contents don't matter for -v flag inspection
err = self.run_process([EMCC, test_file('hello_world.c'), '-v', '-O2', '-ffast-math'], stderr=PIPE).stderr
self.assertContained('--fast-math', err)

err_no_fast = self.run_process([EMCC, test_file('hello_world.c'), '-v', '-O2'], stderr=PIPE).stderr
self.assertNotContained('--fast-math', err_no_fast)

def test_fast_math(self):
self.run_process([EMCC, test_file('other/test_fast_math.c'), '-O2', '-o', 'no_fast.wasm'])
no_fast_size = os.path.getsize('no_fast.wasm')
self.run_process([EMCC, test_file('other/test_fast_math.c'), '-O2', '-ffast-math', '-o', 'with_fast.wasm'])
with_fast_size = os.path.getsize('with_fast.wasm')
print(f'no_fast_size={no_fast_size} with_fast_size={with_fast_size}')

self.assertLessEqual(with_fast_size, no_fast_size)
7 changes: 4 additions & 3 deletions tools/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,8 @@ def consume_arg_file():
settings.SHRINK_LEVEL = 0
settings.DEBUG_LEVEL = max(settings.DEBUG_LEVEL, 1)
elif requested_level == 'fast':
# TODO(https://github.com/emscripten-core/emscripten/issues/21497):
# If we ever map `-ffast-math` to `wasm-opt --fast-math` then
# then we should enable that too here.
# -Ofast typically includes -ffast-math semantics
settings.FAST_MATH = 1
requested_level = 3
settings.SHRINK_LEVEL = 0
else:
Expand Down Expand Up @@ -545,6 +544,8 @@ def consume_arg_file():
settings.WASM_EXCEPTIONS = 1
elif arg == '-fignore-exceptions':
settings.DISABLE_EXCEPTION_CATCHING = 1
elif arg == '-ffast-math':
settings.FAST_MATH = 1
elif check_arg('--default-obj-ext'):
exit_with_error('--default-obj-ext is no longer supported by emcc')
elif arg.startswith('-fsanitize=cfi'):
Expand Down
2 changes: 2 additions & 0 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ def get_binaryen_passes():
passes += ['--pass-arg=post-emscripten-side-module']
if optimizing:
passes += [building.opt_level_to_str(settings.OPT_LEVEL, settings.SHRINK_LEVEL)]
if settings.FAST_MATH:
passes += ['--fast-math']
# when optimizing, use the fact that low memory is never used (1024 is a
# hardcoded value in the binaryen pass). we also cannot do it when the stack
# is first, as then the stack is in the low memory that should be unused.
Expand Down