Emscripten: Document long double printing issues (was: std::numeric_limits<long double>::max() returns infinity)

Created on 6 Nov 2020  路  26Comments  路  Source: emscripten-core/emscripten

According to the spec, this value should be finite: https://en.cppreference.com/w/cpp/types/numeric_limits/max. This is with Emscripten 2.0.8.

good first bug help wanted

Most helpful comment

Yes, adding -s PRINTF_LONG_DOUBLE=1 does the trick in my case.

On a more general note (let me know if I should make it a separate issue), it seems to me there are two broad categories of builds involving Emscripten: (1) building the end-product (e.g., to be used in the browser) and (2) building common libraries (normally to be used in (1)) and running their tests (to make sure they are functioning correctly under Emscripten). While disabling expensive things (exceptions, printing long double, etc) by default probably makes sense for (1), it also makes the second type of builds really painful, with a long and noisy string of extra -s options. So I am wondering if it would make sense to have a single -s option that can be used to get an environment that is as close as possible to the "standard" C/C++ and POSIX?

All 26 comments

This is a printing limitation, I believe. Building with -sPRINTF_LONG_DOUBLE will enable full long double printing, and show a correct value on e.g.

#include <limits>
#include <cstddef>
#include <iostream>
int main()
{
    std::cout << "double: " << std::defaultfloat << std::numeric_limits<long double>::max()
              << " or " << std::hexfloat << std::numeric_limits<long double>::max() << '\n';
}

We should probably add an FAQ entry for this.

Yes, adding -s PRINTF_LONG_DOUBLE=1 does the trick in my case.

On a more general note (let me know if I should make it a separate issue), it seems to me there are two broad categories of builds involving Emscripten: (1) building the end-product (e.g., to be used in the browser) and (2) building common libraries (normally to be used in (1)) and running their tests (to make sure they are functioning correctly under Emscripten). While disabling expensive things (exceptions, printing long double, etc) by default probably makes sense for (1), it also makes the second type of builds really painful, with a long and noisy string of extra -s options. So I am wondering if it would make sense to have a single -s option that can be used to get an environment that is as close as possible to the "standard" C/C++ and POSIX?

I think that is not a bad idea. Something like -s COMPATIBLE which means you generally don't care about size and speed as much as normal web app?

What other options would you suggest putting behind such as flag?

Something like -s COMPATIBLE

I like the name, analogous to STRICT in a way.

What other options would you suggest putting behind such as flag?

In my experiments I needed all of these:

DISABLE_EXCEPTION_CATCHING=0
PRINTF_LONG_DOUBLE=1
EXIT_RUNTIME=1
ALLOW_MEMORY_GROWTH=1

With these I was able to build the following packages and run their tests (some of which failed but not because of "you need -s XXX" errors):

libexpat-2.2.9
libpdjson-0.1.0
libsqlite3-3.27.2+1
libstud-json-0.1.0
libz-1.2.1100+1
pugixml-1.10.0+2
sqlite3-3.27.2+1

I've also audited settings.js and I think it makes sense to add the following (I've tried the above packages with STRICT and they all built fine):

STRICT=1

I've also noticed the following options which currently have the desired values but if that changes (like DISABLE_EXCEPTION_CATCHING), then they would need to be included.

FILESYSTEM=1
ERROR_ON_UNDEFINED_SYMBOLS=1
SUPPORT_ERRNO=1
SUPPORT_LONGJMP=1

Finally, the following seems like it might be a good idea to add but I am not sure:

WASM_BIGINT=1

It is unfortunate that we have -s STRICT=1. The meaning of that flag mutated along time to a completely different meaning from what I had intended.

Originally meaning of -s STRICT=1 was a way to quickly opt-in to future possibly breaking changes to the compiler. That way, one could install the latest compiler trunk, and re-do a build of whatever project they were building by flipping just one flag -s STRICT=1 to quickly & simply check if their project was still "safe" with the latest compiler features and best practices. I.e. the meaning was what it documents on the first line of strict:

Emscripten 'strict' build mode: Drop supporting any deprecated build options

Historically, not breaking users has been very important to Emscripten. That is why all breaking changes that come in that we know users in wild depend on, would first come with an optional flag to opt-in to the breakage, so that people have a good migration experience. Then at some point we'd flip the default, and then finally delete the flag (but keep supporting the redundant cmdline set).

Keeping track of all these different breaking changes tracks would be hard, so I introduced -s STRICT=1 as a shorthand abbreviation to quickly enable into all the breakages.

What -s STRICT=1 would enable would very much depend on what kind of deprecations were going on at any particular time. If we had none, then -s STRICT=1 would amount to doing nothing, if no features were being phased away at the time.

The intent was that no project should be setting -s STRICT=1 permanently in their build system in production. It was a local dev aid. With that flag, users would have a quick way of locally building a project with a local -s STRICT=1 mod applied to the build, to check what they need to do to stay compatible with the compiler in the future.

However, the meaning of STRICT=1 has somehow changed in the past years. Now it means "like regular build, but just.. more strict". It now enables flags that have never meant to become the default flags in the future (STRICT_JS and DEFAULT_TO_CXX at least). However, curiously in src/settings.js STRICT=1 still carries the original comment, which implies that STRICT_JS=0 and DEFAULT_TO_CXX=1 would be deprecated options that are being phased away.

I think that permanent "collection" flags that serve to flip other flags for "convenience"; like the current meaning of -s STRICT, but also e.g. LEGACY_VM_SUPPORT are not a good design. It is less bad for STRICT=1, where there also exists an individual way to flip the different features that it enables, but worse so for LEGACY_VM_SUPPORT, which does not have a way to toggle the individual fields, but different features are just piled on top.

The experience with both STRICT and LEGACY_VM_SUPPORT make me torn about introducing a new -s COMPATIBLE=1 flag. On the surface it seems like a nice way to quickly opt in to certain flag sets, but I am not sure the scenario is so black and white that people would ultimately use COMPATIBLE=1, but the reality is more gray than that?

If we do intend to still go ahead with that, how about -s PORTABLE=1 or -s PORTABLE_COMPATIBLE=1, or -s POSIX_COMPATIBLE=1? Or just -s POSIX=1?

I disagree about strict no longer carrying the original meaning. Every setting STRICT is something we hope to enable by default in the future. At least that is my understanding. DEFAULT_TO_CXX for example.. I hope to disable that by default very soon to match the gcc/clang behaviour. STRICT_JS is also something we should aim to enable by default. All of these can be opt out of course. I see STRICT as way to people to opt into incompatible changes early. I think that matches your original idea behind it.

One issue with the current STRICT that we should fix is that its not possible to enable it but than opt out of one of the specific things it does. So it would be nice to say -s STRICT=1 -s SPECIFIC_THING_UNDER_STRICT=0. The pattern would also be useful for COMPATIBLE.

What don't you like about the name COMPATIBLE? It seems better to be me that PORTABLE or POSIX. Maybe I'm just used to that name because the is that name of the setting in vim that enabled compatibility with old vi.

How about FULL_POSIX, which would be consistent with things like FULL_ES2? I think FULL_ as a prefix is a good hint to the meaning of such flags.

I think any POSIX derivative is not a good idea because this flag is as much (if not more) about standard C/C++ as about POSIX. I still like COMPATIBLE best. Or maybe STANDARD (as in, matching both C/C++ and POSIX standards as close as possible)?

Regarding STRICT, I also don't see the distinction: to me the name matches the first line of the documentation quoted and doesn't match the explained original intent.

@juj re-reading your commant about STRICT I think maybe I do see the distiction between what we have today in the docs:

  • Emscripten 'strict' build mode: Drop supporting any deprecated build options

And what my understand is/was:

  • Emscripten 'strict' build mode: Drop supporting any deprecated build options *and opt into future default options*.

Would this updated definition be OK with you? Or should keep it strictly (no pun intended) the formar?

I disagree about strict no longer carrying the original meaning. Every setting STRICT is something we hope to enable by default in the future. At least that is my understanding. DEFAULT_TO_CXX for example.. I hope to disable that by default very soon to match the gcc/clang behaviour. STRICT_JS is also something we should aim to enable by default. All of these can be opt out of course. I see STRICT as way to people to opt into incompatible changes early. I think that matches your original idea behind it.

Oh sorry, my bad! I remember having this conversation in the past, and now when I looked at STRICT, and saw DEFAULT_TO_CXX and STRICT_JS, I thought the "just a stricter Emscripten" meaning was still there. In my opinion STRICT_JS=0 should never become forbidden. Some applications have all of their JS autogenerated, and I'd then pay for the 'use strict'; preamble to gain nothing.

Also, does upstream Clang really not allow building C++ with clang a.cpp -o a.exe? Tried it today, and it does work?

Would this updated definition be OK with you?

Yeah, I believe that is good. That would make sense.

I still like COMPATIBLE best. Or maybe STANDARD

The issue I have with very short general words like these is that the chance of ambiguity is extremely high. -s STRICT is a good example, because I learned later that most people first assume it means 'use strict';.

Personally I would prefer something very explicit like -s STANDARD_CXX or -s STRICT_CXX with the name of the language in the name of the option.

I would definitely use such feature as I already am familiar with the C++ standard and any deviation from it that is not explicitly requested will cause (bad) surprises through development.

Personally I would prefer something very explicit like -s STANDARD_CXX

Again, like POSIX, CXX is misleading since it's not about C++, it's also about C and about POSIX, unless we are going to have separate STANDARD_C and STANDARD_POSIX which I hope we can avoid.

The issue I have with very short general words like these is that the chance of ambiguity is extremely high.

On the other hand, at least in my case, this option will be in every compilation command line and having something long like -s DISABLE_EXCEPTION_CATCHING=0 will make the diagnostics quite noisy.

I disagree about strict no longer carrying the original meaning. Every setting STRICT is something we hope to enable by default in the future. At least that is my understanding. DEFAULT_TO_CXX for example.. I hope to disable that by default very soon to match the gcc/clang behaviour. STRICT_JS is also something we should aim to enable by default. All of these can be opt out of course. I see STRICT as way to people to opt into incompatible changes early. I think that matches your original idea behind it.

Oh sorry, my bad! I remember having this conversation in the past, and now when I looked at STRICT, and saw DEFAULT_TO_CXX and STRICT_JS, I thought the "just a stricter Emscripten" meaning was still there. In my opinion STRICT_JS=0 should never become forbidden. Some applications have all of their JS autogenerated, and I'd then pay for the 'use strict'; preamble to gain nothing.

Also, does upstream Clang really not allow building C++ with clang a.cpp -o a.exe? Tried it today, and it does work?

It will compile but it won't link because the driver won't include the C++ stdlibs:

$ cat test.cpp 
#include <iostream>

int main() {
  std::cout << "hello\n";
}
$ gcc test.cpp -o out
/usr/bin/ld: /tmp/ccZuG0f7.o: warning: relocation against `_ZSt4cout' in read-only section `.text'
/usr/bin/ld: /tmp/ccZuG0f7.o: in function `main':
test.cpp:(.text+0xe): undefined reference to `std::cout'
/usr/bin/ld: test.cpp:(.text+0x13): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
/usr/bin/ld: /tmp/ccZuG0f7.o: in function `__static_initialization_and_destruction_0(int, int)':
test.cpp:(.text+0x43): undefined reference to `std::ios_base::Init::Init()'
/usr/bin/ld: test.cpp:(.text+0x58): undefined reference to `std::ios_base::Init::~Init()'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
$ clang test.cpp -o out
/usr/bin/ld: /tmp/test-f7e3fd.o: in function `main':
test.cpp:(.text+0xa): undefined reference to `std::cout'
/usr/bin/ld: test.cpp:(.text+0x1d): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
/usr/bin/ld: /tmp/test-f7e3fd.o: in function `__cxx_global_var_init':
test.cpp:(.text.startup+0xf): undefined reference to `std::ios_base::Init::Init()'
/usr/bin/ld: test.cpp:(.text.startup+0x15): undefined reference to `std::ios_base::Init::~Init()'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I disagree about strict no longer carrying the original meaning. Every setting STRICT is something we hope to enable by default in the future. At least that is my understanding. DEFAULT_TO_CXX for example.. I hope to disable that by default very soon to match the gcc/clang behaviour. STRICT_JS is also something we should aim to enable by default. All of these can be opt out of course. I see STRICT as way to people to opt into incompatible changes early. I think that matches your original idea behind it.

Oh sorry, my bad! I remember having this conversation in the past, and now when I looked at STRICT, and saw DEFAULT_TO_CXX and STRICT_JS, I thought the "just a stricter Emscripten" meaning was still there. In my opinion STRICT_JS=0 should never become forbidden. Some applications have all of their JS autogenerated, and I'd then pay for the 'use strict'; preamble to gain nothing.

Maybe it fine for -Oz builds to strip or never include the pre-amble? But I still might be a good idea that the output should be valid in 'use strict'; mode by default. How about if you think about this option as include_only_strict_qualify_js_in_generated_output. Only folks who want to write sloppy JS libraries or EM_JS blocks would then need to opt out of it. For normal developers it could be a useful default, like a linting tool to have the output JS conform to such standards. Especially as we move towards ESM modules becoming more normal.

Anyway, I could be convinced do go other way on this one I think... and we can discuss that on different issue. Suffice to say that my hope/plan was to make STRICT_JS the default one day. Maybe its not a reasonable plan...

It will compile but it won't link because the driver won't include the C++ stdlibs:

Interesting.. On Windows it does work.

E:\code\emsdk\llvm\git\build_master_vs2019_64>type a.cpp
#include <iostream>

int main() {
  std::cout << "hello\n";
}

E:\code\emsdk\llvm\git\build_master_vs2019_64>release\bin\clang++ --version
clang version 11.0.0 (https://github.com/llvm/llvm-project.git badc7e6cf9fe9c9d5941899a929f36e5dc083770)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: E:\code\emsdk\llvm\git\build_master_vs2019_64\release\bin

E:\code\emsdk\llvm\git\build_master_vs2019_64>Release\bin\clang++ a.cpp -o a.exe

E:\code\emsdk\llvm\git\build_master_vs2019_64>a
hello

might be a good idea that the output should be valid in 'use strict'; mode by default.

Yeah, I think we (Emscripten core libraries and JS codegen) have always been, and always should be valid 'use strict'. (modulo bugs) I can't recall any cases where we would have been at odds against that. I do agree that defaulting to STRICT_JS=1 makes a lot of sense, we should do that - but we should not forbid setting STRICT_JS=0 also, in case one needs sloppy mode for a feature, or just wants to squeeze out the last few bytes.

It will compile but it won't link because the driver won't include the C++ stdlibs:

Interesting.. On Windows it does work.

E:\code\emsdk\llvm\git\build_master_vs2019_64>type a.cpp
#include <iostream>

int main() {
  std::cout << "hello\n";
}

E:\code\emsdk\llvm\git\build_master_vs2019_64>release\bin\clang++ --version
clang version 11.0.0 (https://github.com/llvm/llvm-project.git badc7e6cf9fe9c9d5941899a929f36e5dc083770)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: E:\code\emsdk\llvm\git\build_master_vs2019_64\release\bin

E:\code\emsdk\llvm\git\build_master_vs2019_64>Release\bin\clang++ a.cpp -o a.exe

Did you mean to run clang++ (with the ++ there?).. the point is that when you run it without the ++ it will not link the C++ libraries.

About the name of a possible new flag: Good point that it's not just about POSIX. How about FULL_COMPATIBILITY or FULL_COMPAT? With the meaning that it enables all flags that add compatibility for standard APIs, even at the cost of code size or speed.

I still think -s COMPATIBLE is the cleanest name:

// Include many emscripten compatibility features that are otherwise disabled by default.   For example this enables
// exception handling, full printf support, and filesystem all of which add to the size of the output but but allow more 
// programs to build and run.   Individual features can also be disable if not all of them are desired.   The list of
// settings this currently enables is:
//  - EXCEPTION_CATCHING
//  - PRINTF_LONG_DOUBLE
//  - EXIT_RUNTIME
//  - ALLOW_MEMORY_GROWTH
//  - ...
var COMPATIBLE = 0;

COMPATIBLE sgtm

I am happy with COMPATIBLE.

I still think that is a very vague name, but if others are happy with it, it is easy to agree to.

On that note, can we default to be -s SUPPORT_ERRNO=0, and have COMPATIBLE flip on -s SUPPORT_ERRNO=1? I doubt there are many developers who use errno at all, and it generates a lot of extra code.

Sounds like good idea yes.

Any ETA on this? I would really like to start using it in build2 instead of a long and noisy sequence of -s's.

I don't believe anyone is working on this. If you would like to contribute a patch it would be most welcome.

I wish I had time but unfortunately I am unable to chew what's already on my plate.

Was this page helpful?
0 / 5 - 0 ratings