diff --git a/.circleci/config.yml b/.circleci/config.yml index aefe8fc0f934a..7c0d945d74065 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,7 +68,6 @@ commands: $PYTHON_BIN ./embuilder.py build ALL $PYTHON_BIN tests/runner.py test_hello_world - run: - name: embuilder (LTO) command: | $PYTHON_BIN ./embuilder.py build MINIMAL --lto diff --git a/ChangeLog.md b/ChangeLog.md index 59f3ca256fd19..d8f987350d0ac 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -17,6 +17,8 @@ See docs/process.md for how version tagging works. Current Trunk ------------- +- A new `PRINTF_LONG_DOUBLE` option allows printf to print long doubles at full + float128 precision. (#11130) - `emscripten_async_queue_on_thread` has been renamed to `emscripten_dispatch_to_thread` which no longer implies that it is async - the operation is in fact only async if it is sent to another thread, while it diff --git a/src/settings.js b/src/settings.js index a8ab4d1060a31..924921c7cca43 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1773,6 +1773,12 @@ var LLD_REPORT_UNDEFINED = 0; // programs. This which matches the behaviour of gcc/g++ and clang/clang++. var DEFAULT_TO_CXX = 1; +// While LLVM's wasm32 has long double = float128, we don't support printing +// that at full precision by default. Instead we print as 64-bit doubles, which +// saves libc code size. You can flip this option on to get a libc with full +// long double printing precision. +var PRINTF_LONG_DOUBLE = 0; + //=========================================== // Internal, used for testing only, from here //=========================================== diff --git a/system/lib/libc/musl/src/stdio/vfprintf.c b/system/lib/libc/musl/src/stdio/vfprintf.c index 83a1a6a964195..40b9388cfb8c9 100644 --- a/system/lib/libc/musl/src/stdio/vfprintf.c +++ b/system/lib/libc/musl/src/stdio/vfprintf.c @@ -9,11 +9,12 @@ #include #include +#ifndef EMSCRIPTEN_PRINTF_LONG_DOUBLE // XXX EMSCRIPTEN - while wasm32 has long double = float128, we don't support -// printing it at full precision; instead, we lower to +// printing at full precision by default. instead, we lower to // 64-bit double. These macros makes our changes a little less // invasive. -#define long_double double +typedef double long_double; #undef LDBL_TRUE_MIN #define LDBL_TRUE_MIN DBL_DENORM_MIN #undef LDBL_MIN @@ -36,6 +37,10 @@ #define LDBL_MAX_10_EXP DBL_MAX_10_EXP #undef frexpl #define frexpl(x, exp) frexp(x, exp) +#else // EMSCRIPTEN_FULL_LONG_DOUBLE_PRINTING +// XXX EMSCRIPTEN - full long double printing support +typedef long double long_double; +#endif /* Some useful macros */ @@ -244,9 +249,9 @@ typedef char compiler_defines_long_double_incorrectly[9-(int)sizeof(long_double) // get it linked in // also use a double argument here, as mentioned before, // we print float128s at double precision -typedef int (*fmt_fp_t)(FILE *f, double y, int w, int p, int fl, int t); +typedef int (*fmt_fp_t)(FILE *f, long_double y, int w, int p, int fl, int t); -static int fmt_fp(FILE *f, double y, int w, int p, int fl, int t) +static int fmt_fp(FILE *f, long_double y, int w, int p, int fl, int t) { uint32_t big[(LDBL_MANT_DIG+28)/29 + 1 // mantissa expansion + (LDBL_MAX_EXP+LDBL_MANT_DIG+28+8)/9]; // exponent expansion diff --git a/tests/test_other.py b/tests/test_other.py index 8b44480e72d26..c281d46722687 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -10055,6 +10055,35 @@ def test(code): self.assertGreater(lf, both - 100) self.assertLess(lf, both - 50) + @parameterized({ + 'normal': ([], '''\ +0.000051 => -5.123719529365189373493194580078e-05 +0.000051 => -5.123719300544352718866300544498e-05 +0.000051 => -5.123719300544352718866300544498e-05 +'''), + 'full_long_double': (['-s', 'PRINTF_LONG_DOUBLE'], '''\ +0.000051 => -5.123719529365189373493194580078e-05 +0.000051 => -5.123719300544352718866300544498e-05 +0.000051 => -5.123719300544352710023893104250e-05 +'''), + }) + @no_fastcomp('float128 is wasm backend only') + def test_long_double_printing(self, args, expected): + create_test_file('src.cpp', r''' +#include + +int main(void) { + float f = 5.123456789e-5; + double d = 5.123456789e-5; + long double ld = 5.123456789e-5; + printf("%f => %.30e\n", f, f / (f - 1)); + printf("%f => %.30e\n", d, d / (d - 1)); + printf("%Lf => %.30Le\n", ld, ld / (ld - 1)); +} + ''') + run_process([PYTHON, EMCC, 'src.cpp'] + args) + self.assertContained(expected, run_js('a.out.js')) + # Tests that passing -s MALLOC=none will not include system malloc() to the build. def test_malloc_none(self): stderr = self.expect_fail([PYTHON, EMCC, path_from_root('tests', 'malloc_none.c'), '-s', 'MALLOC=none']) diff --git a/tools/system_libs.py b/tools/system_libs.py index 3091e14668b1b..99c072845d4d3 100755 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -814,6 +814,20 @@ def get_files(self): return libc_files +class libprintf_long_double(libc): + name = 'libprintf_long_double' + + cflags = ['-DEMSCRIPTEN_PRINTF_LONG_DOUBLE'] + + def get_files(self): + return files_in_path( + path_components=['system', 'lib', 'libc', 'musl', 'src', 'stdio'], + filenames=['vfprintf.c']) + + def can_build(self): + return super(libprintf_long_double, self).can_build() and shared.Settings.WASM_BACKEND + + class libsockets(MuslInternalLibrary, MTLibrary): name = 'libsockets' @@ -1571,6 +1585,15 @@ def add_library(lib): sanitize = shared.Settings.USE_LSAN or shared.Settings.USE_ASAN or shared.Settings.UBSAN_RUNTIME + # JS math must come before anything else, so that it overrides the normal + # libc math. + if shared.Settings.JS_MATH: + add_library(system_libs_map['libjsmath']) + + # to override the normal libc printf, we must come before it + if shared.Settings.PRINTF_LONG_DOUBLE: + add_library(system_libs_map['libprintf_long_double']) + add_library(system_libs_map['libc']) add_library(system_libs_map['libcompiler_rt']) if not shared.Settings.WASM_BACKEND and not shared.Settings.MINIMAL_RUNTIME: @@ -1633,11 +1656,6 @@ def add_library(lib): # header methods. To avoid that, we link libc++abi last. libs_to_link.sort(key=lambda x: x[0].endswith('libc++abi.bc')) - # JS math must come before anything else, so that it overrides the normal - # libc math. - if shared.Settings.JS_MATH: - libs_to_link = [(system_libs_map['libjsmath'].get_path(), True)] + libs_to_link - # When LINKABLE is set the entire link command line is wrapped in --whole-archive by # Building.link_ldd. And since --whole-archive/--no-whole-archive processing does not nest we # shouldn't add any extra `--no-whole-archive` or we will undo the intent of Building.link_ldd.