Skip to content

Commit 9c8beb9

Browse files
authored
PRINTF_LONG_DOUBLE option (#11130)
While wasm32 has long double = float128, we didn't support printing it at full precision; instead, we convert long doubles to doubles for printing, which lets libc be a little smaller, as musl has a single float path for printf; if it were long double, all doubles would use it, and it makes us include float128 emulation code. This new option links in a modified vfprintf that uses long double, and therefore allows long doubles to be printed at full software float128 precision (at the cost of a larger libc).
1 parent 12b23e5 commit 9c8beb9

File tree

6 files changed

+69
-10
lines changed

6 files changed

+69
-10
lines changed

.circleci/config.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ commands:
6868
$PYTHON_BIN ./embuilder.py build ALL
6969
$PYTHON_BIN tests/runner.py test_hello_world
7070
- run:
71-
7271
name: embuilder (LTO)
7372
command: |
7473
$PYTHON_BIN ./embuilder.py build MINIMAL --lto

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ See docs/process.md for how version tagging works.
1717

1818
Current Trunk
1919
-------------
20+
- A new `PRINTF_LONG_DOUBLE` option allows printf to print long doubles at full
21+
float128 precision. (#11130)
2022
- `emscripten_async_queue_on_thread` has been renamed to
2123
`emscripten_dispatch_to_thread` which no longer implies that it is async -
2224
the operation is in fact only async if it is sent to another thread, while it

src/settings.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,12 @@ var LLD_REPORT_UNDEFINED = 0;
17731773
// programs. This which matches the behaviour of gcc/g++ and clang/clang++.
17741774
var DEFAULT_TO_CXX = 1;
17751775

1776+
// While LLVM's wasm32 has long double = float128, we don't support printing
1777+
// that at full precision by default. Instead we print as 64-bit doubles, which
1778+
// saves libc code size. You can flip this option on to get a libc with full
1779+
// long double printing precision.
1780+
var PRINTF_LONG_DOUBLE = 0;
1781+
17761782
//===========================================
17771783
// Internal, used for testing only, from here
17781784
//===========================================

system/lib/libc/musl/src/stdio/vfprintf.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
#include <math.h>
1010
#include <float.h>
1111

12+
#ifndef EMSCRIPTEN_PRINTF_LONG_DOUBLE
1213
// XXX EMSCRIPTEN - while wasm32 has long double = float128, we don't support
13-
// printing it at full precision; instead, we lower to
14+
// printing at full precision by default. instead, we lower to
1415
// 64-bit double. These macros makes our changes a little less
1516
// invasive.
16-
#define long_double double
17+
typedef double long_double;
1718
#undef LDBL_TRUE_MIN
1819
#define LDBL_TRUE_MIN DBL_DENORM_MIN
1920
#undef LDBL_MIN
@@ -36,6 +37,10 @@
3637
#define LDBL_MAX_10_EXP DBL_MAX_10_EXP
3738
#undef frexpl
3839
#define frexpl(x, exp) frexp(x, exp)
40+
#else // EMSCRIPTEN_FULL_LONG_DOUBLE_PRINTING
41+
// XXX EMSCRIPTEN - full long double printing support
42+
typedef long double long_double;
43+
#endif
3944

4045
/* Some useful macros */
4146

@@ -244,9 +249,9 @@ typedef char compiler_defines_long_double_incorrectly[9-(int)sizeof(long_double)
244249
// get it linked in
245250
// also use a double argument here, as mentioned before,
246251
// we print float128s at double precision
247-
typedef int (*fmt_fp_t)(FILE *f, double y, int w, int p, int fl, int t);
252+
typedef int (*fmt_fp_t)(FILE *f, long_double y, int w, int p, int fl, int t);
248253

249-
static int fmt_fp(FILE *f, double y, int w, int p, int fl, int t)
254+
static int fmt_fp(FILE *f, long_double y, int w, int p, int fl, int t)
250255
{
251256
uint32_t big[(LDBL_MANT_DIG+28)/29 + 1 // mantissa expansion
252257
+ (LDBL_MAX_EXP+LDBL_MANT_DIG+28+8)/9]; // exponent expansion

tests/test_other.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10055,6 +10055,35 @@ def test(code):
1005510055
self.assertGreater(lf, both - 100)
1005610056
self.assertLess(lf, both - 50)
1005710057

10058+
@parameterized({
10059+
'normal': ([], '''\
10060+
0.000051 => -5.123719529365189373493194580078e-05
10061+
0.000051 => -5.123719300544352718866300544498e-05
10062+
0.000051 => -5.123719300544352718866300544498e-05
10063+
'''),
10064+
'full_long_double': (['-s', 'PRINTF_LONG_DOUBLE'], '''\
10065+
0.000051 => -5.123719529365189373493194580078e-05
10066+
0.000051 => -5.123719300544352718866300544498e-05
10067+
0.000051 => -5.123719300544352710023893104250e-05
10068+
'''),
10069+
})
10070+
@no_fastcomp('float128 is wasm backend only')
10071+
def test_long_double_printing(self, args, expected):
10072+
create_test_file('src.cpp', r'''
10073+
#include <stdio.h>
10074+
10075+
int main(void) {
10076+
float f = 5.123456789e-5;
10077+
double d = 5.123456789e-5;
10078+
long double ld = 5.123456789e-5;
10079+
printf("%f => %.30e\n", f, f / (f - 1));
10080+
printf("%f => %.30e\n", d, d / (d - 1));
10081+
printf("%Lf => %.30Le\n", ld, ld / (ld - 1));
10082+
}
10083+
''')
10084+
run_process([PYTHON, EMCC, 'src.cpp'] + args)
10085+
self.assertContained(expected, run_js('a.out.js'))
10086+
1005810087
# Tests that passing -s MALLOC=none will not include system malloc() to the build.
1005910088
def test_malloc_none(self):
1006010089
stderr = self.expect_fail([PYTHON, EMCC, path_from_root('tests', 'malloc_none.c'), '-s', 'MALLOC=none'])

tools/system_libs.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,20 @@ def get_files(self):
814814
return libc_files
815815

816816

817+
class libprintf_long_double(libc):
818+
name = 'libprintf_long_double'
819+
820+
cflags = ['-DEMSCRIPTEN_PRINTF_LONG_DOUBLE']
821+
822+
def get_files(self):
823+
return files_in_path(
824+
path_components=['system', 'lib', 'libc', 'musl', 'src', 'stdio'],
825+
filenames=['vfprintf.c'])
826+
827+
def can_build(self):
828+
return super(libprintf_long_double, self).can_build() and shared.Settings.WASM_BACKEND
829+
830+
817831
class libsockets(MuslInternalLibrary, MTLibrary):
818832
name = 'libsockets'
819833

@@ -1571,6 +1585,15 @@ def add_library(lib):
15711585

15721586
sanitize = shared.Settings.USE_LSAN or shared.Settings.USE_ASAN or shared.Settings.UBSAN_RUNTIME
15731587

1588+
# JS math must come before anything else, so that it overrides the normal
1589+
# libc math.
1590+
if shared.Settings.JS_MATH:
1591+
add_library(system_libs_map['libjsmath'])
1592+
1593+
# to override the normal libc printf, we must come before it
1594+
if shared.Settings.PRINTF_LONG_DOUBLE:
1595+
add_library(system_libs_map['libprintf_long_double'])
1596+
15741597
add_library(system_libs_map['libc'])
15751598
add_library(system_libs_map['libcompiler_rt'])
15761599
if not shared.Settings.WASM_BACKEND and not shared.Settings.MINIMAL_RUNTIME:
@@ -1633,11 +1656,6 @@ def add_library(lib):
16331656
# header methods. To avoid that, we link libc++abi last.
16341657
libs_to_link.sort(key=lambda x: x[0].endswith('libc++abi.bc'))
16351658

1636-
# JS math must come before anything else, so that it overrides the normal
1637-
# libc math.
1638-
if shared.Settings.JS_MATH:
1639-
libs_to_link = [(system_libs_map['libjsmath'].get_path(), True)] + libs_to_link
1640-
16411659
# When LINKABLE is set the entire link command line is wrapped in --whole-archive by
16421660
# Building.link_ldd. And since --whole-archive/--no-whole-archive processing does not nest we
16431661
# shouldn't add any extra `--no-whole-archive` or we will undo the intent of Building.link_ldd.

0 commit comments

Comments
 (0)